From 1a2b1cec66115385f1c6107747ef9a6f95c23d34 Mon Sep 17 00:00:00 2001 From: Alex Taranovsky Date: Thu, 28 Mar 2019 15:40:33 +0200 Subject: [PATCH 001/369] magento/magento2#22010: Updates AbstractExtensibleObject and AbstractExtensibleModel annotations --- lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php | 2 ++ .../Magento/Framework/Model/AbstractExtensibleModel.php | 1 + 2 files changed, 3 insertions(+) diff --git a/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php b/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php index 97c24167d47e1..bf2967ba564ff 100644 --- a/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php +++ b/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php @@ -12,6 +12,8 @@ * * @SuppressWarnings(PHPMD.NumberOfChildren) * @api + * @deprecated + * @see \Magento\Framework\Model\AbstractExtensibleModel */ abstract class AbstractExtensibleObject extends AbstractSimpleObject implements CustomAttributesDataInterface { diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php index 1cffba2543b0b..1beacf1cc6bdd 100644 --- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php @@ -15,6 +15,7 @@ * This class defines basic data structure of how custom attributes are stored in an ExtensibleModel. * Implementations may choose to process custom attributes as their persistence requires them to. * @SuppressWarnings(PHPMD.NumberOfChildren) + * @api */ abstract class AbstractExtensibleModel extends AbstractModel implements \Magento\Framework\Api\CustomAttributesDataInterface From 49ff38a9c319c9602781b89345a209f3a86e033c Mon Sep 17 00:00:00 2001 From: Ward Cappelle Date: Sun, 21 Apr 2019 09:45:58 +0200 Subject: [PATCH 002/369] Add support for char element to dto factory To avoid the notice "Types char is not declared" (which breaks setup:upgrade) when using the very type anywhere in the MySQL database, mapping for this specific type is added to the DI as well. --- app/etc/di.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/etc/di.xml b/app/etc/di.xml index 476285878650b..4760550ba10e7 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1435,6 +1435,7 @@ \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\MediumText \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Text \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\StringBinary + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\StringBinary \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\StringBinary \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Blob \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\MediumBlob From a2695192a15a49e3f5d455bd5cae49d72bfbb124 Mon Sep 17 00:00:00 2001 From: Alexander Menk Date: Mon, 10 Jun 2019 09:30:13 +0200 Subject: [PATCH 003/369] Refactoring: Extract Link Processing Logic to own Class "LinkProcessor" --- .../Model/Import/Product.php | 198 +----------- .../Model/Import/Product/LinkProcessor.php | 283 ++++++++++++++++++ 2 files changed, 297 insertions(+), 184 deletions(-) create mode 100644 app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 0b7fbaf86826b..2f6fc64641c98 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -11,6 +11,7 @@ use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\ResourceModel\Product\Link; use Magento\CatalogImportExport\Model\Import\Product\ImageTypeProcessor; +use Magento\CatalogImportExport\Model\Import\Product\LinkProcessor; use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface; use Magento\CatalogImportExport\Model\StockItemImporterInterface; @@ -742,6 +743,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ private $productRepository; + /** + * @var LinkProcessor + */ + private $linkProcessor; + /** * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param \Magento\ImportExport\Helper\Data $importExportData @@ -787,6 +793,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity * @param StockItemImporterInterface|null $stockItemImporter * @param DateTimeFactory $dateTimeFactory * @param ProductRepositoryInterface|null $productRepository + * @oaram LinkProcessor|null $linkProcessor * @throws LocalizedException * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -836,7 +843,8 @@ public function __construct( MediaGalleryProcessor $mediaProcessor = null, StockItemImporterInterface $stockItemImporter = null, DateTimeFactory $dateTimeFactory = null, - ProductRepositoryInterface $productRepository = null + ProductRepositoryInterface $productRepository = null, + LinkProcessor $linkProcessor = null ) { $this->_eventManager = $eventManager; $this->stockRegistry = $stockRegistry; @@ -872,6 +880,8 @@ public function __construct( $this->mediaProcessor = $mediaProcessor ?: ObjectManager::getInstance()->get(MediaGalleryProcessor::class); $this->stockItemImporter = $stockItemImporter ?: ObjectManager::getInstance() ->get(StockItemImporterInterface::class); + $this->linkProcessor = $linkProcessor ?? ObjectManager::getInstance() + ->get(LinkProcessor::class); parent::__construct( $jsonHelper, $importExportData, @@ -1248,30 +1258,13 @@ protected function _prepareRowForDb(array $rowData) * * Must be called after ALL products saving done. * + * @deprecated use linkProcessor Directly + * * @return $this */ protected function _saveLinks() { - /** @var Link $resource */ - $resource = $this->_linkFactory->create(); - $mainTable = $resource->getMainTable(); - $positionAttrId = []; - $nextLinkId = $this->_resourceHelper->getNextAutoincrement($mainTable); - - // pre-load 'position' attributes ID for each link type once - foreach ($this->_linkNameToId as $linkId) { - $select = $this->_connection->select()->from( - $resource->getTable('catalog_product_link_attribute'), - ['id' => 'product_link_attribute_id'] - )->where( - 'link_type_id = :link_id AND product_link_attribute_code = :position' - ); - $bind = [':link_id' => $linkId, ':position' => 'position']; - $positionAttrId[$linkId] = $this->_connection->fetchOne($select, $bind); - } - while ($bunch = $this->_dataSourceModel->getNextBunch()) { - $this->processLinkBunches($bunch, $resource, $nextLinkId, $positionAttrId); - } + $this->linkProcessor->saveLinks($this, $this->_dataSourceModel, $this->getProductEntityLinkField()); return $this; } @@ -3034,167 +3027,4 @@ private function getValidationErrorLevel($sku): string ? ProcessingError::ERROR_LEVEL_CRITICAL : ProcessingError::ERROR_LEVEL_NOT_CRITICAL; } - - /** - * Processes link bunches - * - * @param array $bunch - * @param Link $resource - * @param int $nextLinkId - * @param array $positionAttrId - * @return void - */ - private function processLinkBunches( - array $bunch, - Link $resource, - int $nextLinkId, - array $positionAttrId - ): void { - $productIds = []; - $linkRows = []; - $positionRows = []; - - $bunch = array_filter($bunch, [$this, 'isRowAllowedToImport'], ARRAY_FILTER_USE_BOTH); - foreach ($bunch as $rowData) { - $sku = $rowData[self::COL_SKU]; - $productId = $this->skuProcessor->getNewSku($sku)[$this->getProductEntityLinkField()]; - $productIds[] = $productId; - $productLinkKeys = $this->fetchProductLinks($resource, $productId); - $linkNameToId = array_filter( - $this->_linkNameToId, - function ($linkName) use ($rowData) { - return isset($rowData[$linkName . 'sku']); - }, - ARRAY_FILTER_USE_KEY - ); - foreach ($linkNameToId as $linkName => $linkId) { - $linkSkus = explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'sku']); - $linkPositions = !empty($rowData[$linkName . 'position']) - ? explode($this->getMultipleValueSeparator(), $rowData[$linkName . 'position']) - : []; - - $linkSkus = array_filter( - $linkSkus, - function ($linkedSku) use ($sku) { - $linkedSku = trim($linkedSku); - return ($this->skuProcessor->getNewSku($linkedSku) !== null || $this->isSkuExist($linkedSku)) - && strcasecmp($linkedSku, $sku) !== 0; - } - ); - foreach ($linkSkus as $linkedKey => $linkedSku) { - $linkedId = $this->getProductLinkedId($linkedSku); - if ($linkedId == null) { - // Import file links to a SKU which is skipped for some reason, which leads to a "NULL" - // link causing fatal errors. - $formatStr = 'WARNING: Orphaned link skipped: From SKU %s (ID %d) to SKU %s, Link type id: %d'; - $exception = new \Exception(sprintf($formatStr, $sku, $productId, $linkedSku, $linkId)); - $this->_logger->critical($exception); - continue; - } - $linkKey = $this->composeLinkKey($productId, $linkedId, $linkId); - $productLinkKeys[$linkKey] = $productLinkKeys[$linkKey] ?? $nextLinkId; - - $linkRows[$linkKey] = $linkRows[$linkKey] ?? [ - 'link_id' => $productLinkKeys[$linkKey], - 'product_id' => $productId, - 'linked_product_id' => $linkedId, - 'link_type_id' => $linkId, - ]; - - if (!empty($linkPositions[$linkedKey])) { - $positionRows[] = [ - 'link_id' => $productLinkKeys[$linkKey], - 'product_link_attribute_id' => $positionAttrId[$linkId], - 'value' => $linkPositions[$linkedKey], - ]; - } - $nextLinkId++; - } - } - } - $this->saveLinksData($resource, $productIds, $linkRows, $positionRows); - } - - /** - * Fetches Product Links - * - * @param Link $resource - * @param int $productId - * @return array - */ - private function fetchProductLinks(Link $resource, int $productId) : array - { - $productLinkKeys = []; - $select = $this->_connection->select()->from( - $resource->getTable('catalog_product_link'), - ['id' => 'link_id', 'linked_id' => 'linked_product_id', 'link_type_id' => 'link_type_id'] - )->where( - 'product_id = :product_id' - ); - $bind = [':product_id' => $productId]; - foreach ($this->_connection->fetchAll($select, $bind) as $linkData) { - $linkKey = $this->composeLinkKey($productId, $linkData['linked_id'], $linkData['link_type_id']); - $productLinkKeys[$linkKey] = $linkData['id']; - } - - return $productLinkKeys; - } - - /** - * Gets the Id of the Sku - * - * @param string $linkedSku - * @return int|null - */ - private function getProductLinkedId(string $linkedSku) : ?int - { - $linkedSku = trim($linkedSku); - $newSku = $this->skuProcessor->getNewSku($linkedSku); - $linkedId = !empty($newSku) ? $newSku['entity_id'] : $this->getExistingSku($linkedSku)['entity_id']; - return $linkedId; - } - - /** - * Saves information about product links - * - * @param Link $resource - * @param array $productIds - * @param array $linkRows - * @param array $positionRows - * @throws LocalizedException - */ - private function saveLinksData(Link $resource, array $productIds, array $linkRows, array $positionRows) - { - $mainTable = $resource->getMainTable(); - if (Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) { - $this->_connection->delete( - $mainTable, - $this->_connection->quoteInto('product_id IN (?)', array_unique($productIds)) - ); - } - if ($linkRows) { - $this->_connection->insertOnDuplicate($mainTable, $linkRows, ['link_id']); - } - if ($positionRows) { - // process linked product positions - $this->_connection->insertOnDuplicate( - $resource->getAttributeTypeTable('int'), - $positionRows, - ['value'] - ); - } - } - - /** - * Composes the link key - * - * @param int $productId - * @param int $linkedId - * @param int $linkTypeId - * @return string - */ - private function composeLinkKey(int $productId, int $linkedId, int $linkTypeId) : string - { - return "{$productId}-{$linkedId}-{$linkTypeId}"; - } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php new file mode 100644 index 0000000000000..303ab77f83cad --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -0,0 +1,283 @@ + \Magento\Catalog\Model\Product\Link::LINK_TYPE_RELATED, + '_crosssell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL, + '_upsell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL, + ]; + + /** @var LinkFactory */ + private $linkFactory; + + /** @var Helper */ + private $resourceHelper; + + /** @var SkuProcessor */ + protected $skuProcessor; + + /** @var LoggerInterface */ + protected $logger; + + public function __construct( + LinkFactory $linkFactory, + Helper $resourceHelper, + SkuProcessor $skuProcessor, + LoggerInterface $logger + ) { + $this->linkFactory = $linkFactory; + $this->resourceHelper = $resourceHelper; + $this->skuProcessor = $skuProcessor; + $this->logger = $logger; + } + + /** + * Gather and save information about product links. + * + * Must be called after ALL products saving done. + * + * @return $this + * @throws LocalizedException + */ + public function saveLinks( + Product $importEntity, + Data $dataSourceModel, + string $linkField + ) { + /** @var Link $resource */ + $resource = $this->linkFactory->create(); + $mainTable = $resource->getMainTable(); + $positionAttrId = []; + $nextLinkId = $this->resourceHelper->getNextAutoincrement($mainTable); + + // pre-load 'position' attributes ID for each link type once + foreach ($this->_linkNameToId as $linkId) { + $select = $importEntity->getConnection()->select()->from( + $resource->getTable('catalog_product_link_attribute'), + ['id' => 'product_link_attribute_id'] + )->where( + 'link_type_id = :link_id AND product_link_attribute_code = :position' + ); + $bind = [':link_id' => $linkId, ':position' => 'position']; + $positionAttrId[$linkId] = $importEntity->getConnection()->fetchOne($select, $bind); + } + while ($bunch = $dataSourceModel->getNextBunch()) { + $this->processLinkBunches($importEntity, $dataSourceModel, $linkField, $bunch, $resource, $nextLinkId, $positionAttrId); + } + + return $this; + } + + /** + * Processes link bunches + * + * @param array $bunch + * @param Link $resource + * @param int $nextLinkId + * @param array $positionAttrId + * + * @return void + * @throws LocalizedException + */ + private function processLinkBunches( + Product $importEntity, + Data $dataSourceModel, + string $linkField, + array $bunch, + Link $resource, + int $nextLinkId, + array $positionAttrId + ): void { + $productIds = []; + $linkRows = []; + $positionRows = []; + + $bunch = array_filter($bunch, [$importEntity, 'isRowAllowedToImport'], ARRAY_FILTER_USE_BOTH); + foreach ($bunch as $rowData) { + $sku = $rowData[Product::COL_SKU]; + $productId = $this->skuProcessor->getNewSku($sku)[$linkField]; + $productIds[] = $productId; + $productLinkKeys = $this->fetchProductLinks($importEntity, $resource, $productId); + $linkNameToId = array_filter( + $this->_linkNameToId, + function ($linkName) use ($rowData) { + return isset($rowData[$linkName . 'sku']); + }, + ARRAY_FILTER_USE_KEY + ); + foreach ($linkNameToId as $linkName => $linkId) { + $linkSkus = explode($importEntity->getMultipleValueSeparator(), $rowData[$linkName . 'sku']); + $linkPositions = ! empty($rowData[$linkName . 'position']) + ? explode($importEntity->getMultipleValueSeparator(), $rowData[$linkName . 'position']) + : []; + + $linkSkus = array_filter( + $linkSkus, + function ($linkedSku) use ($sku, $importEntity) { + $linkedSku = trim($linkedSku); + + return ( + $this->skuProcessor->getNewSku($linkedSku) !== null + || $this->isSkuExist($importEntity, $linkedSku) + ) + && strcasecmp($linkedSku, $sku) !== 0; + } + ); + foreach ($linkSkus as $linkedKey => $linkedSku) { + $linkedId = $this->getProductLinkedId($linkedSku); + if ($linkedId == null) { + // Import file links to a SKU which is skipped for some reason, which leads to a "NULL" + // link causing fatal errors. + $formatStr = 'WARNING: Orphaned link skipped: From SKU %s (ID %d) to SKU %s, Link type id: %d'; + $exception = new \Exception(sprintf($formatStr, $sku, $productId, $linkedSku, $linkId)); + $this->logger->critical($exception); + continue; + } + $linkKey = $this->composeLinkKey($productId, $linkedId, $linkId); + $productLinkKeys[$linkKey] = $productLinkKeys[$linkKey] ?? $nextLinkId; + + $linkRows[$linkKey] = $linkRows[$linkKey] ?? [ + 'link_id' => $productLinkKeys[$linkKey], + 'product_id' => $productId, + 'linked_product_id' => $linkedId, + 'link_type_id' => $linkId, + ]; + + if (! empty($linkPositions[$linkedKey])) { + $positionRows[] = [ + 'link_id' => $productLinkKeys[$linkKey], + 'product_link_attribute_id' => $positionAttrId[$linkId], + 'value' => $linkPositions[$linkedKey], + ]; + } + $nextLinkId++; + } + } + } + $this->saveLinksData($importEntity, $resource, $productIds, $linkRows, $positionRows); + } + + /** + * Check if product exists for specified SKU + * + * @param string $sku + * @return bool + */ + private function isSkuExist(Product $importEntity, $sku) + { + $sku = strtolower($sku); + return isset($importEntity->getOldSku()[$sku]); + } + + /** + * Fetches Product Links + * + * @param Link $resource + * @param int $productId + * + * @return array + */ + private function fetchProductLinks(Product $importEntity, Link $resource, int $productId): array + { + $productLinkKeys = []; + $select = $importEntity->getConnection()->select()->from( + $resource->getTable('catalog_product_link'), + ['id' => 'link_id', 'linked_id' => 'linked_product_id', 'link_type_id' => 'link_type_id'] + )->where( + 'product_id = :product_id' + ); + $bind = [':product_id' => $productId]; + foreach ($importEntity->getConnection()->fetchAll($select, $bind) as $linkData) { + $linkKey = $this->composeLinkKey($productId, $linkData['linked_id'], $linkData['link_type_id']); + $productLinkKeys[$linkKey] = $linkData['id']; + } + + return $productLinkKeys; + } + + /** + * Gets the Id of the Sku + * + * @param string $linkedSku + * + * @return int|null + */ + private function getProductLinkedId(string $linkedSku): ?int + { + $linkedSku = trim($linkedSku); + $newSku = $this->skuProcessor->getNewSku($linkedSku); + $linkedId = ! empty($newSku) ? $newSku['entity_id'] : $this->getExistingSku($linkedSku)['entity_id']; + + return $linkedId; + } + + /** + * Saves information about product links + * + * @param Link $resource + * @param array $productIds + * @param array $linkRows + * @param array $positionRows + * + * @throws LocalizedException + */ + private function saveLinksData(Product $importEntity, Link $resource, array $productIds, array $linkRows, array $positionRows) + { + $mainTable = $resource->getMainTable(); + if (Import::BEHAVIOR_APPEND != $importEntity->getBehavior() && $productIds) { + $importEntity->getConnection()->delete( + $mainTable, + $importEntity->getConnection()->quoteInto('product_id IN (?)', array_unique($productIds)) + ); + } + if ($linkRows) { + $importEntity->getConnection()->insertOnDuplicate($mainTable, $linkRows, ['link_id']); + } + if ($positionRows) { + // process linked product positions + $importEntity->getConnection()->insertOnDuplicate( + $resource->getAttributeTypeTable('int'), + $positionRows, + ['value'] + ); + } + } + + /** + * Composes the link key + * + * @param int $productId + * @param int $linkedId + * @param int $linkTypeId + * + * @return string + */ + private function composeLinkKey(int $productId, int $linkedId, int $linkTypeId): string + { + return "{$productId}-{$linkedId}-{$linkTypeId}"; + } +} From 095c4346a8c6d47062817b65c64e91c009b840c4 Mon Sep 17 00:00:00 2001 From: Alexander Menk Date: Mon, 10 Jun 2019 09:36:06 +0200 Subject: [PATCH 004/369] Import: Allow injecting own link types to LinkProcessor --- .../Model/Import/Product.php | 4 ++++ .../Model/Import/Product/LinkProcessor.php | 18 +++++++++++++++--- .../Magento/CatalogImportExport/etc/di.xml | 9 +++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 2f6fc64641c98..ab9151b8e1e0a 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -222,6 +222,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity /** * Links attribute name-to-link type ID. * + * @deprecated use DI for LinkProcessor class if you want to add additional types + * * @var array */ protected $_linkNameToId = [ @@ -882,6 +884,8 @@ public function __construct( ->get(StockItemImporterInterface::class); $this->linkProcessor = $linkProcessor ?? ObjectManager::getInstance() ->get(LinkProcessor::class); + $this->linkProcessor->addNameToIds($this->_linkNameToId); + parent::__construct( $jsonHelper, $importExportData, diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index 303ab77f83cad..8f50132e5ec88 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -30,6 +30,10 @@ class LinkProcessor '_crosssell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL, '_upsell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL, ]; + /** + * @var array + */ + private $linkNameToId; /** @var LinkFactory */ private $linkFactory; @@ -38,21 +42,24 @@ class LinkProcessor private $resourceHelper; /** @var SkuProcessor */ - protected $skuProcessor; + private $skuProcessor; /** @var LoggerInterface */ - protected $logger; + private $logger; public function __construct( LinkFactory $linkFactory, Helper $resourceHelper, SkuProcessor $skuProcessor, - LoggerInterface $logger + LoggerInterface $logger, + array $linkNameToId ) { $this->linkFactory = $linkFactory; $this->resourceHelper = $resourceHelper; $this->skuProcessor = $skuProcessor; $this->logger = $logger; + + $this->linkNameToId = $linkNameToId; } /** @@ -92,6 +99,11 @@ public function saveLinks( return $this; } + public function addNameToIds($nameToIds) + { + $this->linkNameToId = array_merge($nameToIds, $this->linkNameToId); + } + /** * Processes link bunches * diff --git a/app/code/Magento/CatalogImportExport/etc/di.xml b/app/code/Magento/CatalogImportExport/etc/di.xml index 6906272b11d68..64b6e7bcb2afa 100644 --- a/app/code/Magento/CatalogImportExport/etc/di.xml +++ b/app/code/Magento/CatalogImportExport/etc/di.xml @@ -28,4 +28,13 @@ + + + + Magento\Catalog\Model\Product\Link::LINK_TYPE_RELATED + Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL + Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL + + + From 84ccd7fc3f9fbe513007c1b3763a6ab4fa2d2886 Mon Sep 17 00:00:00 2001 From: Alexander Menk Date: Mon, 10 Jun 2019 11:47:15 +0200 Subject: [PATCH 005/369] Remove default values from property, as they are injected via DI --- .../Model/Import/Product/LinkProcessor.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index 8f50132e5ec88..a6ffc9a67084f 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -21,15 +21,10 @@ class LinkProcessor { /** - * Links attribute name-to-link type ID. - * TODO: inject via DI * @var array */ - protected $_linkNameToId = [ - '_related_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_RELATED, - '_crosssell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL, - '_upsell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL, - ]; + private $_linkNameToId; + /** * @var array */ From 3b2a4fe0c11f50a624b26c116e7e2857967b092b Mon Sep 17 00:00:00 2001 From: Alexander Menk Date: Mon, 10 Jun 2019 15:59:22 +0200 Subject: [PATCH 006/369] Fix initialisation of linkNameToId, unused variable, run phpcbf --- .../Model/Import/Product/LinkProcessor.php | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index a6ffc9a67084f..9bd454fb84b4f 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -16,30 +16,37 @@ /** * Class LinkProcessor - * */ class LinkProcessor { /** * @var array */ - private $_linkNameToId; + private $_linkNameToId = []; /** * @var array */ private $linkNameToId; - /** @var LinkFactory */ + /** + * @var LinkFactory + */ private $linkFactory; - /** @var Helper */ + /** + * @var Helper + */ private $resourceHelper; - /** @var SkuProcessor */ + /** + * @var SkuProcessor + */ private $skuProcessor; - /** @var LoggerInterface */ + /** + * @var LoggerInterface + */ private $logger; public function __construct( @@ -70,7 +77,9 @@ public function saveLinks( Data $dataSourceModel, string $linkField ) { - /** @var Link $resource */ + /** + * @var Link $resource + */ $resource = $this->linkFactory->create(); $mainTable = $resource->getMainTable(); $positionAttrId = []; @@ -88,7 +97,7 @@ public function saveLinks( $positionAttrId[$linkId] = $importEntity->getConnection()->fetchOne($select, $bind); } while ($bunch = $dataSourceModel->getNextBunch()) { - $this->processLinkBunches($importEntity, $dataSourceModel, $linkField, $bunch, $resource, $nextLinkId, $positionAttrId); + $this->processLinkBunches($importEntity, $linkField, $bunch, $resource, $nextLinkId, $positionAttrId); } return $this; @@ -103,8 +112,8 @@ public function addNameToIds($nameToIds) * Processes link bunches * * @param array $bunch - * @param Link $resource - * @param int $nextLinkId + * @param Link $resource + * @param int $nextLinkId * @param array $positionAttrId * * @return void @@ -112,7 +121,6 @@ public function addNameToIds($nameToIds) */ private function processLinkBunches( Product $importEntity, - Data $dataSourceModel, string $linkField, array $bunch, Link $resource, @@ -191,7 +199,7 @@ function ($linkedSku) use ($sku, $importEntity) { /** * Check if product exists for specified SKU * - * @param string $sku + * @param string $sku * @return bool */ private function isSkuExist(Product $importEntity, $sku) @@ -204,7 +212,7 @@ private function isSkuExist(Product $importEntity, $sku) * Fetches Product Links * * @param Link $resource - * @param int $productId + * @param int $productId * * @return array */ @@ -245,7 +253,7 @@ private function getProductLinkedId(string $linkedSku): ?int /** * Saves information about product links * - * @param Link $resource + * @param Link $resource * @param array $productIds * @param array $linkRows * @param array $positionRows From f06e4d7aed9639aa2eb8a0f27c8c1e0dc727a980 Mon Sep 17 00:00:00 2001 From: Alexander Menk Date: Mon, 10 Jun 2019 19:27:53 +0200 Subject: [PATCH 007/369] Fix testProductWithLinks failing --- .../Model/Import/Product/LinkProcessor.php | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index 9bd454fb84b4f..fecd0cd06bdc4 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -19,11 +19,6 @@ */ class LinkProcessor { - /** - * @var array - */ - private $_linkNameToId = []; - /** * @var array */ @@ -86,7 +81,7 @@ public function saveLinks( $nextLinkId = $this->resourceHelper->getNextAutoincrement($mainTable); // pre-load 'position' attributes ID for each link type once - foreach ($this->_linkNameToId as $linkId) { + foreach ($this->linkNameToId as $linkId) { $select = $importEntity->getConnection()->select()->from( $resource->getTable('catalog_product_link_attribute'), ['id' => 'product_link_attribute_id'] @@ -138,7 +133,7 @@ private function processLinkBunches( $productIds[] = $productId; $productLinkKeys = $this->fetchProductLinks($importEntity, $resource, $productId); $linkNameToId = array_filter( - $this->_linkNameToId, + $this->linkNameToId, function ($linkName) use ($rowData) { return isset($rowData[$linkName . 'sku']); }, From 7dbdfd5b9e10adbd2842211d693ead06c7c50cfa Mon Sep 17 00:00:00 2001 From: Alexander Menk Date: Mon, 10 Jun 2019 19:54:29 +0200 Subject: [PATCH 008/369] Fix CodeStyle problems --- .../Model/Import/Product/LinkProcessor.php | 61 +++++++++++++------ 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index fecd0cd06bdc4..ff5e4527a0ebf 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -25,25 +25,34 @@ class LinkProcessor private $linkNameToId; /** - * @var LinkFactory + * @var LinkFactory */ private $linkFactory; /** - * @var Helper + * @var Helper */ private $resourceHelper; /** - * @var SkuProcessor + * @var SkuProcessor */ private $skuProcessor; /** - * @var LoggerInterface + * @var LoggerInterface */ private $logger; + /** + * LinkProcessor constructor. + * + * @param LinkFactory $linkFactory + * @param Helper $resourceHelper + * @param SkuProcessor $skuProcessor + * @param LoggerInterface $logger + * @param array $linkNameToId + */ public function __construct( LinkFactory $linkFactory, Helper $resourceHelper, @@ -64,6 +73,9 @@ public function __construct( * * Must be called after ALL products saving done. * + * @param Product $importEntity + * @param Data $dataSourceModel + * @param string $linkField * @return $this * @throws LocalizedException */ @@ -71,10 +83,7 @@ public function saveLinks( Product $importEntity, Data $dataSourceModel, string $linkField - ) { - /** - * @var Link $resource - */ + ): void { $resource = $this->linkFactory->create(); $mainTable = $resource->getMainTable(); $positionAttrId = []; @@ -94,11 +103,15 @@ public function saveLinks( while ($bunch = $dataSourceModel->getNextBunch()) { $this->processLinkBunches($importEntity, $linkField, $bunch, $resource, $nextLinkId, $positionAttrId); } - - return $this; } - public function addNameToIds($nameToIds) + /** + * Add link types (exists for backwards compatibility) + * + * @deprecated Use DI to inject to the constructor + * @param array $nameToIds + */ + public function addNameToIds(array $nameToIds): void { $this->linkNameToId = array_merge($nameToIds, $this->linkNameToId); } @@ -106,9 +119,11 @@ public function addNameToIds($nameToIds) /** * Processes link bunches * + * @param Product $importEntity + * @param string $linkField * @param array $bunch - * @param Link $resource - * @param int $nextLinkId + * @param Link $resource + * @param int $nextLinkId * @param array $positionAttrId * * @return void @@ -194,10 +209,11 @@ function ($linkedSku) use ($sku, $importEntity) { /** * Check if product exists for specified SKU * - * @param string $sku + * @param Product $importEntity + * @param string $sku * @return bool */ - private function isSkuExist(Product $importEntity, $sku) + private function isSkuExist(Product $importEntity, string $sku): bool { $sku = strtolower($sku); return isset($importEntity->getOldSku()[$sku]); @@ -206,8 +222,9 @@ private function isSkuExist(Product $importEntity, $sku) /** * Fetches Product Links * + * @param Product $importEntity * @param Link $resource - * @param int $productId + * @param int $productId * * @return array */ @@ -248,15 +265,21 @@ private function getProductLinkedId(string $linkedSku): ?int /** * Saves information about product links * - * @param Link $resource + * @param Product $importEntity + * @param Link $resource * @param array $productIds * @param array $linkRows * @param array $positionRows * * @throws LocalizedException */ - private function saveLinksData(Product $importEntity, Link $resource, array $productIds, array $linkRows, array $positionRows) - { + private function saveLinksData( + Product $importEntity, + Link $resource, + array $productIds, + array $linkRows, + array $positionRows + ) { $mainTable = $resource->getMainTable(); if (Import::BEHAVIOR_APPEND != $importEntity->getBehavior() && $productIds) { $importEntity->getConnection()->delete( From 39542fd5dabf869ecd58b914cb886c368ea6bba0 Mon Sep 17 00:00:00 2001 From: Alexander Menk Date: Mon, 10 Jun 2019 21:10:09 +0200 Subject: [PATCH 009/369] Fix one code sniffer problem; Add phpcs:disable... ...for code style problem existing before and add according FIXME --- .../Magento/CatalogImportExport/Model/Import/Product.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index ab9151b8e1e0a..c229a4656f3c3 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -795,7 +795,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity * @param StockItemImporterInterface|null $stockItemImporter * @param DateTimeFactory $dateTimeFactory * @param ProductRepositoryInterface|null $productRepository - * @oaram LinkProcessor|null $linkProcessor + * @param LinkProcessor|null $linkProcessor * @throws LocalizedException * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -1496,14 +1496,19 @@ public function getImagesFromRow(array $rowData) return [$images, $labels]; } + // phpcs:disable Generic.Metrics.NestingLevel + /** * Gather and save information about product entities. * + * FIXME: Reduce nesting level + * * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @throws LocalizedException */ protected function _saveProducts() @@ -1880,6 +1885,8 @@ protected function _saveProducts() return $this; } + // phpcs:enable + /** * Prepare array with image states (visible or hidden from product page) * From 4fbbcf14b60efb49d2a9f1078109ac2ab6920000 Mon Sep 17 00:00:00 2001 From: Alexander Menk Date: Mon, 10 Jun 2019 21:11:05 +0200 Subject: [PATCH 010/369] Do not call deprecated code --- app/code/Magento/CatalogImportExport/Model/Import/Product.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index c229a4656f3c3..045252fd3d3ba 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1123,6 +1123,7 @@ protected function _replaceProducts() * Save products data. * * @return $this + * @throws LocalizedException */ protected function _saveProductsData() { @@ -1130,7 +1131,7 @@ protected function _saveProductsData() foreach ($this->_productTypeModels as $productTypeModel) { $productTypeModel->saveData(); } - $this->_saveLinks(); + $this->linkProcessor->saveLinks($this, $this->_dataSourceModel, $this->getProductEntityLinkField()); $this->_saveStockItem(); if ($this->_replaceFlag) { $this->getOptionEntity()->clearProductsSkuToId(); From a40815ebf0a35cd0cf6efbcb7c63c8645520ad4c Mon Sep 17 00:00:00 2001 From: Francis Gallagher Date: Wed, 17 Jul 2019 12:01:49 +0100 Subject: [PATCH 011/369] Add Header (h1>h6) tags to layout htmlTags Allowed types --- .../Magento/Framework/View/Layout/Generator/Container.php | 6 ++++++ lib/internal/Magento/Framework/View/Layout/etc/elements.xsd | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lib/internal/Magento/Framework/View/Layout/Generator/Container.php b/lib/internal/Magento/Framework/View/Layout/Generator/Container.php index d220808e21455..9a8953ec08312 100644 --- a/lib/internal/Magento/Framework/View/Layout/Generator/Container.php +++ b/lib/internal/Magento/Framework/View/Layout/Generator/Container.php @@ -39,6 +39,12 @@ class Container implements Layout\GeneratorInterface 'table', 'tfoot', 'ul', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', ]; /** diff --git a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd index 17857c9ab0658..43456ce27b044 100755 --- a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd @@ -140,6 +140,12 @@ + + + + + + From 64405fa21c617bbe37dd88cca05c31f334cfeb8a Mon Sep 17 00:00:00 2001 From: Francis Date: Wed, 17 Jul 2019 13:11:05 +0100 Subject: [PATCH 012/369] Correct Integration test to know about new tags --- .../integration/testsuite/Magento/Framework/View/LayoutTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/LayoutTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/LayoutTest.php index 66e8eb3e453f9..dc5b29f8d26f5 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/LayoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/View/LayoutTest.php @@ -274,7 +274,7 @@ public function testAddContainerInvalidHtmlTag() { $msg = 'Html tag "span" is forbidden for usage in containers. ' . 'Consider to use one of the allowed: aside, dd, div, dl, fieldset, main, nav, ' . - 'header, footer, ol, p, section, table, tfoot, ul.'; + 'header, footer, ol, p, section, table, tfoot, ul, h1, h2, h3, h4, h5, h6.'; $this->expectException(\Magento\Framework\Exception\LocalizedException::class); $this->expectExceptionMessage($msg); $this->_layout->addContainer('container', 'Container', ['htmlTag' => 'span']); From 5124dfb61211e9a28ce0a9016e99eb65022b761b Mon Sep 17 00:00:00 2001 From: Francis Gallagher Date: Fri, 19 Jul 2019 09:54:32 +0100 Subject: [PATCH 013/369] Added annotation data to Container class to pass current coding style test --- .../Framework/View/Layout/Generator/Container.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/View/Layout/Generator/Container.php b/lib/internal/Magento/Framework/View/Layout/Generator/Container.php index 9a8953ec08312..1551714a6a7d4 100644 --- a/lib/internal/Magento/Framework/View/Layout/Generator/Container.php +++ b/lib/internal/Magento/Framework/View/Layout/Generator/Container.php @@ -7,6 +7,12 @@ use Magento\Framework\View\Layout; +/** + * Creates html container and validates element + * + * Class Container + * @package Magento\Framework\View\Layout\Generator + */ class Container implements Layout\GeneratorInterface { /**#@+ @@ -48,7 +54,7 @@ class Container implements Layout\GeneratorInterface ]; /** - * {@inheritdoc} + * @inheritdoc * * @return string */ @@ -101,6 +107,8 @@ public function generateContainer( } /** + * Validates allowed htmlTags for layout + * * @param array $options * @return void * @throws \Magento\Framework\Exception\LocalizedException From 2ac0fe3ac5c5accab736a3001c70817eaf785117 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 16 Aug 2019 15:53:52 +0530 Subject: [PATCH 014/369] force change files --- app/etc/NonComposerComponentRegistration.php | 0 app/etc/db_schema.xml | 0 app/etc/di.xml | 0 app/etc/registration_globlist.php | 0 app/etc/vendor_path.php | 0 generated/.htaccess | 0 pub/media/.htaccess | 0 pub/media/customer/.htaccess | 0 pub/media/downloadable/.htaccess | 0 pub/media/import/.htaccess | 0 pub/media/theme_customization/.htaccess | 0 pub/static/.htaccess | 0 var/.htaccess | 0 13 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 app/etc/NonComposerComponentRegistration.php mode change 100644 => 100755 app/etc/db_schema.xml mode change 100644 => 100755 app/etc/di.xml mode change 100644 => 100755 app/etc/registration_globlist.php mode change 100644 => 100755 app/etc/vendor_path.php mode change 100644 => 100755 generated/.htaccess mode change 100644 => 100755 pub/media/.htaccess mode change 100644 => 100755 pub/media/customer/.htaccess mode change 100644 => 100755 pub/media/downloadable/.htaccess mode change 100644 => 100755 pub/media/import/.htaccess mode change 100644 => 100755 pub/media/theme_customization/.htaccess mode change 100644 => 100755 pub/static/.htaccess mode change 100644 => 100755 var/.htaccess diff --git a/app/etc/NonComposerComponentRegistration.php b/app/etc/NonComposerComponentRegistration.php old mode 100644 new mode 100755 diff --git a/app/etc/db_schema.xml b/app/etc/db_schema.xml old mode 100644 new mode 100755 diff --git a/app/etc/di.xml b/app/etc/di.xml old mode 100644 new mode 100755 diff --git a/app/etc/registration_globlist.php b/app/etc/registration_globlist.php old mode 100644 new mode 100755 diff --git a/app/etc/vendor_path.php b/app/etc/vendor_path.php old mode 100644 new mode 100755 diff --git a/generated/.htaccess b/generated/.htaccess old mode 100644 new mode 100755 diff --git a/pub/media/.htaccess b/pub/media/.htaccess old mode 100644 new mode 100755 diff --git a/pub/media/customer/.htaccess b/pub/media/customer/.htaccess old mode 100644 new mode 100755 diff --git a/pub/media/downloadable/.htaccess b/pub/media/downloadable/.htaccess old mode 100644 new mode 100755 diff --git a/pub/media/import/.htaccess b/pub/media/import/.htaccess old mode 100644 new mode 100755 diff --git a/pub/media/theme_customization/.htaccess b/pub/media/theme_customization/.htaccess old mode 100644 new mode 100755 diff --git a/pub/static/.htaccess b/pub/static/.htaccess old mode 100644 new mode 100755 diff --git a/var/.htaccess b/var/.htaccess old mode 100644 new mode 100755 From d78361ee7b1ecfd207a0415f982055beceb9fa5e Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 16 Aug 2019 17:26:04 +0530 Subject: [PATCH 015/369] Admin Order - Email is Now Required - Magento 2.3 Updated --- .../Magento/Customer/etc/adminhtml/system.xml | 5 ++++ app/code/Magento/Customer/etc/config.xml | 1 + .../Adminhtml/Order/Create/Form/Account.php | 17 ++++++++++- .../Magento/Sales/Model/AdminOrder/Create.php | 28 +++++++++++++++++-- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml index 2bd1041214801..325de3680b463 100644 --- a/app/code/Magento/Customer/etc/adminhtml/system.xml +++ b/app/code/Magento/Customer/etc/adminhtml/system.xml @@ -86,6 +86,11 @@ To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes. Magento\Config\Model\Config\Source\Yesno + + + If set YES Email field will be required during Admin order creation for new Customer. + Magento\Config\Model\Config\Source\Yesno + diff --git a/app/code/Magento/Customer/etc/config.xml b/app/code/Magento/Customer/etc/config.xml index da4b80536e631..d979cf19fc5f7 100644 --- a/app/code/Magento/Customer/etc/config.xml +++ b/app/code/Magento/Customer/etc/config.xml @@ -15,6 +15,7 @@ 0 1 billing + 0 example.com general customer_create_account_email_template diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 03915c0499367..28b1cc9c80a91 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -39,6 +39,8 @@ class Account extends AbstractForm */ protected $_extensibleDataObjectConverter; + private const XML_PATH_EMAIL_REQUIRED_CREATE_ORDER = 'customer/create_account/email_required_create_order'; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Backend\Model\Session\Quote $sessionQuote @@ -147,7 +149,7 @@ protected function _addAdditionalFormElementData(AbstractElement $element) { switch ($element->getId()) { case 'email': - $element->setRequired(1); + $element->setRequired($this->isEmailRequiredCreateOrder()); $element->setClass('validate-email admin__control-text'); break; } @@ -204,4 +206,17 @@ private function extractValuesFromAttributes(array $attributes): array return $formValues; } + + /** + * Retrieve email is required field for admin order creation + * + * @return bool + */ + private function isEmailRequiredCreateOrder() + { + return $this->_scopeConfig->getValue( + self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } } diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 01f1fe850b837..211c6f75dad18 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -33,6 +33,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ */ const XML_PATH_DEFAULT_EMAIL_DOMAIN = 'customer/create_account/email_domain'; + private const XML_PATH_EMAIL_REQUIRED_CREATE_ORDER = 'customer/create_account/email_required_create_order'; /** * Quote session object * @@ -1369,7 +1370,7 @@ protected function _setQuoteAddress(\Magento\Quote\Model\Quote\Address $address, $data = isset($data['region']) && is_array($data['region']) ? array_merge($data, $data['region']) : $data; $addressForm = $this->_metadataFormFactory->create( - + AddressMetadataInterface::ENTITY_TYPE_ADDRESS, 'adminhtml_customer_address', $data, @@ -2037,7 +2038,30 @@ protected function _validate() */ protected function _getNewCustomerEmail() { - return $this->getData('account/email'); + $emailrequired = $this->_scopeConfig->getValue( + self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + + if($emailrequired) { + return $this->getData('account/email'); + } else { + $email = $this->getData('account/email'); + if (empty($email)) { + $host = $this->_scopeConfig->getValue( + self::XML_PATH_DEFAULT_EMAIL_DOMAIN, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + $account = time(); + $email = $account . '@' . $host; + $account = $this->getData('account'); + $account['email'] = $email; + $this->setData('account', $account); + } + + return $email; + } + } /** From 078bd695742a96210b16a4e5bd45b98e8a9b0301 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Mon, 9 Sep 2019 17:46:11 +0530 Subject: [PATCH 016/369] Email field translation --- app/code/Magento/Customer/i18n/en_US.csv | 770 +++++++++--------- .../Adminhtml/Order/Create/Form/Account.php | 4 +- .../Magento/Sales/Model/AdminOrder/Create.php | 7 +- 3 files changed, 391 insertions(+), 390 deletions(-) mode change 100644 => 100755 app/code/Magento/Customer/i18n/en_US.csv mode change 100644 => 100755 app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php mode change 100644 => 100755 app/code/Magento/Sales/Model/AdminOrder/Create.php diff --git a/app/code/Magento/Customer/i18n/en_US.csv b/app/code/Magento/Customer/i18n/en_US.csv old mode 100644 new mode 100755 index 3495feb925cb3..74c30a47a835e --- a/app/code/Magento/Customer/i18n/en_US.csv +++ b/app/code/Magento/Customer/i18n/en_US.csv @@ -1,23 +1,23 @@ -"Sign Out","Sign Out" -"Sign In","Sign In" -"You are subscribed to our newsletter.","You are subscribed to our newsletter." -"You aren't subscribed to our newsletter.","You aren't subscribed to our newsletter." -"You have not set a default shipping address.","You have not set a default shipping address." -"You have not set a default billing address.","You have not set a default billing address." -"Address Book","Address Book" -"Edit Address","Edit Address" -"Add New Address","Add New Address" +Sign Out,Sign Out +Sign In,Sign In +You are subscribed to our newsletter.,You are subscribed to our newsletter. +You aren't subscribed to our newsletter.,You aren't subscribed to our newsletter. +You have not set a default shipping address.,You have not set a default shipping address. +You have not set a default billing address.,You have not set a default billing address. +Address Book,Address Book +Edit Address,Edit Address +Add New Address,Add New Address region,region -"Create Order","Create Order" -"Save Customer","Save Customer" -"Delete Customer","Delete Customer" -"Reset Password","Reset Password" -"Are you sure you want to revoke the customer's tokens?","Are you sure you want to revoke the customer's tokens?" -"Force Sign-In","Force Sign-In" -"New Customer","New Customer" -"Save and Continue Edit","Save and Continue Edit" +Create Order,Create Order +Save Customer,Save Customer +Delete Customer,Delete Customer +Reset Password,Reset Password +Are you sure you want to revoke the customer's tokens?,Are you sure you want to revoke the customer's tokens? +Force Sign-In,Force Sign-In +New Customer,New Customer +Save and Continue Edit,Save and Continue Edit Back,Back -"Please select","Please select" +Please select,Please select Reset,Reset ID,ID Product,Product @@ -28,186 +28,186 @@ Total,Total Action,Action Configure,Configure Delete,Delete -"Shopping Cart from %1","Shopping Cart from %1" +Shopping Cart from %1,Shopping Cart from %1 Newsletter,Newsletter -"Newsletter Information","Newsletter Information" -"Subscribed to Newsletter","Subscribed to Newsletter" -"Last Date Subscribed","Last Date Subscribed" -"Last Date Unsubscribed","Last Date Unsubscribed" -"No Newsletter Found","No Newsletter Found" -"Start date","Start date" -"End Date","End Date" -"Receive Date","Receive Date" +Newsletter Information,Newsletter Information +Subscribed to Newsletter,Subscribed to Newsletter +Last Date Subscribed,Last Date Subscribed +Last Date Unsubscribed,Last Date Unsubscribed +No Newsletter Found,No Newsletter Found +Start date,Start date +End Date,End Date +Receive Date,Receive Date Subject,Subject Status,Status Sent,Sent Cancel,Cancel -"Not Sent","Not Sent" +Not Sent,Not Sent Sending,Sending Paused,Paused View,View Unknown,Unknown -"Order #","Order #" +Order #,Order # Purchased,Purchased -"Bill-to Name","Bill-to Name" -"Ship-to Name","Ship-to Name" -"Order Total","Order Total" -"Purchase Point","Purchase Point" -"Customer View","Customer View" -"There are no items in customer's shopping cart.","There are no items in customer's shopping cart." +Bill-to Name,Bill-to Name +Ship-to Name,Ship-to Name +Order Total,Order Total +Purchase Point,Purchase Point +Customer View,Customer View +There are no items in customer's shopping cart.,There are no items in customer's shopping cart. Qty,Qty Confirmed,Confirmed -"Confirmation Required","Confirmation Required" -"Confirmation Not Required","Confirmation Not Required" +Confirmation Required,Confirmation Required +Confirmation Not Required,Confirmation Not Required Indeterminate,Indeterminate -"The customer does not have default billing address.","The customer does not have default billing address." +The customer does not have default billing address.,The customer does not have default billing address. Offline,Offline Online,Online Never,Never Unlocked,Unlocked Locked,Locked -"Deleted Stores","Deleted Stores" -"Add Locale","Add Locale" -"Add Date","Add Date" -"Days in Wish List","Days in Wish List" +Deleted Stores,Deleted Stores +Add Locale,Add Locale +Add Date,Add Date +Days in Wish List,Days in Wish List Unlock,Unlock No,No Yes,Yes -"Delete File","Delete File" +Delete File,Delete File Download,Download -"Delete Image","Delete Image" -"View Full Size","View Full Size" -"All countries","All countries" -"Customer Groups","Customer Groups" -"Add New Customer Group","Add New Customer Group" -"Save Customer Group","Save Customer Group" -"Delete Customer Group","Delete Customer Group" -"New Customer Group","New Customer Group" +Delete Image,Delete Image +View Full Size,View Full Size +All countries,All countries +Customer Groups,Customer Groups +Add New Customer Group,Add New Customer Group +Save Customer Group,Save Customer Group +Delete Customer Group,Delete Customer Group +New Customer Group,New Customer Group "Edit Customer Group ""%1""","Edit Customer Group ""%1""" -"Group Information","Group Information" -"Group Name","Group Name" -"Maximum length must be less then %1 characters.","Maximum length must be less then %1 characters." -"Tax Class","Tax Class" -"The customer is now assigned to Customer Group %s.","The customer is now assigned to Customer Group %s." -"Would you like to change the Customer Group for this order?","Would you like to change the Customer Group for this order?" -"The VAT ID is valid.","The VAT ID is valid." -"The VAT ID entered (%s) is not a valid VAT ID.","The VAT ID entered (%s) is not a valid VAT ID." -"The VAT ID is valid. The current Customer Group will be used.","The VAT ID is valid. The current Customer Group will be used." -"The VAT ID is valid but no Customer Group is assigned for it.","The VAT ID is valid but no Customer Group is assigned for it." +Group Information,Group Information +Group Name,Group Name +Maximum length must be less then %1 characters.,Maximum length must be less then %1 characters. +Tax Class,Tax Class +The customer is now assigned to Customer Group %s.,The customer is now assigned to Customer Group %s. +Would you like to change the Customer Group for this order?,Would you like to change the Customer Group for this order? +The VAT ID is valid.,The VAT ID is valid. +The VAT ID entered (%s) is not a valid VAT ID.,The VAT ID entered (%s) is not a valid VAT ID. +The VAT ID is valid. The current Customer Group will be used.,The VAT ID is valid. The current Customer Group will be used. +The VAT ID is valid but no Customer Group is assigned for it.,The VAT ID is valid but no Customer Group is assigned for it. "Based on the VAT ID, the customer belongs to the Customer Group %s.","Based on the VAT ID, the customer belongs to the Customer Group %s." -"Something went wrong while validating the VAT ID.","Something went wrong while validating the VAT ID." -"The customer would belong to Customer Group %s.","The customer would belong to Customer Group %s." -"There was an error detecting Customer Group.","There was an error detecting Customer Group." -"Validate VAT Number","Validate VAT Number" -"Customer Login","Customer Login" -"Create New Customer Account","Create New Customer Account" -"Date of Birth","Date of Birth" -"Bad request.","Bad request." -"This confirmation key is invalid or has expired.","This confirmation key is invalid or has expired." -"There was an error confirming the account","There was an error confirming the account" +Something went wrong while validating the VAT ID.,Something went wrong while validating the VAT ID. +The customer would belong to Customer Group %s.,The customer would belong to Customer Group %s. +There was an error detecting Customer Group.,There was an error detecting Customer Group. +Validate VAT Number,Validate VAT Number +Customer Login,Customer Login +Create New Customer Account,Create New Customer Account +Date of Birth,Date of Birth +Bad request.,Bad request. +This confirmation key is invalid or has expired.,This confirmation key is invalid or has expired. +There was an error confirming the account,There was an error confirming the account "If you are a registered VAT customer, please click here to enter your shipping address for proper VAT calculation.","If you are a registered VAT customer, please click here to enter your shipping address for proper VAT calculation." "If you are a registered VAT customer, please click here to enter your billing address for proper VAT calculation.","If you are a registered VAT customer, please click here to enter your billing address for proper VAT calculation." -"Thank you for registering with %1.","Thank you for registering with %1." -"Please check your email for confirmation key.","Please check your email for confirmation key." -"This email does not require confirmation.","This email does not require confirmation." -"Wrong email.","Wrong email." -"Your password reset link has expired.","Your password reset link has expired." +Thank you for registering with %1.,Thank you for registering with %1. +Please check your email for confirmation key.,Please check your email for confirmation key. +This email does not require confirmation.,This email does not require confirmation. +Wrong email.,Wrong email. +Your password reset link has expired.,Your password reset link has expired. "You must confirm your account. Please check your email for the confirmation link or click here for a new link.","You must confirm your account. Please check your email for the confirmation link or click here for a new link." "There is already an account with this email address. If you are sure that it is your email address, click here to get your password and access your account.","There is already an account with this email address. If you are sure that it is your email address, click here to get your password and access your account." -"We can't save the customer.","We can't save the customer." -"Please make sure your passwords match.","Please make sure your passwords match." +We can't save the customer.,We can't save the customer. +Please make sure your passwords match.,Please make sure your passwords match. "If you are a registered VAT customer, please click here to enter your shipping address for proper VAT calculation.","If you are a registered VAT customer, please click here to enter your shipping address for proper VAT calculation." "If you are a registered VAT customer, please click here to enter your billing address for proper VAT calculation.","If you are a registered VAT customer, please click here to enter your billing address for proper VAT calculation." -"Account Information","Account Information" -"You saved the account information.","You saved the account information." -"You did not sign in correctly or your account is temporarily disabled.","You did not sign in correctly or your account is temporarily disabled." -"Password confirmation doesn't match entered password.","Password confirmation doesn't match entered password." -"The password doesn't match this account.","The password doesn't match this account." -"Please correct the email address.","Please correct the email address." -"We're unable to send the password reset email.","We're unable to send the password reset email." -"Please enter your email.","Please enter your email." -"If there is an account associated with %1 you will receive an email with a link to reset your password.","If there is an account associated with %1 you will receive an email with a link to reset your password." -"My Account","My Account" +Account Information,Account Information +You saved the account information.,You saved the account information. +You did not sign in correctly or your account is temporarily disabled.,You did not sign in correctly or your account is temporarily disabled. +Password confirmation doesn't match entered password.,Password confirmation doesn't match entered password. +The password doesn't match this account.,The password doesn't match this account. +Please correct the email address.,Please correct the email address. +We're unable to send the password reset email.,We're unable to send the password reset email. +Please enter your email.,Please enter your email. +If there is an account associated with %1 you will receive an email with a link to reset your password.,If there is an account associated with %1 you will receive an email with a link to reset your password. +My Account,My Account "This account is not confirmed. Click here to resend confirmation email.","This account is not confirmed. Click here to resend confirmation email." -"An unspecified error occurred. Please contact us for assistance.","An unspecified error occurred. Please contact us for assistance." -"A login and a password are required.","A login and a password are required." -"New Password and Confirm New Password values didn't match.","New Password and Confirm New Password values didn't match." -"Please enter a new password.","Please enter a new password." -"You updated your password.","You updated your password." -"Something went wrong while saving the new password.","Something went wrong while saving the new password." -"You deleted the address.","You deleted the address." -"We can't delete the address right now.","We can't delete the address right now." -"You saved the address.","You saved the address." -"We can't save the address.","We can't save the address." -"No customer ID defined.","No customer ID defined." -"Please correct the quote items and try again.","Please correct the quote items and try again." -"You have revoked the customer's tokens.","You have revoked the customer's tokens." -"We can't find a customer to revoke.","We can't find a customer to revoke." -"Something went wrong while saving file.","Something went wrong while saving file." -"You deleted the customer group.","You deleted the customer group." -"The customer group no longer exists.","The customer group no longer exists." +An unspecified error occurred. Please contact us for assistance.,An unspecified error occurred. Please contact us for assistance. +A login and a password are required.,A login and a password are required. +New Password and Confirm New Password values didn't match.,New Password and Confirm New Password values didn't match. +Please enter a new password.,Please enter a new password. +You updated your password.,You updated your password. +Something went wrong while saving the new password.,Something went wrong while saving the new password. +You deleted the address.,You deleted the address. +We can't delete the address right now.,We can't delete the address right now. +You saved the address.,You saved the address. +We can't save the address.,We can't save the address. +No customer ID defined.,No customer ID defined. +Please correct the quote items and try again.,Please correct the quote items and try again. +You have revoked the customer's tokens.,You have revoked the customer's tokens. +We can't find a customer to revoke.,We can't find a customer to revoke. +Something went wrong while saving file.,Something went wrong while saving file. +You deleted the customer group.,You deleted the customer group. +The customer group no longer exists.,The customer group no longer exists. Customers,Customers -"New Group","New Group" -"New Customer Groups","New Customer Groups" -"Edit Group","Edit Group" -"Edit Customer Groups","Edit Customer Groups" -"You saved the customer group.","You saved the customer group." -"Please select customer(s).","Please select customer(s)." -"Customer could not be deleted.","Customer could not be deleted." -"You deleted the customer.","You deleted the customer." -"Something went wrong while editing the customer.","Something went wrong while editing the customer." -"Manage Customers","Manage Customers" -"Please correct the data sent.","Please correct the data sent." -"A total of %1 record(s) were updated.","A total of %1 record(s) were updated." -"A total of %1 record(s) were deleted.","A total of %1 record(s) were deleted." -"The customer will receive an email with a link to reset password.","The customer will receive an email with a link to reset password." -"Something went wrong while resetting customer password.","Something went wrong while resetting customer password." -"You saved the customer.","You saved the customer." -"Something went wrong while saving the customer.","Something went wrong while saving the customer." -"Page not found.","Page not found." -"Customer has been unlocked successfully.","Customer has been unlocked successfully." -"Online Customers","Online Customers" -"Customers Now Online","Customers Now Online" -"Please define Wish List item ID.","Please define Wish List item ID." -"Please load Wish List item.","Please load Wish List item." -"Login successful.","Login successful." -"Invalid login or password.","Invalid login or password." +New Group,New Group +New Customer Groups,New Customer Groups +Edit Group,Edit Group +Edit Customer Groups,Edit Customer Groups +You saved the customer group.,You saved the customer group. +Please select customer(s).,Please select customer(s). +Customer could not be deleted.,Customer could not be deleted. +You deleted the customer.,You deleted the customer. +Something went wrong while editing the customer.,Something went wrong while editing the customer. +Manage Customers,Manage Customers +Please correct the data sent.,Please correct the data sent. +A total of %1 record(s) were updated.,A total of %1 record(s) were updated. +A total of %1 record(s) were deleted.,A total of %1 record(s) were deleted. +The customer will receive an email with a link to reset password.,The customer will receive an email with a link to reset password. +Something went wrong while resetting customer password.,Something went wrong while resetting customer password. +You saved the customer.,You saved the customer. +Something went wrong while saving the customer.,Something went wrong while saving the customer. +Page not found.,Page not found. +Customer has been unlocked successfully.,Customer has been unlocked successfully. +Online Customers,Online Customers +Customers Now Online,Customers Now Online +Please define Wish List item ID.,Please define Wish List item ID. +Please load Wish List item.,Please load Wish List item. +Login successful.,Login successful. +Invalid login or password.,Invalid login or password. """%1"" section source is not supported","""%1"" section source is not supported" -"%1 doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface","%1 doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface" -"No confirmation needed.","No confirmation needed." -"Account already active","Account already active" -"Invalid confirmation token","Invalid confirmation token" -"The account is locked.","The account is locked." -"This account is not confirmed.","This account is not confirmed." +%1 doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface,%1 doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface +No confirmation needed.,No confirmation needed. +Account already active,Account already active +Invalid confirmation token,Invalid confirmation token +The account is locked.,The account is locked. +This account is not confirmed.,This account is not confirmed. "Invalid value of ""%value"" provided for the %fieldName field.","Invalid value of ""%value"" provided for the %fieldName field." -"Please enter a password with at most %1 characters.","Please enter a password with at most %1 characters." -"Please enter a password with at least %1 characters.","Please enter a password with at least %1 characters." -"The password can't begin or end with a space.","The password can't begin or end with a space." +Please enter a password with at most %1 characters.,Please enter a password with at most %1 characters. +Please enter a password with at least %1 characters.,Please enter a password with at least %1 characters. +The password can't begin or end with a space.,The password can't begin or end with a space. "Minimum of different classes of characters in password is %1. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.","Minimum of different classes of characters in password is %1. Classes of characters: Lower Case, Upper Case, Digits, Special Characters." -"Password cannot be the same as email address.","Password cannot be the same as email address." -"This customer already exists in this store.","This customer already exists in this store." -"A customer with the same email already exists in an associated website.","A customer with the same email already exists in an associated website." -"%fieldName is a required field.","%fieldName is a required field." -"Reset password token mismatch.","Reset password token mismatch." -"Reset password token expired.","Reset password token expired." -"Please correct the transactional account email type.","Please correct the transactional account email type." +Password cannot be the same as email address.,Password cannot be the same as email address. +This customer already exists in this store.,This customer already exists in this store. +A customer with the same email already exists in an associated website.,A customer with the same email already exists in an associated website. +%fieldName is a required field.,%fieldName is a required field. +Reset password token mismatch.,Reset password token mismatch. +Reset password token expired.,Reset password token expired. +Please correct the transactional account email type.,Please correct the transactional account email type. """%1"" is a required value.","""%1"" is a required value." Global,Global -"Per Website","Per Website" -"We can't share customer accounts globally when the accounts share identical email addresses on more than one website.","We can't share customer accounts globally when the accounts share identical email addresses on more than one website." -"Billing Address","Billing Address" -"Shipping Address","Shipping Address" -"-- Please Select --","-- Please Select --" -"Please enter a valid password reset token.","Please enter a valid password reset token." -"The password can not begin or end with a space.","The password can not begin or end with a space." +Per Website,Per Website +We can't share customer accounts globally when the accounts share identical email addresses on more than one website.,We can't share customer accounts globally when the accounts share identical email addresses on more than one website. +Billing Address,Billing Address +Shipping Address,Shipping Address +-- Please Select --,-- Please Select -- +Please enter a valid password reset token.,Please enter a valid password reset token. +The password can not begin or end with a space.,The password can not begin or end with a space. Admin,Admin -"ALL GROUPS","ALL GROUPS" +ALL GROUPS,ALL GROUPS "No such entity with %fieldName = %fieldValue, %field2Name = %field2Value","No such entity with %fieldName = %fieldValue, %field2Name = %field2Value" -"File can not be saved to the destination folder.","File can not be saved to the destination folder." -"Unable to create directory %1.","Unable to create directory %1." -"Destination folder is not writable or does not exists.","Destination folder is not writable or does not exists." -"Something went wrong while saving the file.","Something went wrong while saving the file." -"Attribute object is undefined","Attribute object is undefined" +File can not be saved to the destination folder.,File can not be saved to the destination folder. +Unable to create directory %1.,Unable to create directory %1. +Destination folder is not writable or does not exists.,Destination folder is not writable or does not exists. +Something went wrong while saving the file.,Something went wrong while saving the file. +Attribute object is undefined,Attribute object is undefined """%1"" invalid type entered.","""%1"" invalid type entered." """%1"" contains non-alphabetic or non-numeric characters.","""%1"" contains non-alphabetic or non-numeric characters." """%1"" is an empty string.","""%1"" is an empty string." @@ -217,19 +217,19 @@ Admin,Admin """%1"" is not a valid hostname.","""%1"" is not a valid hostname." """%1"" uses too many characters.","""%1"" uses too many characters." "'%value%' looks like an IP address, which is not an acceptable format.","'%value%' looks like an IP address, which is not an acceptable format." -"'%value%' looks like a DNS hostname but we cannot match the TLD against known list.","'%value%' looks like a DNS hostname but we cannot match the TLD against known list." -"'%value%' looks like a DNS hostname but contains a dash in an invalid position.","'%value%' looks like a DNS hostname but contains a dash in an invalid position." -"'%value%' looks like a DNS hostname but we cannot match it against the hostname schema for TLD '%tld%'.","'%value%' looks like a DNS hostname but we cannot match it against the hostname schema for TLD '%tld%'." -"'%value%' looks like a DNS hostname but cannot extract TLD part.","'%value%' looks like a DNS hostname but cannot extract TLD part." -"'%value%' does not look like a valid local network name.","'%value%' does not look like a valid local network name." +'%value%' looks like a DNS hostname but we cannot match the TLD against known list.,'%value%' looks like a DNS hostname but we cannot match the TLD against known list. +'%value%' looks like a DNS hostname but contains a dash in an invalid position.,'%value%' looks like a DNS hostname but contains a dash in an invalid position. +'%value%' looks like a DNS hostname but we cannot match it against the hostname schema for TLD '%tld%'.,'%value%' looks like a DNS hostname but we cannot match it against the hostname schema for TLD '%tld%'. +'%value%' looks like a DNS hostname but cannot extract TLD part.,'%value%' looks like a DNS hostname but cannot extract TLD part. +'%value%' does not look like a valid local network name.,'%value%' does not look like a valid local network name. "'%value%' looks like a local network name, which is not an acceptable format.","'%value%' looks like a local network name, which is not an acceptable format." "'%value%' appears to be a DNS hostname, but the given punycode notation cannot be decoded.","'%value%' appears to be a DNS hostname, but the given punycode notation cannot be decoded." """%1"" is not a valid URL.","""%1"" is not a valid URL." """%1"" is not a valid date.","""%1"" is not a valid date." """%1"" does not fit the entered date format.","""%1"" does not fit the entered date format." -"Please enter a valid date between %1 and %2 at %3.","Please enter a valid date between %1 and %2 at %3." -"Please enter a valid date equal to or greater than %1 at %2.","Please enter a valid date equal to or greater than %1 at %2." -"Please enter a valid date less than or equal to %1 at %2.","Please enter a valid date less than or equal to %1 at %2." +Please enter a valid date between %1 and %2 at %3.,Please enter a valid date between %1 and %2 at %3. +Please enter a valid date equal to or greater than %1 at %2.,Please enter a valid date equal to or greater than %1 at %2. +Please enter a valid date less than or equal to %1 at %2.,Please enter a valid date less than or equal to %1 at %2. """%1"" is not a valid file extension.","""%1"" is not a valid file extension." """%1"" is not a valid file.","""%1"" is not a valid file." """%1"" exceeds the allowed file size.","""%1"" exceeds the allowed file size." @@ -239,203 +239,203 @@ Admin,Admin """%1"" length must be equal or greater than %2 characters.","""%1"" length must be equal or greater than %2 characters." """%1"" length must be equal or less than %2 characters.","""%1"" length must be equal or less than %2 characters." label,label -"Please enter a customer email.","Please enter a customer email." -"A customer website ID must be specified when using the website scope.","A customer website ID must be specified when using the website scope." -"Customer Group","Customer Group" +Please enter a customer email.,Please enter a customer email. +A customer website ID must be specified when using the website scope.,A customer website ID must be specified when using the website scope. +Customer Group,Customer Group "You can't delete group ""%1"".","You can't delete group ""%1""." -"Customer Group already exists.","Customer Group already exists." -"Cannot delete group.","Cannot delete group." -"Error during VAT Number verification.","Error during VAT Number verification." -"PHP SOAP extension is required.","PHP SOAP extension is required." -"VAT Number is valid.","VAT Number is valid." -"Please enter a valid VAT number.","Please enter a valid VAT number." -"Your VAT ID was successfully validated.","Your VAT ID was successfully validated." -"You will be charged tax.","You will be charged tax." -"You will not be charged tax.","You will not be charged tax." -"The VAT ID entered (%1) is not a valid VAT ID.","The VAT ID entered (%1) is not a valid VAT ID." -"Your Tax ID cannot be validated.","Your Tax ID cannot be validated." +Customer Group already exists.,Customer Group already exists. +Cannot delete group.,Cannot delete group. +Error during VAT Number verification.,Error during VAT Number verification. +PHP SOAP extension is required.,PHP SOAP extension is required. +VAT Number is valid.,VAT Number is valid. +Please enter a valid VAT number.,Please enter a valid VAT number. +Your VAT ID was successfully validated.,Your VAT ID was successfully validated. +You will be charged tax.,You will be charged tax. +You will not be charged tax.,You will not be charged tax. +The VAT ID entered (%1) is not a valid VAT ID.,The VAT ID entered (%1) is not a valid VAT ID. +Your Tax ID cannot be validated.,Your Tax ID cannot be validated. "If you believe this is an error, please contact us at %1","If you believe this is an error, please contact us at %1" -"No such entity with %fieldName = %fieldValue","No such entity with %fieldName = %fieldValue" +No such entity with %fieldName = %fieldValue,No such entity with %fieldName = %fieldValue Male,Male Female,Female -"Not Specified","Not Specified" -"Thank you for registering with","Thank you for registering with" -"enter your billing address for proper VAT calculation","enter your billing address for proper VAT calculation" -"enter your shipping address for proper VAT calculation","enter your shipping address for proper VAT calculation" -"Please enter new password.","Please enter new password." +Not Specified,Not Specified +Thank you for registering with,Thank you for registering with +enter your billing address for proper VAT calculation,enter your billing address for proper VAT calculation +enter your shipping address for proper VAT calculation,enter your shipping address for proper VAT calculation +Please enter new password.,Please enter new password. message,message NoSuchEntityException,NoSuchEntityException Exception,Exception InputException.,InputException. InputException,InputException -"Exception message","Exception message" -"Validator Exception","Validator Exception" -"Localized Exception","Localized Exception" -"some error","some error" +Exception message,Exception message +Validator Exception,Validator Exception +Localized Exception,Localized Exception +some error,some error frontend_label,frontend_label -"NOT LOGGED IN","NOT LOGGED IN" -"Please enter the state/province.","Please enter the state/province." -"region is a required field.","region is a required field." -"regionId is a required field.","regionId is a required field." +NOT LOGGED IN,NOT LOGGED IN +Please enter the state/province.,Please enter the state/province. +region is a required field.,region is a required field. +regionId is a required field.,regionId is a required field. Label,Label Select...,Select... Edit,Edit Visitor,Visitor Customer,Customer -"No item specified.","No item specified." -"Are you sure you want to remove this item?","Are you sure you want to remove this item?" -"Personal Information","Personal Information" -"Last Logged In:","Last Logged In:" -"Last Logged In (%1):","Last Logged In (%1):" -"Account Lock:","Account Lock:" -"Confirmed email:","Confirmed email:" -"Account Created:","Account Created:" -"Account Created on (%1):","Account Created on (%1):" -"Account Created in:","Account Created in:" -"Customer Group:","Customer Group:" -"Default Billing Address","Default Billing Address" -"Sales Statistics","Sales Statistics" -"Web Site","Web Site" +No item specified.,No item specified. +Are you sure you want to remove this item?,Are you sure you want to remove this item? +Personal Information,Personal Information +Last Logged In:,Last Logged In: +Last Logged In (%1):,Last Logged In (%1): +Account Lock:,Account Lock: +Confirmed email:,Confirmed email: +Account Created:,Account Created: +Account Created on (%1):,Account Created on (%1): +Account Created in:,Account Created in: +Customer Group:,Customer Group: +Default Billing Address,Default Billing Address +Sales Statistics,Sales Statistics +Web Site,Web Site Store,Store -"Store View","Store View" -"Lifetime Sales","Lifetime Sales" -"Average Sale","Average Sale" -"All Store Views","All Store Views" +Store View,Store View +Lifetime Sales,Lifetime Sales +Average Sale,Average Sale +All Store Views,All Store Views Change,Change -"Manage Addresses","Manage Addresses" -"Default Shipping Address","Default Shipping Address" -"Contact Information","Contact Information" -"Change Password","Change Password" +Manage Addresses,Manage Addresses +Default Shipping Address,Default Shipping Address +Contact Information,Contact Information +Change Password,Change Password Newsletters,Newsletters "You are subscribed to ""General Subscription"".","You are subscribed to ""General Subscription""." or,or -"Default Addresses","Default Addresses" -"Change Billing Address","Change Billing Address" -"You have no default billing address in your address book.","You have no default billing address in your address book." -"Change Shipping Address","Change Shipping Address" -"You have no default shipping address in your address book.","You have no default shipping address in your address book." -"Additional Address Entries","Additional Address Entries" -"Delete Address","Delete Address" -"You have no other address entries in your address book.","You have no other address entries in your address book." -"* Required Fields","* Required Fields" +Default Addresses,Default Addresses +Change Billing Address,Change Billing Address +You have no default billing address in your address book.,You have no default billing address in your address book. +Change Shipping Address,Change Shipping Address +You have no default shipping address in your address book.,You have no default shipping address in your address book. +Additional Address Entries,Additional Address Entries +Delete Address,Delete Address +You have no other address entries in your address book.,You have no other address entries in your address book. +* Required Fields,* Required Fields Address,Address -"Street Address","Street Address" -"Street Address %1","Street Address %1" -"VAT Number","VAT Number" +Street Address,Street Address +Street Address %1,Street Address %1 +VAT Number,VAT Number City,City State/Province,State/Province "Please select a region, state or province.","Please select a region, state or province." -"Zip/Postal Code","Zip/Postal Code" +Zip/Postal Code,Zip/Postal Code Country,Country -"It's a default billing address.","It's a default billing address." -"Use as my default billing address","Use as my default billing address" -"It's a default shipping address.","It's a default shipping address." -"Use as my default shipping address","Use as my default shipping address" -"Save Address","Save Address" -"Go back","Go back" -"Please enter your email below and we will send you the confirmation link.","Please enter your email below and we will send you the confirmation link." +It's a default billing address.,It's a default billing address. +Use as my default billing address,Use as my default billing address +It's a default shipping address.,It's a default shipping address. +Use as my default shipping address,Use as my default shipping address +Save Address,Save Address +Go back,Go back +Please enter your email below and we will send you the confirmation link.,Please enter your email below and we will send you the confirmation link. Email,Email -"Send confirmation link","Send confirmation link" -"Back to Sign In","Back to Sign In" -"Change Email","Change Email" -"Change Email and Password","Change Email and Password" -"Current Password","Current Password" -"New Password","New Password" -"Password Strength","Password Strength" -"No Password","No Password" -"Confirm New Password","Confirm New Password" +Send confirmation link,Send confirmation link +Back to Sign In,Back to Sign In +Change Email,Change Email +Change Email and Password,Change Email and Password +Current Password,Current Password +New Password,New Password +Password Strength,Password Strength +No Password,No Password +Confirm New Password,Confirm New Password Save,Save -"Please enter your email address below to receive a password reset link.","Please enter your email address below to receive a password reset link." -"Reset My Password","Reset My Password" -"Registered Customers","Registered Customers" +Please enter your email address below to receive a password reset link.,Please enter your email address below to receive a password reset link. +Reset My Password,Reset My Password +Registered Customers,Registered Customers "If you have an account, sign in with your email address.","If you have an account, sign in with your email address." Password,Password -"Forgot Your Password?","Forgot Your Password?" -"Subscription option","Subscription option" -"General Subscription","General Subscription" -"Sign Up for Newsletter","Sign Up for Newsletter" -"Address Information","Address Information" -"Sign-in Information","Sign-in Information" -"Confirm Password","Confirm Password" -"Create an Account","Create an Account" -"Set a New Password","Set a New Password" -"You have signed out and will go to our homepage in 5 seconds.","You have signed out and will go to our homepage in 5 seconds." -"New Customers","New Customers" +Forgot Your Password?,Forgot Your Password? +Subscription option,Subscription option +General Subscription,General Subscription +Sign Up for Newsletter,Sign Up for Newsletter +Address Information,Address Information +Sign-in Information,Sign-in Information +Confirm Password,Confirm Password +Create an Account,Create an Account +Set a New Password,Set a New Password +You have signed out and will go to our homepage in 5 seconds.,You have signed out and will go to our homepage in 5 seconds. +New Customers,New Customers "Creating an account has many benefits: check out faster, keep more than one address, track orders and more.","Creating an account has many benefits: check out faster, keep more than one address, track orders and more." Company,Company Fax,Fax Gender,Gender Name,Name -"Tax/VAT number","Tax/VAT number" -"Phone Number","Phone Number" -"Welcome to %store_name","Welcome to %store_name" +Tax/VAT number,Tax/VAT number +Phone Number,Phone Number +Welcome to %store_name,Welcome to %store_name "%name,","%name," -"Welcome to %store_name.","Welcome to %store_name." +Welcome to %store_name.,Welcome to %store_name. "To sign in to our site, use these credentials during checkout or on the My Account page:","To sign in to our site, use these credentials during checkout or on the My Account page:" Email:,Email: Password:,Password: -"Password you set when creating account","Password you set when creating account" +Password you set when creating account,Password you set when creating account "Forgot your account password? Click here to reset it.","Forgot your account password? Click here to reset it." "When you sign in to your account, you will be able to:","When you sign in to your account, you will be able to:" -"Proceed through checkout faster","Proceed through checkout faster" -"Check the status of orders","Check the status of orders" -"View past orders","View past orders" -"Store alternative addresses (for shipping to multiple family members and friends)","Store alternative addresses (for shipping to multiple family members and friends)" -"Please confirm your %store_name account","Please confirm your %store_name account" -"You must confirm your %customer_email email before you can sign in (link is only valid once):","You must confirm your %customer_email email before you can sign in (link is only valid once):" -"Confirm Your Account","Confirm Your Account" -"Thank you for confirming your %store_name account.","Thank you for confirming your %store_name account." +Proceed through checkout faster,Proceed through checkout faster +Check the status of orders,Check the status of orders +View past orders,View past orders +Store alternative addresses (for shipping to multiple family members and friends),Store alternative addresses (for shipping to multiple family members and friends) +Please confirm your %store_name account,Please confirm your %store_name account +You must confirm your %customer_email email before you can sign in (link is only valid once):,You must confirm your %customer_email email before you can sign in (link is only valid once): +Confirm Your Account,Confirm Your Account +Thank you for confirming your %store_name account.,Thank you for confirming your %store_name account. "To sign in to our site and set a password, click on the link:","To sign in to our site and set a password, click on the link:" -"Your %store_name email has been changed","Your %store_name email has been changed" +Your %store_name email has been changed,Your %store_name email has been changed "Hello,","Hello," -"We have received a request to change the following information associated with your account at %store_name: email.","We have received a request to change the following information associated with your account at %store_name: email." +We have received a request to change the following information associated with your account at %store_name: email.,We have received a request to change the following information associated with your account at %store_name: email. "If you have not authorized this action, please contact us immediately at %store_email","If you have not authorized this action, please contact us immediately at %store_email" "or call us at %store_phone","or call us at %store_phone" "Thanks,
%store_name","Thanks,
%store_name" -"Your %store_name email and password has been changed","Your %store_name email and password has been changed" +Your %store_name email and password has been changed,Your %store_name email and password has been changed "We have received a request to change the following information associated with your account at %store_name: email, password.","We have received a request to change the following information associated with your account at %store_name: email, password." -"Reset your %store_name password","Reset your %store_name password" -"There was recently a request to change the password for your account.","There was recently a request to change the password for your account." +Reset your %store_name password,Reset your %store_name password +There was recently a request to change the password for your account.,There was recently a request to change the password for your account. "If you requested this change, set a new password here:","If you requested this change, set a new password here:" "If you did not make this request, you can ignore this email and your password will remain the same.","If you did not make this request, you can ignore this email and your password will remain the same." -"Your %store_name password has been changed","Your %store_name password has been changed" -"We have received a request to change the following information associated with your account at %store_name: password.","We have received a request to change the following information associated with your account at %store_name: password." -"Checkout as a new customer","Checkout as a new customer" -"Creating an account has many benefits:","Creating an account has many benefits:" -"See order and shipping status","See order and shipping status" -"Track order history","Track order history" -"Check out faster","Check out faster" -"Checkout using your account","Checkout using your account" -"Email Address","Email Address" -"Are you sure you want to do this?","Are you sure you want to do this?" -"Are you sure you want to delete this address?","Are you sure you want to delete this address?" +Your %store_name password has been changed,Your %store_name password has been changed +We have received a request to change the following information associated with your account at %store_name: password.,We have received a request to change the following information associated with your account at %store_name: password. +Checkout as a new customer,Checkout as a new customer +Creating an account has many benefits:,Creating an account has many benefits: +See order and shipping status,See order and shipping status +Track order history,Track order history +Check out faster,Check out faster +Checkout using your account,Checkout using your account +Email Address,Email Address +Are you sure you want to do this?,Are you sure you want to do this? +Are you sure you want to delete this address?,Are you sure you want to delete this address? Weak,Weak Medium,Medium Strong,Strong -"Very Strong","Very Strong" -"Guest checkout is disabled.","Guest checkout is disabled." -"All Customers","All Customers" -"Now Online","Now Online" -"Customers Section","Customers Section" -"Customer Configuration","Customer Configuration" -"Account Sharing Options","Account Sharing Options" -"Share Customer Accounts","Share Customer Accounts" -"Create New Account Options","Create New Account Options" -"Enable Automatic Assignment to Customer Group","Enable Automatic Assignment to Customer Group" -"Tax Calculation Based On","Tax Calculation Based On" -"Default Group","Default Group" -"Group for Valid VAT ID - Domestic","Group for Valid VAT ID - Domestic" -"Group for Valid VAT ID - Intra-Union","Group for Valid VAT ID - Intra-Union" -"Group for Invalid VAT ID","Group for Invalid VAT ID" -"Validation Error Group","Validation Error Group" -"Validate on Each Transaction","Validate on Each Transaction" -"Default Value for Disable Automatic Group Changes Based on VAT ID","Default Value for Disable Automatic Group Changes Based on VAT ID" -"Show VAT Number on Storefront","Show VAT Number on Storefront" +Very Strong,Very Strong +Guest checkout is disabled.,Guest checkout is disabled. +All Customers,All Customers +Now Online,Now Online +Customers Section,Customers Section +Customer Configuration,Customer Configuration +Account Sharing Options,Account Sharing Options +Share Customer Accounts,Share Customer Accounts +Create New Account Options,Create New Account Options +Enable Automatic Assignment to Customer Group,Enable Automatic Assignment to Customer Group +Tax Calculation Based On,Tax Calculation Based On +Default Group,Default Group +Group for Valid VAT ID - Domestic,Group for Valid VAT ID - Domestic +Group for Valid VAT ID - Intra-Union,Group for Valid VAT ID - Intra-Union +Group for Invalid VAT ID,Group for Invalid VAT ID +Validation Error Group,Validation Error Group +Validate on Each Transaction,Validate on Each Transaction +Default Value for Disable Automatic Group Changes Based on VAT ID,Default Value for Disable Automatic Group Changes Based on VAT ID +Show VAT Number on Storefront,Show VAT Number on Storefront "To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes.","To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes." -"Default Email Domain","Default Email Domain" -"Default Welcome Email","Default Welcome Email" +Default Email Domain,Default Email Domain +Default Welcome Email,Default Welcome Email "Email template chosen based on theme fallback when ""Default"" option is selected.","Email template chosen based on theme fallback when ""Default"" option is selected." -"Default Welcome Email Without Password","Default Welcome Email Without Password" +Default Welcome Email Without Password,Default Welcome Email Without Password " This email will be sent instead of the Default Welcome Email, if a customer was created without password.

Email template chosen based on theme fallback when ""Default"" option is selected. @@ -443,10 +443,10 @@ Strong,Strong This email will be sent instead of the Default Welcome Email, if a customer was created without password.

Email template chosen based on theme fallback when ""Default"" option is selected. " -"Email Sender","Email Sender" -"Require Emails Confirmation","Require Emails Confirmation" -"Confirmation Link Email","Confirmation Link Email" -"Welcome Email","Welcome Email" +Email Sender,Email Sender +Require Emails Confirmation,Require Emails Confirmation +Confirmation Link Email,Confirmation Link Email +Welcome Email,Welcome Email " This email will be sent instead of the Default Welcome Email, after account confirmation.

Email template chosen based on theme fallback when ""Default"" option is selected. @@ -454,88 +454,90 @@ Strong,Strong This email will be sent instead of the Default Welcome Email, after account confirmation.

Email template chosen based on theme fallback when ""Default"" option is selected. " -"Generate Human-Friendly Customer ID","Generate Human-Friendly Customer ID" -"Password Options","Password Options" -"Forgot Email Template","Forgot Email Template" -"Remind Email Template","Remind Email Template" -"Reset Password Template","Reset Password Template" -"Password Template Email Sender","Password Template Email Sender" -"Recovery Link Expiration Period (hours)","Recovery Link Expiration Period (hours)" -"Please enter a number 1 or greater in this field.","Please enter a number 1 or greater in this field." -"Number of Required Character Classes","Number of Required Character Classes" +Generate Human-Friendly Customer ID,Generate Human-Friendly Customer ID +Password Options,Password Options +Forgot Email Template,Forgot Email Template +Remind Email Template,Remind Email Template +Reset Password Template,Reset Password Template +Password Template Email Sender,Password Template Email Sender +Recovery Link Expiration Period (hours),Recovery Link Expiration Period (hours) +Please enter a number 1 or greater in this field.,Please enter a number 1 or greater in this field. +Number of Required Character Classes,Number of Required Character Classes "Number of different character classes required in password: Lowercase, Uppercase, Digits, Special Characters.","Number of different character classes required in password: Lowercase, Uppercase, Digits, Special Characters." -"Minimum Password Length","Minimum Password Length" -"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" -"Use 0 to disable account locking.","Use 0 to disable account locking." -"Lockout Time (minutes)","Lockout Time (minutes)" -"Enable Autocomplete on login/forgot password forms","Enable Autocomplete on login/forgot password forms" -"Account Information Options","Account Information Options" -"Change Email Template","Change Email Template" -"Change Email and Password Template","Change Email and Password Template" -"Name and Address Options","Name and Address Options" -"Number of Lines in a Street Address","Number of Lines in a Street Address" -"Leave empty for default (2). Valid range: 1-4","Leave empty for default (2). Valid range: 1-4" -"Show Prefix","Show Prefix" +Minimum Password Length,Minimum Password Length +Maximum Login Failures to Lockout Account,Maximum Login Failures to Lockout Account +Use 0 to disable account locking.,Use 0 to disable account locking. +Lockout Time (minutes),Lockout Time (minutes) +Enable Autocomplete on login/forgot password forms,Enable Autocomplete on login/forgot password forms +Account Information Options,Account Information Options +Change Email Template,Change Email Template +Change Email and Password Template,Change Email and Password Template +Name and Address Options,Name and Address Options +Number of Lines in a Street Address,Number of Lines in a Street Address +Leave empty for default (2). Valid range: 1-4,Leave empty for default (2). Valid range: 1-4 +Show Prefix,Show Prefix "The title that goes before name (Mr., Mrs., etc.)","The title that goes before name (Mr., Mrs., etc.)" -"Prefix Dropdown Options","Prefix Dropdown Options" +Prefix Dropdown Options,Prefix Dropdown Options " Semicolon (;) separated values.
Leave empty for open text field. "," Semicolon (;) separated values.
Leave empty for open text field. " -"Show Middle Name (initial)","Show Middle Name (initial)" -"Always optional.","Always optional." -"Show Suffix","Show Suffix" +Show Middle Name (initial),Show Middle Name (initial) +Always optional.,Always optional. +Show Suffix,Show Suffix "The suffix that goes after name (Jr., Sr., etc.)","The suffix that goes after name (Jr., Sr., etc.)" -"Suffix Dropdown Options","Suffix Dropdown Options" -"Show Date of Birth","Show Date of Birth" -"Show Tax/VAT Number","Show Tax/VAT Number" -"Show Gender","Show Gender" -"Show Telephone","Show Telephone" -"Show Company","Show Company" -"Show Fax","Show Fax" -"Login Options","Login Options" -"Redirect Customer to Account Dashboard after Logging in","Redirect Customer to Account Dashboard after Logging in" +Suffix Dropdown Options,Suffix Dropdown Options +Show Date of Birth,Show Date of Birth +Show Tax/VAT Number,Show Tax/VAT Number +Show Gender,Show Gender +Show Telephone,Show Telephone +Show Company,Show Company +Show Fax,Show Fax +Login Options,Login Options +Redirect Customer to Account Dashboard after Logging in,Redirect Customer to Account Dashboard after Logging in "Customer will stay on the current page if ""No"" is selected.","Customer will stay on the current page if ""No"" is selected." -"Address Templates","Address Templates" -"Online Customers Options","Online Customers Options" -"Online Minutes Interval","Online Minutes Interval" +Address Templates,Address Templates +Online Customers Options,Online Customers Options +Online Minutes Interval,Online Minutes Interval "Only 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub', 'sup', 'ul' tags are allowed","Only 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub', 'sup', 'ul' tags are allowed" -"Leave empty for default (15 minutes).","Leave empty for default (15 minutes)." -"Customer Notification","Customer Notification" -"Customer Grid","Customer Grid" -"Rebuild Customer grid index","Rebuild Customer grid index" +Leave empty for default (15 minutes).,Leave empty for default (15 minutes). +Customer Notification,Customer Notification +Customer Grid,Customer Grid +Rebuild Customer grid index,Rebuild Customer grid index Group,Group -"Add New Customer","Add New Customer" -"Are you sure you want to delete the selected customers?","Are you sure you want to delete the selected customers?" -"Delete items","Delete items" -"Subscribe to Newsletter","Subscribe to Newsletter" -"Are you sure you want to unsubscribe the selected customers from the newsletter?","Are you sure you want to unsubscribe the selected customers from the newsletter?" -"Unsubscribe from Newsletter","Unsubscribe from Newsletter" -"Assign a Customer Group","Assign a Customer Group" +Add New Customer,Add New Customer +Are you sure you want to delete the selected customers?,Are you sure you want to delete the selected customers? +Delete items,Delete items +Subscribe to Newsletter,Subscribe to Newsletter +Are you sure you want to unsubscribe the selected customers from the newsletter?,Are you sure you want to unsubscribe the selected customers from the newsletter? +Unsubscribe from Newsletter,Unsubscribe from Newsletter +Assign a Customer Group,Assign a Customer Group Phone,Phone ZIP,ZIP -"Customer Since","Customer Since" -"Confirmed email","Confirmed email" -"Account Created in","Account Created in" -"Tax VAT Number","Tax VAT Number" -"Billing Firstname","Billing Firstname" -"Billing Lastname","Billing Lastname" -"Account Lock","Account Lock" -"First Name","First Name" -"Last Name","Last Name" -"Last Activity","Last Activity" +Customer Since,Customer Since +Confirmed email,Confirmed email +Account Created in,Account Created in +Tax VAT Number,Tax VAT Number +Billing Firstname,Billing Firstname +Billing Lastname,Billing Lastname +Account Lock,Account Lock +First Name,First Name +Last Name,Last Name +Last Activity,Last Activity Type,Type -"Customer Information","Customer Information" +Customer Information,Customer Information "If your Magento installation has multiple websites, you can edit the scope to associate the customer with a specific site.","If your Magento installation has multiple websites, you can edit the scope to associate the customer with a specific site." -"Disable Automatic Group Change Based on VAT ID","Disable Automatic Group Change Based on VAT ID" -"Send Welcome Email From","Send Welcome Email From" -"Are you sure you want to delete this item?","Are you sure you want to delete this item?" +Disable Automatic Group Change Based on VAT ID,Disable Automatic Group Change Based on VAT ID +Send Welcome Email From,Send Welcome Email From +Are you sure you want to delete this item?,Are you sure you want to delete this item? Addresses,Addresses -"Edit Account Information","Edit Account Information" -"Password forgotten","Password forgotten" -"You are signed out","You are signed out" -"Associate to Website","Associate to Website" -"Prefix","Prefix" -"Middle Name/Initial","Middle Name/Initial" -"Suffix","Suffix" +Edit Account Information,Edit Account Information +Password forgotten,Password forgotten +You are signed out,You are signed out +Associate to Website,Associate to Website +Prefix,Prefix +Middle Name/Initial,Middle Name/Initial +Suffix,Suffix +Email is required field for Admin order creation,Email is required field for Admin order creation +If set YES Email field will be required during Admin order creation for new Customer.,If set YES Email field will be required during Admin order creation for new Customer. diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php old mode 100644 new mode 100755 index 28b1cc9c80a91..fc48e351b0e7e --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -149,7 +149,7 @@ protected function _addAdditionalFormElementData(AbstractElement $element) { switch ($element->getId()) { case 'email': - $element->setRequired($this->isEmailRequiredCreateOrder()); + $element->setRequired($this->isEmailRequiredToCreateOrder()); $element->setClass('validate-email admin__control-text'); break; } @@ -212,7 +212,7 @@ private function extractValuesFromAttributes(array $attributes): array * * @return bool */ - private function isEmailRequiredCreateOrder() + private function isEmailRequiredToCreateOrder() { return $this->_scopeConfig->getValue( self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php old mode 100644 new mode 100755 index 211c6f75dad18..00a971bdf2b84 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -2043,11 +2043,11 @@ protected function _getNewCustomerEmail() \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); - if($emailrequired) { + if ($emailrequired) { return $this->getData('account/email'); } else { $email = $this->getData('account/email'); - if (empty($email)) { + if (empty($email)) { $host = $this->_scopeConfig->getValue( self::XML_PATH_DEFAULT_EMAIL_DOMAIN, \Magento\Store\Model\ScopeInterface::SCOPE_STORE @@ -2057,11 +2057,10 @@ protected function _getNewCustomerEmail() $account = $this->getData('account'); $account['email'] = $email; $this->setData('account', $account); - } + } return $email; } - } /** From 7e5bb80c72c62eebd4599c8bce8c68d205ae21bd Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Sat, 14 Sep 2019 14:19:47 +0530 Subject: [PATCH 017/369] _getNewCustomerEmail seperate it in differnet method --- .../Magento/Sales/Model/AdminOrder/Create.php | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) mode change 100755 => 100644 app/code/Magento/Sales/Model/AdminOrder/Create.php diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php old mode 100755 new mode 100644 index 00a971bdf2b84..1446f4ad1153d --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -2038,31 +2038,51 @@ protected function _validate() */ protected function _getNewCustomerEmail() { - $emailrequired = $this->_scopeConfig->getValue( - self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); + $emailrequired = $this->getIsEmailRequired(); if ($emailrequired) { return $this->getData('account/email'); } else { $email = $this->getData('account/email'); if (empty($email)) { - $host = $this->_scopeConfig->getValue( - self::XML_PATH_DEFAULT_EMAIL_DOMAIN, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); - $account = time(); - $email = $account . '@' . $host; - $account = $this->getData('account'); - $account['email'] = $email; - $this->setData('account', $account); + $email = $this-> generateEmail(); } - return $email; } } + /** + * Check email is require + * + * @return bool + */ + protected function getIsEmailRequired() + { + return $this->_scopeConfig->getValue( + self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + } + + /** + * Generate Email + * + * @return string + */ + protected function generateEmail() + { + $host = $this->_scopeConfig->getValue( + self::XML_PATH_DEFAULT_EMAIL_DOMAIN, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + $account = time(); + $email = $account . '@' . $host; + $account = $this->getData('account'); + $account['email'] = $email; + $this->setData('account', $account); + return $email; + } + /** * Checks id shipping and billing addresses are equal. * From b5265e8446441ebcb38981b69e3494653c106818 Mon Sep 17 00:00:00 2001 From: Oleksandr Kravchuk Date: Mon, 16 Sep 2019 10:36:46 +0300 Subject: [PATCH 018/369] Update Create.php --- .../Magento/Sales/Model/AdminOrder/Create.php | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 1446f4ad1153d..d51fa0778b192 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -2038,17 +2038,13 @@ protected function _validate() */ protected function _getNewCustomerEmail() { - $emailrequired = $this->getIsEmailRequired(); - - if ($emailrequired) { - return $this->getData('account/email'); - } else { - $email = $this->getData('account/email'); - if (empty($email)) { - $email = $this-> generateEmail(); - } - return $email; + $email = $this->getData('account/email'); + + if ($email || $this->getIsEmailRequired()) { + return $email; } + + return $this->generateEmail(); } /** @@ -2056,9 +2052,9 @@ protected function _getNewCustomerEmail() * * @return bool */ - protected function getIsEmailRequired() + private function getIsEmailRequired(): bool { - return $this->_scopeConfig->getValue( + return (bool)$this->_scopeConfig->getValue( self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); @@ -2069,7 +2065,7 @@ protected function getIsEmailRequired() * * @return string */ - protected function generateEmail() + private function generateEmail(): string { $host = $this->_scopeConfig->getValue( self::XML_PATH_DEFAULT_EMAIL_DOMAIN, @@ -2080,6 +2076,7 @@ protected function generateEmail() $account = $this->getData('account'); $account['email'] = $email; $this->setData('account', $account); + return $email; } From 3c1d73e26e4ff18da5bbc3b2eb3780fcea244dbf Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Tue, 17 Sep 2019 17:26:44 +0530 Subject: [PATCH 019/369] system.xml is placed in sales module --- .../Magento/Customer/etc/adminhtml/system.xml | 5 - app/code/Magento/Customer/etc/config.xml | 1 - app/code/Magento/Customer/i18n/en_US.csv | 2 - .../Magento/Sales/etc/adminhtml/system.xml | 10 + app/code/Magento/Sales/etc/config.xml | 5 + app/code/Magento/Sales/i18n/en_US.csv | 1600 +++++++++-------- 6 files changed, 816 insertions(+), 807 deletions(-) mode change 100755 => 100644 app/code/Magento/Customer/i18n/en_US.csv diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml index 325de3680b463..2bd1041214801 100644 --- a/app/code/Magento/Customer/etc/adminhtml/system.xml +++ b/app/code/Magento/Customer/etc/adminhtml/system.xml @@ -86,11 +86,6 @@ To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes. Magento\Config\Model\Config\Source\Yesno - - - If set YES Email field will be required during Admin order creation for new Customer. - Magento\Config\Model\Config\Source\Yesno - diff --git a/app/code/Magento/Customer/etc/config.xml b/app/code/Magento/Customer/etc/config.xml index d979cf19fc5f7..da4b80536e631 100644 --- a/app/code/Magento/Customer/etc/config.xml +++ b/app/code/Magento/Customer/etc/config.xml @@ -15,7 +15,6 @@ 0 1 billing - 0 example.com general customer_create_account_email_template diff --git a/app/code/Magento/Customer/i18n/en_US.csv b/app/code/Magento/Customer/i18n/en_US.csv old mode 100755 new mode 100644 index 74c30a47a835e..4ab5e2b7068fb --- a/app/code/Magento/Customer/i18n/en_US.csv +++ b/app/code/Magento/Customer/i18n/en_US.csv @@ -539,5 +539,3 @@ Associate to Website,Associate to Website Prefix,Prefix Middle Name/Initial,Middle Name/Initial Suffix,Suffix -Email is required field for Admin order creation,Email is required field for Admin order creation -If set YES Email field will be required during Admin order creation for new Customer.,If set YES Email field will be required during Admin order creation for new Customer. diff --git a/app/code/Magento/Sales/etc/adminhtml/system.xml b/app/code/Magento/Sales/etc/adminhtml/system.xml index d1e5680b883c2..5b243786d8fd9 100644 --- a/app/code/Magento/Sales/etc/adminhtml/system.xml +++ b/app/code/Magento/Sales/etc/adminhtml/system.xml @@ -446,5 +446,15 @@ +
+ + + + + If set YES Email field will be required during Admin order creation for new Customer. + Magento\Config\Model\Config\Source\Yesno + + +
diff --git a/app/code/Magento/Sales/etc/config.xml b/app/code/Magento/Sales/etc/config.xml index 2480da4ad214b..cb921b8b0a1bc 100644 --- a/app/code/Magento/Sales/etc/config.xml +++ b/app/code/Magento/Sales/etc/config.xml @@ -111,5 +111,10 @@ 0 + + + 0 + + diff --git a/app/code/Magento/Sales/i18n/en_US.csv b/app/code/Magento/Sales/i18n/en_US.csv index c5657f3a309f7..fc1fac1311985 100644 --- a/app/code/Magento/Sales/i18n/en_US.csv +++ b/app/code/Magento/Sales/i18n/en_US.csv @@ -1,799 +1,801 @@ -"Credit Memos","Credit Memos" -Orders,Orders -Invoices,Invoices -"We can't get the order instance right now.","We can't get the order instance right now." -"Create New Order","Create New Order" -"Save Order Address","Save Order Address" -Shipping,Shipping -Billing,Billing -"Edit Order %1 %2 Address","Edit Order %1 %2 Address" -"Order Address Information","Order Address Information" -"Please correct the parent block for this block.","Please correct the parent block for this block." -"Submit Comment","Submit Comment" -"Submit Order","Submit Order" -"Are you sure you want to cancel this order?","Are you sure you want to cancel this order?" -Cancel,Cancel -"Billing Address","Billing Address" -"Payment Method","Payment Method" -"Order Comment","Order Comment" -Coupons,Coupons -"Please select a customer","Please select a customer" -"Create New Customer","Create New Customer" -"Account Information","Account Information" -From,From -To,To -Message,Message -"Edit Order #%1","Edit Order #%1" -"Create New Order for %1 in %2","Create New Order for %1 in %2" -"Create New Order in %1","Create New Order in %1" -"Create New Order for %1","Create New Order for %1" -"Create New Order for New Customer","Create New Order for New Customer" -"Items Ordered","Items Ordered" -"This product is disabled.","This product is disabled." -"Buy %1 for price %2","Buy %1 for price %2" -"Item ordered qty","Item ordered qty" -"%1 with %2 discount each","%1 with %2 discount each" -"%1 for %2","%1 for %2" -"* - Enter custom price including tax","* - Enter custom price including tax" -"* - Enter custom price excluding tax","* - Enter custom price excluding tax" -Configure,Configure -"This product does not have any configurable options","This product does not have any configurable options" -"Newsletter Subscription","Newsletter Subscription" -"Please select products","Please select products" -"Add Selected Product(s) to Order","Add Selected Product(s) to Order" -ID,ID -Product,Product -SKU,SKU -Price,Price -Select,Select -Quantity,Quantity -"Shipping Address","Shipping Address" -"Shipping Method","Shipping Method" -"Update Changes","Update Changes" -"Shopping Cart","Shopping Cart" -"Are you sure you want to delete all items from shopping cart?","Are you sure you want to delete all items from shopping cart?" -"Clear Shopping Cart","Clear Shopping Cart" -"Products in Comparison List","Products in Comparison List" -"Recently Compared Products","Recently Compared Products" -"Recently Viewed Products","Recently Viewed Products" -"Last Ordered Items","Last Ordered Items" -"Recently Viewed","Recently Viewed" -"Wish List","Wish List" -"Please select a store","Please select a store" -"Order Totals","Order Totals" -"Shipping Incl. Tax (%1)","Shipping Incl. Tax (%1)" -"Shipping Excl. Tax (%1)","Shipping Excl. Tax (%1)" -"New Credit Memo for Invoice #%1","New Credit Memo for Invoice #%1" -"New Credit Memo for Order #%1","New Credit Memo for Order #%1" -"Refund Shipping (Incl. Tax)","Refund Shipping (Incl. Tax)" -"Refund Shipping (Excl. Tax)","Refund Shipping (Excl. Tax)" -"Refund Shipping","Refund Shipping" -"Update Qty's","Update Qty's" -Refund,Refund -"Refund Offline","Refund Offline" -"Paid Amount","Paid Amount" -"Refund Amount","Refund Amount" -"Shipping Amount","Shipping Amount" -"Shipping Refund","Shipping Refund" -"Order Grand Total","Order Grand Total" -"Adjustment Refund","Adjustment Refund" -"Adjustment Fee","Adjustment Fee" -"Send Email","Send Email" -"Are you sure you want to send a credit memo email to customer?","Are you sure you want to send a credit memo email to customer?" -Void,Void -Print,Print -"The credit memo email was sent.","The credit memo email was sent." -"The credit memo email wasn't sent.","The credit memo email wasn't sent." -"Credit Memo #%1 | %3 | %2 (%4)","Credit Memo #%1 | %3 | %2 (%4)" -"Total Refund","Total Refund" -"New Invoice and Shipment for Order #%1","New Invoice and Shipment for Order #%1" -"New Invoice for Order #%1","New Invoice for Order #%1" -"Submit Invoice and Shipment","Submit Invoice and Shipment" -"Submit Invoice","Submit Invoice" -"Are you sure you want to send an invoice email to customer?","Are you sure you want to send an invoice email to customer?" -"Credit Memo","Credit Memo" -Capture,Capture -"The invoice email was sent.","The invoice email was sent." -"The invoice email wasn't sent.","The invoice email wasn't sent." -"Invoice #%1 | %2 | %4 (%3)","Invoice #%1 | %2 | %4 (%3)" -"Invalid parent block for this block","Invalid parent block for this block" -"Order Statuses","Order Statuses" -"Create New Status","Create New Status" -"Assign Status to State","Assign Status to State" -"Save Status Assignment","Save Status Assignment" -"Assign Order Status to State","Assign Order Status to State" -"Assignment Information","Assignment Information" -"Order Status","Order Status" -"Order State","Order State" -"Use Order Status As Default","Use Order Status As Default" -"Visible On Storefront","Visible On Storefront" -"Edit Order Status","Edit Order Status" -"Save Status","Save Status" -"New Order Status","New Order Status" -"Order Status Information","Order Status Information" -"Status Code","Status Code" -"Status Label","Status Label" -"Store View Specific Labels","Store View Specific Labels" -"Total Paid","Total Paid" -"Total Refunded","Total Refunded" -"Total Due","Total Due" -Edit,Edit -"Are you sure you want to send an order email to customer?","Are you sure you want to send an order email to customer?" -"This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you want to continue?","This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you want to continue?" -"Are you sure you want to void the payment?","Are you sure you want to void the payment?" -Hold,Hold -hold,hold -Unhold,Unhold -unhold,unhold -"Are you sure you want to accept this payment?","Are you sure you want to accept this payment?" -"Accept Payment","Accept Payment" -"Are you sure you want to deny this payment?","Are you sure you want to deny this payment?" -"Deny Payment","Deny Payment" -"Get Payment Update","Get Payment Update" -"Invoice and Ship","Invoice and Ship" -Invoice,Invoice -Ship,Ship -Reorder,Reorder -"Order # %1 %2 | %3","Order # %1 %2 | %3" -"This order contains (%1) items and therefore cannot be edited through the admin interface. If you wish to continue editing, the (%2) items will be removed, the order will be canceled and a new order will be placed.","This order contains (%1) items and therefore cannot be edited through the admin interface. If you wish to continue editing, the (%2) items will be removed, the order will be canceled and a new order will be placed." -"Are you sure? This order will be canceled and a new one will be created instead.","Are you sure? This order will be canceled and a new one will be created instead." -"Save Gift Message","Save Gift Message" -" [deleted]"," [deleted]" -"Order Credit Memos","Order Credit Memos" -"Credit memo #%1 created","Credit memo #%1 created" -"Credit memo #%1 comment added","Credit memo #%1 comment added" -"Shipment #%1 created","Shipment #%1 created" -"Shipment #%1 comment added","Shipment #%1 comment added" -"Invoice #%1 created","Invoice #%1 created" -"Invoice #%1 comment added","Invoice #%1 comment added" -"Tracking number %1 for %2 assigned","Tracking number %1 for %2 assigned" -"Comments History","Comments History" -"Order History","Order History" -Information,Information -"Order Information","Order Information" -"Order Invoices","Order Invoices" -Shipments,Shipments -"Order Shipments","Order Shipments" -Transactions,Transactions -"Order View","Order View" -Any,Any -Specified,Specified -"Applies to Any of the Specified Order Statuses except canceled orders","Applies to Any of the Specified Order Statuses except canceled orders" -"Cart Price Rule","Cart Price Rule" -Yes,Yes -No,No -"Show Actual Values","Show Actual Values" -"New Order RSS","New Order RSS" -Subtotal,Subtotal -"Shipping & Handling","Shipping & Handling" -"Discount (%1)","Discount (%1)" -Discount,Discount -"Grand Total","Grand Total" -Back,Back -Fetch,Fetch -"Transaction # %1 | %2","Transaction # %1 | %2" -N/A,N/A -Key,Key -Value,Value -"We found an invalid entity model.","We found an invalid entity model." -"Order # %1","Order # %1" -"Back to My Orders","Back to My Orders" -"View Another Order","View Another Order" -"About Your Refund","About Your Refund" -"My Orders","My Orders" -"Subscribe to Order Status","Subscribe to Order Status" -"About Your Invoice","About Your Invoice" -"Print Order # %1","Print Order # %1" -"Grand Total to be Charged","Grand Total to be Charged" -Unassign,Unassign -"We can't add this item to your shopping cart right now.","We can't add this item to your shopping cart right now." -"You sent the message.","You sent the message." -Sales,Sales -"Invoice capturing error","Invoice capturing error" -"This order no longer exists.","This order no longer exists." -"Please enter a comment.","Please enter a comment." -"We cannot add order history.","We cannot add order history." -"You updated the order address.","You updated the order address." -"We can't update the order address right now.","We can't update the order address right now." -"You have not canceled the item.","You have not canceled the item." -"You canceled the order.","You canceled the order." -"""%1"" coupon code was not applied. Do not apply discount is selected for item(s)","""%1"" coupon code was not applied. Do not apply discount is selected for item(s)" -"""%1"" coupon code is not valid.","""%1"" coupon code is not valid." -"The coupon code has been accepted.","The coupon code has been accepted." -"Quote item id is not received.","Quote item id is not received." -"Quote item is not loaded.","Quote item is not loaded." -"New Order","New Order" -"You created the order.","You created the order." -"Order saving error: %1","Order saving error: %1" -"Cannot add new comment.","Cannot add new comment." -"The credit memo has been canceled.","The credit memo has been canceled." -"Credit memo has not been canceled.","Credit memo has not been canceled." -"New Memo for #%1","New Memo for #%1" -"New Memo","New Memo" -"The credit memo's total must be positive.","The credit memo's total must be positive." -"Cannot create online refund for Refund to Store Credit.","Cannot create online refund for Refund to Store Credit." -"You created the credit memo.","You created the credit memo." -"We can't save the credit memo right now.","We can't save the credit memo right now." -"We can't update the item's quantity right now.","We can't update the item's quantity right now." -"View Memo for #%1","View Memo for #%1" -"View Memo","View Memo" -"You voided the credit memo.","You voided the credit memo." -"We can't void the credit memo.","We can't void the credit memo." -"The order no longer exists.","The order no longer exists." -"We can't create credit memo for the order.","We can't create credit memo for the order." -"Edit Order","Edit Order" -"You sent the order email.","You sent the order email." -"We can't send the email order right now.","We can't send the email order right now." -"You have not put the order on hold.","You have not put the order on hold." -"You put the order on hold.","You put the order on hold." -"You canceled the invoice.","You canceled the invoice." -"Invoice canceling error","Invoice canceling error" -"The invoice has been captured.","The invoice has been captured." -"The order does not allow an invoice to be created.","The order does not allow an invoice to be created." -"You can't create an invoice without products.","You can't create an invoice without products." -"New Invoice","New Invoice" -"We can't save the invoice right now.","We can't save the invoice right now." -"You created the invoice and shipment.","You created the invoice and shipment." -"The invoice has been created.","The invoice has been created." -"We can't send the invoice email right now.","We can't send the invoice email right now." -"We can't send the shipment right now.","We can't send the shipment right now." -"Cannot update item quantity.","Cannot update item quantity." -"The invoice has been voided.","The invoice has been voided." -"Invoice voiding error","Invoice voiding error" -"%1 order(s) cannot be canceled.","%1 order(s) cannot be canceled." -"You cannot cancel the order(s).","You cannot cancel the order(s)." -"We canceled %1 order(s).","We canceled %1 order(s)." -"%1 order(s) were not put on hold.","%1 order(s) were not put on hold." -"No order(s) were put on hold.","No order(s) were put on hold." -"You have put %1 order(s) on hold.","You have put %1 order(s) on hold." -"%1 order(s) were not released from on hold status.","%1 order(s) were not released from on hold status." -"No order(s) were released from on hold status.","No order(s) were released from on hold status." -"%1 order(s) have been released from on hold status.","%1 order(s) have been released from on hold status." -"There are no printable documents related to selected orders.","There are no printable documents related to selected orders." -"The payment has been accepted.","The payment has been accepted." -"The payment has been denied.","The payment has been denied." -"Transaction has been approved.","Transaction has been approved." -"Transaction has been voided/declined.","Transaction has been voided/declined." -"There is no update for the transaction.","There is no update for the transaction." -"We can't update the payment right now.","We can't update the payment right now." -"You assigned the order status.","You assigned the order status." -"Something went wrong while assigning the order status.","Something went wrong while assigning the order status." -"We can't find this order status.","We can't find this order status." -"Create New Order Status","Create New Order Status" -"We found another order status with the same order status code.","We found another order status with the same order status code." -"You saved the order status.","You saved the order status." -"We can't add the order status right now.","We can't add the order status right now." -"You have unassigned the order status.","You have unassigned the order status." -"Something went wrong while unassigning the order.","Something went wrong while unassigning the order." -"Can't unhold order.","Can't unhold order." -"You released the order from holding status.","You released the order from holding status." -"The order was not on hold.","The order was not on hold." -"Exception occurred during order load","Exception occurred during order load" -"Something went wrong while saving the gift message.","Something went wrong while saving the gift message." -"You saved the gift card message.","You saved the gift card message." -"The payment has been voided.","The payment has been voided." -"We can't void the payment right now.","We can't void the payment right now." -"Please correct the transaction ID and try again.","Please correct the transaction ID and try again." -"The transaction details have been updated.","The transaction details have been updated." -"We can't update the transaction details.","We can't update the transaction details." -"Orders and Returns","Orders and Returns" -"You entered incorrect data. Please try again.","You entered incorrect data. Please try again." -Home,Home -"Go to Home Page","Go to Home Page" -"We can't find this wish list.","We can't find this wish list." -"We could not add a product to cart by the ID ""%1"".","We could not add a product to cart by the ID ""%1""." -"There is an error in one of the option rows.","There is an error in one of the option rows." -"Shipping Address: ","Shipping Address: " -"Billing Address: ","Billing Address: " -"Please specify order items.","Please specify order items." -"Please specify a shipping method.","Please specify a shipping method." -"Please specify a payment method.","Please specify a payment method." -"This payment method is not available.","This payment method is not available." -"Validation is failed.","Validation is failed." -"You did not email your customer. Please check your email settings.","You did not email your customer. Please check your email settings." -"-- Please Select --","-- Please Select --" -"Path ""%1"" is not part of allowed directory ""%2""","Path ""%1"" is not part of allowed directory ""%2""" -"Identifying Fields required","Identifying Fields required" -"Id required","Id required" -"""Invoice Document Validation Error(s):\n"" .","""Invoice Document Validation Error(s):\n"" ." -"Could not save an invoice, see error log for details","Could not save an invoice, see error log for details" -"A hold action is not available.","A hold action is not available." -"You cannot remove the hold.","You cannot remove the hold." -"We cannot cancel this order.","We cannot cancel this order." -Guest,Guest -"Please enter the first name.","Please enter the first name." -"Please enter the last name.","Please enter the last name." -"Please enter the street.","Please enter the street." -"Please enter the city.","Please enter the city." -"Please enter the phone number.","Please enter the phone number." -"Please enter the company.","Please enter the company." -"Please enter the fax number.","Please enter the fax number." -"Please enter the zip/postal code.","Please enter the zip/postal code." -"Please enter the country.","Please enter the country." -"Please enter the state/province.","Please enter the state/province." -"Requested entity doesn't exist","Requested entity doesn't exist" -"Could not delete order address","Could not delete order address" -"Could not save order address","Could not save order address" -Pending,Pending -Refunded,Refunded -Canceled,Canceled -"Unknown State","Unknown State" -"We found an invalid quantity to refund item ""%1"".","We found an invalid quantity to refund item ""%1""." -"The creditmemo contains product item that is not part of the original order.","The creditmemo contains product item that is not part of the original order." -"The quantity to refund must not be greater than the unrefunded quantity.","The quantity to refund must not be greater than the unrefunded quantity." -"Maximum shipping amount allowed to refund is: %1","Maximum shipping amount allowed to refund is: %1" -"Order Id is required for creditmemo document","Order Id is required for creditmemo document" -"The creditmemo contains product SKU ""%1"" that is not part of the original order.","The creditmemo contains product SKU ""%1"" that is not part of the original order." -"The quantity to creditmemo must not be greater than the unrefunded quantity for product SKU ""%1"".","The quantity to creditmemo must not be greater than the unrefunded quantity for product SKU ""%1""." -"You can't create a creditmemo without products.","You can't create a creditmemo without products." -"The most money available to refund is %1.","The most money available to refund is %1." -"Could not delete credit memo","Could not delete credit memo" -"Could not save credit memo","Could not save credit memo" -"This order already has associated customer account","This order already has associated customer account" -Paid,Paid -"We cannot register an existing invoice","We cannot register an existing invoice" -"We can't create creditmemo for the invoice.","We can't create creditmemo for the invoice." -"Order Id is required for invoice document","Order Id is required for invoice document" -"The quantity to invoice must not be greater than the uninvoiced quantity for product SKU ""%1"".","The quantity to invoice must not be greater than the uninvoiced quantity for product SKU ""%1""." -"The invoice contains one or more items that are not part of the original order.","The invoice contains one or more items that are not part of the original order." -"ID required","ID required" -"Unknown Status","Unknown Status" -Ordered,Ordered -Shipped,Shipped -Invoiced,Invoiced -Backordered,Backordered -Returned,Returned -Partial,Partial -Mixed,Mixed -"Registered a Void notification.","Registered a Void notification." -"If the invoice was created offline, try creating an offline credit memo.","If the invoice was created offline, try creating an offline credit memo." -"We refunded %1 online.","We refunded %1 online." -"We refunded %1 offline.","We refunded %1 offline." -"IPN ""Refunded"". Refund issued by merchant. Registered notification about refunded amount of %1. Transaction ID: ""%2"". Credit Memo has not been created. Please create offline Credit Memo.","IPN ""Refunded"". Refund issued by merchant. Registered notification about refunded amount of %1. Transaction ID: ""%2"". Credit Memo has not been created. Please create offline Credit Memo." -"The credit memo has been created automatically.","The credit memo has been created automatically." -"Registered notification about refunded amount of %1.","Registered notification about refunded amount of %1." -"Canceled order online","Canceled order online" -"Canceled order offline","Canceled order offline" -"Approved the payment online.","Approved the payment online." -"There is no need to approve this payment.","There is no need to approve this payment." -"Denied the payment online","Denied the payment online" -"Registered update about approved payment.","Registered update about approved payment." -"Registered update about denied payment.","Registered update about denied payment." -"There is no update for the payment.","There is no update for the payment." -"Voided authorization.","Voided authorization." -"Amount: %1.","Amount: %1." -"Transaction ID: ""%1""","Transaction ID: ""%1""" -"The payment method you requested is not available.","The payment method you requested is not available." -"The payment disallows storing objects.","The payment disallows storing objects." -"The transaction ""%1"" cannot be captured yet.","The transaction ""%1"" cannot be captured yet." -"The order amount of %1 is pending approval on the payment gateway.","The order amount of %1 is pending approval on the payment gateway." -"Ordered amount of %1","Ordered amount of %1" -"An amount of %1 will be captured after being approved at the payment gateway.","An amount of %1 will be captured after being approved at the payment gateway." -"Registered notification about captured amount of %1.","Registered notification about captured amount of %1." -"Order is suspended as its capture amount %1 is suspected to be fraudulent.","Order is suspended as its capture amount %1 is suspected to be fraudulent." -"The parent transaction ID must have a transaction ID.","The parent transaction ID must have a transaction ID." -"Payment transactions disallow storing objects.","Payment transactions disallow storing objects." -"The transaction ""%1"" (%2) is already closed.","The transaction ""%1"" (%2) is already closed." -"Set order for existing transactions not allowed","Set order for existing transactions not allowed" -"At minimum, you need to set a payment ID.","At minimum, you need to set a payment ID." -Order,Order -Authorization,Authorization -"We found an unsupported transaction type ""%1"".","We found an unsupported transaction type ""%1""." -"Please set a proper payment and order id.","Please set a proper payment and order id." -"Please enter a Transaction ID.","Please enter a Transaction ID." -"You can't do this without a transaction object.","You can't do this without a transaction object." -"Order # ","Order # " -"Order Date: ","Order Date: " -"Sold to:","Sold to:" -"Ship to:","Ship to:" -"Payment Method:","Payment Method:" -"Shipping Method:","Shipping Method:" -"Total Shipping Charges","Total Shipping Charges" -Title,Title -Number,Number -"We found an invalid renderer model.","We found an invalid renderer model." -"Please define the PDF object before using.","Please define the PDF object before using." -"We don't recognize the draw line data. Please define the ""lines"" array.","We don't recognize the draw line data. Please define the ""lines"" array." -Products,Products -"Total (ex)","Total (ex)" -Qty,Qty -Tax,Tax -"Total (inc)","Total (inc)" -"Credit Memo # ","Credit Memo # " -"Invoice # ","Invoice # " -"The order object is not specified.","The order object is not specified." -"The source object is not specified.","The source object is not specified." -"An item object is not specified.","An item object is not specified." -"A PDF object is not specified.","A PDF object is not specified." -"A PDF page object is not specified.","A PDF page object is not specified." -"Excl. Tax","Excl. Tax" -"Incl. Tax","Incl. Tax" -"Packing Slip # ","Packing Slip # " -title,title -"The PDF total model %1 must be or extend \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal.","The PDF total model %1 must be or extend \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal." -"We cannot register an existing shipment","We cannot register an existing shipment" -"Parent shipment cannot be loaded for track object.","Parent shipment cannot be loaded for track object." -"Order Id is required for shipment document","Order Id is required for shipment document" -"You can't create a shipment without products.","You can't create a shipment without products." -"The shipment contains product SKU ""%1"" that is not part of the original order.","The shipment contains product SKU ""%1"" that is not part of the original order." -"The quantity to ship must not be greater than the unshipped quantity for product SKU ""%1"".","The quantity to ship must not be greater than the unshipped quantity for product SKU ""%1""." -"Please enter a tracking number.","Please enter a tracking number." -"Could not delete shipment","Could not delete shipment" -"Could not save shipment","Could not save shipment" -"The last status can't be unassigned from its current state.","The last status can't be unassigned from its current state." -"Status can't be unassigned, because it is used by existing order(s).","Status can't be unassigned, because it is used by existing order(s)." -"The total model should be extended from \Magento\Sales\Model\Order\Total\AbstractTotal.","The total model should be extended from \Magento\Sales\Model\Order\Total\AbstractTotal." -"An invoice cannot be created when an order has a status of %1","An invoice cannot be created when an order has a status of %1" -"A creditmemo can not be created when an order has a status of %1","A creditmemo can not be created when an order has a status of %1" -"The order does not allow a creditmemo to be created.","The order does not allow a creditmemo to be created." -"A shipment cannot be created when an order has a status of %1","A shipment cannot be created when an order has a status of %1" -"The order does not allow a shipment to be created.","The order does not allow a shipment to be created." -"""Creditmemo Document Validation Error(s):\n"" .","""Creditmemo Document Validation Error(s):\n"" ." -"Could not save a Creditmemo, see error log for details","Could not save a Creditmemo, see error log for details" -"We cannot determine the field name.","We cannot determine the field name." -City,City -Company,Company -Country,Country -Email,Email -"First Name","First Name" -"Last Name","Last Name" -State/Province,State/Province -"Street Address","Street Address" -"Phone Number","Phone Number" -"Zip/Postal Code","Zip/Postal Code" -"We can't save the address:\n%1","We can't save the address:\n%1" -"Cannot save comment:\n%1","Cannot save comment:\n%1" -"We don't have enough information to save the parent transaction ID.","We don't have enough information to save the parent transaction ID." -"We cannot create an empty shipment.","We cannot create an empty shipment." -"Cannot save track:\n%1","Cannot save track:\n%1" -"Cannot unassign status from state","Cannot unassign status from state" -"New Orders","New Orders" -"Order #%1 created at %2","Order #%1 created at %2" -"Details for %1 #%2","Details for %1 #%2" -"Notified Date: %1","Notified Date: %1" -"Comment: %1
","Comment: %1
" -"Current Status: %1
","Current Status: %1
" -"Total: %1
","Total: %1
" -"Order # %1 Notification(s)","Order # %1 Notification(s)" -"You can not cancel Credit Memo","You can not cancel Credit Memo" -"Could not cancel creditmemo","Could not cancel creditmemo" -"We cannot register an existing credit memo.","We cannot register an existing credit memo." -"We found an invalid quantity to invoice item ""%1"".","We found an invalid quantity to invoice item ""%1""." -"The Order State ""%1"" must not be set manually.","The Order State ""%1"" must not be set manually." -"""Shipment Document Validation Error(s):\n"" .","""Shipment Document Validation Error(s):\n"" ." -"Could not save a shipment, see error log for details","Could not save a shipment, see error log for details" -"VAT Request Identifier","VAT Request Identifier" -"VAT Request Date","VAT Request Date" -"Pending Payment","Pending Payment" -Processing,Processing -"On Hold","On Hold" -Complete,Complete -Closed,Closed -"Suspected Fraud","Suspected Fraud" -"Payment Review","Payment Review" -New,New -"test message","test message" -"Email has not been sent","Email has not been sent" -"Authorized amount of %1.","Authorized amount of %1." -"We will authorize %1 after the payment is approved at the payment gateway.","We will authorize %1 after the payment is approved at the payment gateway." -"Authorized amount of %1. Order is suspended as its authorizing amount %1 is suspected to be fraudulent.","Authorized amount of %1. Order is suspended as its authorizing amount %1 is suspected to be fraudulent." -"Captured amount of %1 online.","Captured amount of %1 online." -"Authorized amount of %1","Authorized amount of %1" -" Transaction ID: ""%1"""," Transaction ID: ""%1""" -View,View -"Group was removed","Group was removed" -"Changing address information will not recalculate shipping, tax or other order amount.","Changing address information will not recalculate shipping, tax or other order amount." -"Comment Text","Comment Text" -"Notify Customer by Email","Notify Customer by Email" -"Visible on Storefront","Visible on Storefront" -Customer,Customer -Notified,Notified -"Not Notified","Not Notified" -"No Payment Methods","No Payment Methods" -"Order Comments","Order Comments" -"Apply Coupon Code","Apply Coupon Code" -Apply,Apply -"Remove Coupon Code","Remove Coupon Code" -Remove,Remove -"Address Information","Address Information" -"Payment & Shipping Information","Payment & Shipping Information" -"Order Total","Order Total" -"Order Currency:","Order Currency:" -"Same As Billing Address","Same As Billing Address" -"Select from existing customer addresses:","Select from existing customer addresses:" -"Add New Address","Add New Address" -"Save in address book","Save in address book" -"You don't need to select a shipping address.","You don't need to select a shipping address." -"Gift Message for the Entire Order","Gift Message for the Entire Order" -"Leave this box blank if you don't want to leave a gift message for the entire order.","Leave this box blank if you don't want to leave a gift message for the entire order." -"Row Subtotal","Row Subtotal" -Action,Action -"No ordered items","No ordered items" -"Update Items and Quantities","Update Items and Quantities" -"Total %1 product(s)","Total %1 product(s)" -Subtotal:,Subtotal: -"Tier Pricing","Tier Pricing" -"Custom Price","Custom Price" -"Please select","Please select" -"Move to Shopping Cart","Move to Shopping Cart" -"Move to Wish List","Move to Wish List" -"Subscribe to Newsletter","Subscribe to Newsletter" -"Click to change shipping method","Click to change shipping method" -"Sorry, no quotes are available for this order.","Sorry, no quotes are available for this order." -"Get shipping methods and rates","Get shipping methods and rates" -"You don't need to select a shipping method.","You don't need to select a shipping method." -"Customer's Activities","Customer's Activities" -Refresh,Refresh -Item,Item -"Add To Order","Add To Order" -"Configure and Add to Order","Configure and Add to Order" -"No items","No items" -"Append Comments","Append Comments" -"Email Order Confirmation","Email Order Confirmation" -"Grand Total Excl. Tax","Grand Total Excl. Tax" -"Grand Total Incl. Tax","Grand Total Incl. Tax" -"Subtotal (Excl. Tax)","Subtotal (Excl. Tax)" -"Subtotal (Incl. Tax)","Subtotal (Incl. Tax)" -"Payment & Shipping Method","Payment & Shipping Method" -"Payment Information","Payment Information" -"The order was placed using %1.","The order was placed using %1." -"Shipping Information","Shipping Information" -"Items to Refund","Items to Refund" -"Return to Stock","Return to Stock" -"Qty to Refund","Qty to Refund" -"Tax Amount","Tax Amount" -"Discount Amount","Discount Amount" -"Row Total","Row Total" -"No Items To Refund","No Items To Refund" -"Credit Memo Comments","Credit Memo Comments" -"Refund Totals","Refund Totals" -"Email Copy of Credit Memo","Email Copy of Credit Memo" -"Please enter a positive number in this field.","Please enter a positive number in this field." -"Items Refunded","Items Refunded" -"No Items","No Items" -"Memo Total","Memo Total" -"Credit Memo History","Credit Memo History" -"Credit Memo Totals","Credit Memo Totals" -"Customer Name: %1","Customer Name: %1" -"Purchased From: %1","Purchased From: %1" -"Gift Message","Gift Message" -From:,From: -To:,To: -Message:,Message: -"Shipping & Handling","Shipping & Handling" -"Gift Options","Gift Options" -"Create Shipment","Create Shipment" -"Invoice and shipment types do not match for some items on this order. You can create a shipment only after creating the invoice.","Invoice and shipment types do not match for some items on this order. You can create a shipment only after creating the invoice." -%1,%1 -"Qty to Invoice","Qty to Invoice" -"Invoice History","Invoice History" -"Invoice Comments","Invoice Comments" -"Invoice Totals","Invoice Totals" -Amount,Amount -"Capture Online","Capture Online" -"Capture Offline","Capture Offline" -"Not Capture","Not Capture" -"The invoice will be created offline without the payment gateway.","The invoice will be created offline without the payment gateway." -"Email Copy of Invoice","Email Copy of Invoice" -"Items Invoiced","Items Invoiced" -"Total Tax","Total Tax" -"From Name","From Name" -"To Name","To Name" -Status,Status -Comment,Comment -"Notification Not Applicable","Notification Not Applicable" -"Order & Account Information","Order & Account Information" -"The order confirmation email was sent","The order confirmation email was sent" -"The order confirmation email is not sent","The order confirmation email is not sent" -"Order Date","Order Date" -"Order Date (%1)","Order Date (%1)" -"Purchased From","Purchased From" -"Link to the New Order","Link to the New Order" -"Link to the Previous Order","Link to the Previous Order" -"Placed from IP","Placed from IP" -"%1 / %2 rate:","%1 / %2 rate:" -"Customer Name","Customer Name" -"Customer Group","Customer Group" -"Notes for this Order","Notes for this Order" -"Comment added","Comment added" -"Transaction Data","Transaction Data" -"Transaction ID","Transaction ID" -"Parent Transaction ID","Parent Transaction ID" -"Order ID","Order ID" -"Transaction Type","Transaction Type" -"Is Closed","Is Closed" -"Created At","Created At" -"Child Transactions","Child Transactions" -"Transaction Details","Transaction Details" -Items,Items -"Gift Message for this Order","Gift Message for this Order" -"Shipped By","Shipped By" -"Tracking Number","Tracking Number" -"Billing Last Name","Billing Last Name" -"Find Order By","Find Order By" -"ZIP Code","ZIP Code" -"Billing ZIP Code","Billing ZIP Code" -Continue,Continue -"Print All Refunds","Print All Refunds" -"Refund #","Refund #" -"Print Refund","Print Refund" -"Product Name","Product Name" -"Order #","Order #" -Date,Date -"Ship To","Ship To" -Actions,Actions -"View Order","View Order" -"You have placed no orders.","You have placed no orders." -"No shipping information available","No shipping information available" -"Print Order","Print Order" -"Print All Invoices","Print All Invoices" -"Invoice #","Invoice #" -"Print Invoice","Print Invoice" -"Qty Invoiced","Qty Invoiced" -Close,Close -"About Your Order","About Your Order" -"Order Date: %1","Order Date: %1" -"Refund #%1","Refund #%1" -"Shipment #%1","Shipment #%1" -"Qty Shipped","Qty Shipped" -"Recent Orders","Recent Orders" -"View All","View All" -"Gift Message for This Order","Gift Message for This Order" -"Recently Ordered","Recently Ordered" -"Add to Cart","Add to Cart" -Search,Search -"Credit memo for your %store_name order","Credit memo for your %store_name order" -"%name,","%name," -"Thank you for your order from %store_name.","Thank you for your order from %store_name." -"You can check the status of your order by logging into your account.","You can check the status of your order by logging into your account." -"If you have questions about your order, you can email us at %store_email","If you have questions about your order, you can email us at %store_email" -"or call us at %store_phone","or call us at %store_phone" -"Our hours are %store_hours.","Our hours are %store_hours." -"Your Credit Memo #%creditmemo_id for Order #%order_id","Your Credit Memo #%creditmemo_id for Order #%order_id" -"Billing Info","Billing Info" -"Shipping Info","Shipping Info" -"Update to your %store_name credit memo","Update to your %store_name credit memo" -"Your order #%increment_id has been updated with a status of %order_status.","Your order #%increment_id has been updated with a status of %order_status." -"Invoice for your %store_name order","Invoice for your %store_name order" -"Your Invoice #%invoice_id for Order #%order_id","Your Invoice #%invoice_id for Order #%order_id" -"Update to your %store_name invoice","Update to your %store_name invoice" -"Your %store_name order confirmation","Your %store_name order confirmation" -"%customer_name,","%customer_name," -"Once your package ships we will send you a tracking number.","Once your package ships we will send you a tracking number." -"Your Order #%increment_id","Your Order #%increment_id" -"Placed on %created_at","Placed on %created_at" -"Once your package ships we will send an email with a link to track your order.","Once your package ships we will send an email with a link to track your order." -"Update to your %store_name order","Update to your %store_name order" -"Your %store_name order has shipped","Your %store_name order has shipped" -"Your shipping confirmation is below. Thank you again for your business.","Your shipping confirmation is below. Thank you again for your business." -"Your Shipment #%shipment_id for Order #%order_id","Your Shipment #%shipment_id for Order #%order_id" -"Update to your %store_name shipment","Update to your %store_name shipment" -"Gift Options for ","Gift Options for " -"Add Products","Add Products" -"You have item changes","You have item changes" -Ok,Ok -Operations,Operations -Create,Create -"Send Order Email","Send Order Email" -"Accept or Deny Payment","Accept or Deny Payment" -"Send Sales Emails","Send Sales Emails" -"Sales Section","Sales Section" -"Sales Emails Section","Sales Emails Section" -General,General -"Hide Customer IP","Hide Customer IP" -"Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos.","Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos." -"Checkout Totals Sort Order","Checkout Totals Sort Order" -"Allow Reorder","Allow Reorder" -"Invoice and Packing Slip Design","Invoice and Packing Slip Design" -"Logo for PDF Print-outs (200x50)","Logo for PDF Print-outs (200x50)" -"Your default logo will be used in PDF and HTML documents.
(jpeg, tiff, png) If your pdf image is distorted, try to use larger file-size image.","Your default logo will be used in PDF and HTML documents.
(jpeg, tiff, png) If your pdf image is distorted, try to use larger file-size image." -"Logo for HTML Print View","Logo for HTML Print View" -"Logo for HTML documents only. If empty, default will be used.
(jpeg, gif, png)","Logo for HTML documents only. If empty, default will be used.
(jpeg, gif, png)" -Address,Address -"Minimum Order Amount","Minimum Order Amount" -Enable,Enable -"Minimum Amount","Minimum Amount" -"Subtotal after discount","Subtotal after discount" -"Include Tax to Amount","Include Tax to Amount" -"Description Message","Description Message" -"This message will be shown in the shopping cart when the subtotal (after discount) is lower than the minimum allowed amount.","This message will be shown in the shopping cart when the subtotal (after discount) is lower than the minimum allowed amount." -"Error to Show in Shopping Cart","Error to Show in Shopping Cart" -"Validate Each Address Separately in Multi-address Checkout","Validate Each Address Separately in Multi-address Checkout" -"Multi-address Description Message","Multi-address Description Message" -"We'll use the default description above if you leave this empty.","We'll use the default description above if you leave this empty." -"Multi-address Error to Show in Shopping Cart","Multi-address Error to Show in Shopping Cart" -"We'll use the default error above if you leave this empty.","We'll use the default error above if you leave this empty." -Dashboard,Dashboard -"Use Aggregated Data","Use Aggregated Data" -"Orders Cron Settings","Orders Cron Settings" -"Pending Payment Order Lifetime (minutes)","Pending Payment Order Lifetime (minutes)" -"Sales Emails","Sales Emails" -"Asynchronous sending","Asynchronous sending" -Enabled,Enabled -"New Order Confirmation Email Sender","New Order Confirmation Email Sender" -"New Order Confirmation Template","New Order Confirmation Template" -"Email template chosen based on theme fallback when ""Default"" option is selected.","Email template chosen based on theme fallback when ""Default"" option is selected." -"New Order Confirmation Template for Guest","New Order Confirmation Template for Guest" -"Send Order Email Copy To","Send Order Email Copy To" -Comma-separated,Comma-separated -"Send Order Email Copy Method","Send Order Email Copy Method" -"Order Comment Email Sender","Order Comment Email Sender" -"Order Comment Email Template","Order Comment Email Template" -"Order Comment Email Template for Guest","Order Comment Email Template for Guest" -"Send Order Comment Email Copy To","Send Order Comment Email Copy To" -"Send Order Comments Email Copy Method","Send Order Comments Email Copy Method" -"Invoice Email Sender","Invoice Email Sender" -"Invoice Email Template","Invoice Email Template" -"Invoice Email Template for Guest","Invoice Email Template for Guest" -"Send Invoice Email Copy To","Send Invoice Email Copy To" -"Send Invoice Email Copy Method","Send Invoice Email Copy Method" -"Invoice Comment Email Sender","Invoice Comment Email Sender" -"Invoice Comment Email Template","Invoice Comment Email Template" -"Invoice Comment Email Template for Guest","Invoice Comment Email Template for Guest" -"Send Invoice Comment Email Copy To","Send Invoice Comment Email Copy To" -"Send Invoice Comments Email Copy Method","Send Invoice Comments Email Copy Method" -Shipment,Shipment -"Shipment Email Sender","Shipment Email Sender" -"Shipment Email Template","Shipment Email Template" -"Shipment Email Template for Guest","Shipment Email Template for Guest" -"Send Shipment Email Copy To","Send Shipment Email Copy To" -"Send Shipment Email Copy Method","Send Shipment Email Copy Method" -"Shipment Comments","Shipment Comments" -"Shipment Comment Email Sender","Shipment Comment Email Sender" -"Shipment Comment Email Template","Shipment Comment Email Template" -"Shipment Comment Email Template for Guest","Shipment Comment Email Template for Guest" -"Send Shipment Comment Email Copy To","Send Shipment Comment Email Copy To" -"Send Shipment Comments Email Copy Method","Send Shipment Comments Email Copy Method" -"Credit Memo Email Sender","Credit Memo Email Sender" -"Credit Memo Email Template","Credit Memo Email Template" -"Credit Memo Email Template for Guest","Credit Memo Email Template for Guest" -"Send Credit Memo Email Copy To","Send Credit Memo Email Copy To" -"Send Credit Memo Email Copy Method","Send Credit Memo Email Copy Method" -"Credit Memo Comment Email Sender","Credit Memo Comment Email Sender" -"Credit Memo Comment Email Template","Credit Memo Comment Email Template" -"Credit Memo Comment Email Template for Guest","Credit Memo Comment Email Template for Guest" -"Send Credit Memo Comment Email Copy To","Send Credit Memo Comment Email Copy To" -"Send Credit Memo Comments Email Copy Method","Send Credit Memo Comments Email Copy Method" -"PDF Print-outs","PDF Print-outs" -"Display Order ID in Header","Display Order ID in Header" -"Customer Order Status Notification","Customer Order Status Notification" -"Asynchronous indexing","Asynchronous indexing" -"Orders and Returns Search Form","Orders and Returns Search Form" -"Anchor Custom Title","Anchor Custom Title" -Template,Template -"Default Template","Default Template" -Name,Name -Phone,Phone -"ZIP/Post Code","ZIP/Post Code" -"Signed-up Point","Signed-up Point" -Website,Website -"Bill-to Name","Bill-to Name" -Created,Created -"Invoice Date","Invoice Date" -"Ship-to Name","Ship-to Name" -"Ship Date","Ship Date" -"Total Quantity","Total Quantity" -"Default Status","Default Status" -"State Code and Title","State Code and Title" -"Item Status","Item Status" -"Original Price","Original Price" -"Tax Percent","Tax Percent" -"All Store Views","All Store Views" -"PDF Credit Memos","PDF Credit Memos" -"Purchase Point","Purchase Point" -"Print Invoices","Print Invoices" -"Print Packing Slips","Print Packing Slips" -"Print Credit Memos","Print Credit Memos" -"Print All","Print All" -"Print Shipping Labels","Print Shipping Labels" -"Purchase Date","Purchase Date" -"Grand Total (Base)","Grand Total (Base)" -"Grand Total (Purchased)","Grand Total (Purchased)" -"Customer Email","Customer Email" -"Shipping and Handling","Shipping and Handling" -"PDF Invoices","PDF Invoices" -"PDF Shipments","PDF Shipments" -"PDF Creditmemos","PDF Creditmemos" -Refunds,Refunds -"Allow Zero GrandTotal for Creditmemo","Allow Zero GrandTotal for Creditmemo" -"Allow Zero GrandTotal","Allow Zero GrandTotal" +Credit Memos,Credit Memos,, +Orders,Orders,, +Invoices,Invoices,, +We can't get the order instance right now.,We can't get the order instance right now.,, +Create New Order,Create New Order,, +Save Order Address,Save Order Address,, +Shipping,Shipping,, +Billing,Billing,, +Edit Order %1 %2 Address,Edit Order %1 %2 Address,, +Order Address Information,Order Address Information,, +Please correct the parent block for this block.,Please correct the parent block for this block.,, +Submit Comment,Submit Comment,, +Submit Order,Submit Order,, +Are you sure you want to cancel this order?,Are you sure you want to cancel this order?,, +Cancel,Cancel,, +Billing Address,Billing Address,, +Payment Method,Payment Method,, +Order Comment,Order Comment,, +Coupons,Coupons,, +Please select a customer,Please select a customer,, +Create New Customer,Create New Customer,, +Account Information,Account Information,, +From,From,, +To,To,, +Message,Message,, +Edit Order #%1,Edit Order #%1,, +Create New Order for %1 in %2,Create New Order for %1 in %2,, +Create New Order in %1,Create New Order in %1,, +Create New Order for %1,Create New Order for %1,, +Create New Order for New Customer,Create New Order for New Customer,, +Items Ordered,Items Ordered,, +This product is disabled.,This product is disabled.,, +Buy %1 for price %2,Buy %1 for price %2,, +Item ordered qty,Item ordered qty,, +%1 with %2 discount each,%1 with %2 discount each,, +%1 for %2,%1 for %2,, +* - Enter custom price including tax,* - Enter custom price including tax,, +* - Enter custom price excluding tax,* - Enter custom price excluding tax,, +Configure,Configure,, +This product does not have any configurable options,This product does not have any configurable options,, +Newsletter Subscription,Newsletter Subscription,, +Please select products,Please select products,, +Add Selected Product(s) to Order,Add Selected Product(s) to Order,, +ID,ID,, +Product,Product,, +SKU,SKU,, +Price,Price,, +Select,Select,, +Quantity,Quantity,, +Shipping Address,Shipping Address,, +Shipping Method,Shipping Method,, +Update Changes,Update Changes,, +Shopping Cart,Shopping Cart,, +Are you sure you want to delete all items from shopping cart?,Are you sure you want to delete all items from shopping cart?,, +Clear Shopping Cart,Clear Shopping Cart,, +Products in Comparison List,Products in Comparison List,, +Recently Compared Products,Recently Compared Products,, +Recently Viewed Products,Recently Viewed Products,, +Last Ordered Items,Last Ordered Items,, +Recently Viewed,Recently Viewed,, +Wish List,Wish List,, +Please select a store,Please select a store,, +Order Totals,Order Totals,, +Shipping Incl. Tax (%1),Shipping Incl. Tax (%1),, +Shipping Excl. Tax (%1),Shipping Excl. Tax (%1),, +New Credit Memo for Invoice #%1,New Credit Memo for Invoice #%1,, +New Credit Memo for Order #%1,New Credit Memo for Order #%1,, +Refund Shipping (Incl. Tax),Refund Shipping (Incl. Tax),, +Refund Shipping (Excl. Tax),Refund Shipping (Excl. Tax),, +Refund Shipping,Refund Shipping,, +Update Qty's,Update Qty's,, +Refund,Refund,, +Refund Offline,Refund Offline,, +Paid Amount,Paid Amount,, +Refund Amount,Refund Amount,, +Shipping Amount,Shipping Amount,, +Shipping Refund,Shipping Refund,, +Order Grand Total,Order Grand Total,, +Adjustment Refund,Adjustment Refund,, +Adjustment Fee,Adjustment Fee,, +Send Email,Send Email,, +Are you sure you want to send a credit memo email to customer?,Are you sure you want to send a credit memo email to customer?,, +Void,Void,, +Print,Print,, +The credit memo email was sent.,The credit memo email was sent.,, +The credit memo email wasn't sent.,The credit memo email wasn't sent.,, +Credit Memo #%1 | %3 | %2 (%4),Credit Memo #%1 | %3 | %2 (%4),, +Total Refund,Total Refund,, +New Invoice and Shipment for Order #%1,New Invoice and Shipment for Order #%1,, +New Invoice for Order #%1,New Invoice for Order #%1,, +Submit Invoice and Shipment,Submit Invoice and Shipment,, +Submit Invoice,Submit Invoice,, +Are you sure you want to send an invoice email to customer?,Are you sure you want to send an invoice email to customer?,, +Credit Memo,Credit Memo,, +Capture,Capture,, +The invoice email was sent.,The invoice email was sent.,, +The invoice email wasn't sent.,The invoice email wasn't sent.,, +Invoice #%1 | %2 | %4 (%3),Invoice #%1 | %2 | %4 (%3),, +Invalid parent block for this block,Invalid parent block for this block,, +Order Statuses,Order Statuses,, +Create New Status,Create New Status,, +Assign Status to State,Assign Status to State,, +Save Status Assignment,Save Status Assignment,, +Assign Order Status to State,Assign Order Status to State,, +Assignment Information,Assignment Information,, +Order Status,Order Status,, +Order State,Order State,, +Use Order Status As Default,Use Order Status As Default,, +Visible On Storefront,Visible On Storefront,, +Edit Order Status,Edit Order Status,, +Save Status,Save Status,, +New Order Status,New Order Status,, +Order Status Information,Order Status Information,, +Status Code,Status Code,, +Status Label,Status Label,, +Store View Specific Labels,Store View Specific Labels,, +Total Paid,Total Paid,, +Total Refunded,Total Refunded,, +Total Due,Total Due,, +Edit,Edit,, +Are you sure you want to send an order email to customer?,Are you sure you want to send an order email to customer?,, +"This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you want to continue?","This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you want to continue?",, +Are you sure you want to void the payment?,Are you sure you want to void the payment?,, +Hold,Hold,, +hold,hold,, +Unhold,Unhold,, +unhold,unhold,, +Are you sure you want to accept this payment?,Are you sure you want to accept this payment?,, +Accept Payment,Accept Payment,, +Are you sure you want to deny this payment?,Are you sure you want to deny this payment?,, +Deny Payment,Deny Payment,, +Get Payment Update,Get Payment Update,, +Invoice and Ship,Invoice and Ship,, +Invoice,Invoice,, +Ship,Ship,, +Reorder,Reorder,, +Order # %1 %2 | %3,Order # %1 %2 | %3,, +"This order contains (%1) items and therefore cannot be edited through the admin interface. If you wish to continue editing, the (%2) items will be removed, the order will be canceled and a new order will be placed.","This order contains (%1) items and therefore cannot be edited through the admin interface. If you wish to continue editing, the (%2) items will be removed, the order will be canceled and a new order will be placed.",, +Are you sure? This order will be canceled and a new one will be created instead.,Are you sure? This order will be canceled and a new one will be created instead.,, +Save Gift Message,Save Gift Message,, + [deleted], [deleted],, +Order Credit Memos,Order Credit Memos,, +Credit memo #%1 created,Credit memo #%1 created,, +Credit memo #%1 comment added,Credit memo #%1 comment added,, +Shipment #%1 created,Shipment #%1 created,, +Shipment #%1 comment added,Shipment #%1 comment added,, +Invoice #%1 created,Invoice #%1 created,, +Invoice #%1 comment added,Invoice #%1 comment added,, +Tracking number %1 for %2 assigned,Tracking number %1 for %2 assigned,, +Comments History,Comments History,, +Order History,Order History,, +Information,Information,, +Order Information,Order Information,, +Order Invoices,Order Invoices,, +Shipments,Shipments,, +Order Shipments,Order Shipments,, +Transactions,Transactions,, +Order View,Order View,, +Any,Any,, +Specified,Specified,, +Applies to Any of the Specified Order Statuses except canceled orders,Applies to Any of the Specified Order Statuses except canceled orders,, +Cart Price Rule,Cart Price Rule,, +Yes,Yes,, +No,No,, +Show Actual Values,Show Actual Values,, +New Order RSS,New Order RSS,, +Subtotal,Subtotal,, +Shipping & Handling,Shipping & Handling,, +Discount (%1),Discount (%1),, +Discount,Discount,, +Grand Total,Grand Total,, +Back,Back,, +Fetch,Fetch,, +Transaction # %1 | %2,Transaction # %1 | %2,, +N/A,N/A,, +Key,Key,, +Value,Value,, +We found an invalid entity model.,We found an invalid entity model.,, +Order # %1,Order # %1,, +Back to My Orders,Back to My Orders,, +View Another Order,View Another Order,, +About Your Refund,About Your Refund,, +My Orders,My Orders,, +Subscribe to Order Status,Subscribe to Order Status,, +About Your Invoice,About Your Invoice,, +Print Order # %1,Print Order # %1,, +Grand Total to be Charged,Grand Total to be Charged,, +Unassign,Unassign,, +We can't add this item to your shopping cart right now.,We can't add this item to your shopping cart right now.,, +You sent the message.,You sent the message.,, +Sales,Sales,, +Invoice capturing error,Invoice capturing error,, +This order no longer exists.,This order no longer exists.,, +Please enter a comment.,Please enter a comment.,, +We cannot add order history.,We cannot add order history.,, +You updated the order address.,You updated the order address.,, +We can't update the order address right now.,We can't update the order address right now.,, +You have not canceled the item.,You have not canceled the item.,, +You canceled the order.,You canceled the order.,, +"""%1"" coupon code was not applied. Do not apply discount is selected for item(s)","""%1"" coupon code was not applied. Do not apply discount is selected for item(s)",, +"""%1"" coupon code is not valid.","""%1"" coupon code is not valid.",, +The coupon code has been accepted.,The coupon code has been accepted.,, +Quote item id is not received.,Quote item id is not received.,, +Quote item is not loaded.,Quote item is not loaded.,, +New Order,New Order,, +You created the order.,You created the order.,, +Order saving error: %1,Order saving error: %1,, +Cannot add new comment.,Cannot add new comment.,, +The credit memo has been canceled.,The credit memo has been canceled.,, +Credit memo has not been canceled.,Credit memo has not been canceled.,, +New Memo for #%1,New Memo for #%1,, +New Memo,New Memo,, +The credit memo's total must be positive.,The credit memo's total must be positive.,, +Cannot create online refund for Refund to Store Credit.,Cannot create online refund for Refund to Store Credit.,, +You created the credit memo.,You created the credit memo.,, +We can't save the credit memo right now.,We can't save the credit memo right now.,, +We can't update the item's quantity right now.,We can't update the item's quantity right now.,, +View Memo for #%1,View Memo for #%1,, +View Memo,View Memo,, +You voided the credit memo.,You voided the credit memo.,, +We can't void the credit memo.,We can't void the credit memo.,, +The order no longer exists.,The order no longer exists.,, +We can't create credit memo for the order.,We can't create credit memo for the order.,, +Edit Order,Edit Order,, +You sent the order email.,You sent the order email.,, +We can't send the email order right now.,We can't send the email order right now.,, +You have not put the order on hold.,You have not put the order on hold.,, +You put the order on hold.,You put the order on hold.,, +You canceled the invoice.,You canceled the invoice.,, +Invoice canceling error,Invoice canceling error,, +The invoice has been captured.,The invoice has been captured.,, +The order does not allow an invoice to be created.,The order does not allow an invoice to be created.,, +You can't create an invoice without products.,You can't create an invoice without products.,, +New Invoice,New Invoice,, +We can't save the invoice right now.,We can't save the invoice right now.,, +You created the invoice and shipment.,You created the invoice and shipment.,, +The invoice has been created.,The invoice has been created.,, +We can't send the invoice email right now.,We can't send the invoice email right now.,, +We can't send the shipment right now.,We can't send the shipment right now.,, +Cannot update item quantity.,Cannot update item quantity.,, +The invoice has been voided.,The invoice has been voided.,, +Invoice voiding error,Invoice voiding error,, +%1 order(s) cannot be canceled.,%1 order(s) cannot be canceled.,, +You cannot cancel the order(s).,You cannot cancel the order(s).,, +We canceled %1 order(s).,We canceled %1 order(s).,, +%1 order(s) were not put on hold.,%1 order(s) were not put on hold.,, +No order(s) were put on hold.,No order(s) were put on hold.,, +You have put %1 order(s) on hold.,You have put %1 order(s) on hold.,, +%1 order(s) were not released from on hold status.,%1 order(s) were not released from on hold status.,, +No order(s) were released from on hold status.,No order(s) were released from on hold status.,, +%1 order(s) have been released from on hold status.,%1 order(s) have been released from on hold status.,, +There are no printable documents related to selected orders.,There are no printable documents related to selected orders.,, +The payment has been accepted.,The payment has been accepted.,, +The payment has been denied.,The payment has been denied.,, +Transaction has been approved.,Transaction has been approved.,, +Transaction has been voided/declined.,Transaction has been voided/declined.,, +There is no update for the transaction.,There is no update for the transaction.,, +We can't update the payment right now.,We can't update the payment right now.,, +You assigned the order status.,You assigned the order status.,, +Something went wrong while assigning the order status.,Something went wrong while assigning the order status.,, +We can't find this order status.,We can't find this order status.,, +Create New Order Status,Create New Order Status,, +We found another order status with the same order status code.,We found another order status with the same order status code.,, +You saved the order status.,You saved the order status.,, +We can't add the order status right now.,We can't add the order status right now.,, +You have unassigned the order status.,You have unassigned the order status.,, +Something went wrong while unassigning the order.,Something went wrong while unassigning the order.,, +Can't unhold order.,Can't unhold order.,, +You released the order from holding status.,You released the order from holding status.,, +The order was not on hold.,The order was not on hold.,, +Exception occurred during order load,Exception occurred during order load,, +Something went wrong while saving the gift message.,Something went wrong while saving the gift message.,, +You saved the gift card message.,You saved the gift card message.,, +The payment has been voided.,The payment has been voided.,, +We can't void the payment right now.,We can't void the payment right now.,, +Please correct the transaction ID and try again.,Please correct the transaction ID and try again.,, +The transaction details have been updated.,The transaction details have been updated.,, +We can't update the transaction details.,We can't update the transaction details.,, +Orders and Returns,Orders and Returns,, +You entered incorrect data. Please try again.,You entered incorrect data. Please try again.,, +Home,Home,, +Go to Home Page,Go to Home Page,, +We can't find this wish list.,We can't find this wish list.,, +"We could not add a product to cart by the ID ""%1"".","We could not add a product to cart by the ID ""%1"".",, +There is an error in one of the option rows.,There is an error in one of the option rows.,, +Shipping Address: ,Shipping Address: ,, +Billing Address: ,Billing Address: ,, +Please specify order items.,Please specify order items.,, +Please specify a shipping method.,Please specify a shipping method.,, +Please specify a payment method.,Please specify a payment method.,, +This payment method is not available.,This payment method is not available.,, +Validation is failed.,Validation is failed.,, +You did not email your customer. Please check your email settings.,You did not email your customer. Please check your email settings.,, +-- Please Select --,-- Please Select --,, +"Path ""%1"" is not part of allowed directory ""%2""","Path ""%1"" is not part of allowed directory ""%2""",, +Identifying Fields required,Identifying Fields required,, +Id required,Id required,, +"""Invoice Document Validation Error(s):\n"" .","""Invoice Document Validation Error(s):\n"" .",, +"Could not save an invoice, see error log for details","Could not save an invoice, see error log for details",, +A hold action is not available.,A hold action is not available.,, +You cannot remove the hold.,You cannot remove the hold.,, +We cannot cancel this order.,We cannot cancel this order.,, +Guest,Guest,, +Please enter the first name.,Please enter the first name.,, +Please enter the last name.,Please enter the last name.,, +Please enter the street.,Please enter the street.,, +Please enter the city.,Please enter the city.,, +Please enter the phone number.,Please enter the phone number.,, +Please enter the company.,Please enter the company.,, +Please enter the fax number.,Please enter the fax number.,, +Please enter the zip/postal code.,Please enter the zip/postal code.,, +Please enter the country.,Please enter the country.,, +Please enter the state/province.,Please enter the state/province.,, +Requested entity doesn't exist,Requested entity doesn't exist,, +Could not delete order address,Could not delete order address,, +Could not save order address,Could not save order address,, +Pending,Pending,, +Refunded,Refunded,, +Canceled,Canceled,, +Unknown State,Unknown State,, +"We found an invalid quantity to refund item ""%1"".","We found an invalid quantity to refund item ""%1"".",, +The creditmemo contains product item that is not part of the original order.,The creditmemo contains product item that is not part of the original order.,, +The quantity to refund must not be greater than the unrefunded quantity.,The quantity to refund must not be greater than the unrefunded quantity.,, +Maximum shipping amount allowed to refund is: %1,Maximum shipping amount allowed to refund is: %1,, +Order Id is required for creditmemo document,Order Id is required for creditmemo document,, +"The creditmemo contains product SKU ""%1"" that is not part of the original order.","The creditmemo contains product SKU ""%1"" that is not part of the original order.",, +"The quantity to creditmemo must not be greater than the unrefunded quantity for product SKU ""%1"".","The quantity to creditmemo must not be greater than the unrefunded quantity for product SKU ""%1"".",, +You can't create a creditmemo without products.,You can't create a creditmemo without products.,, +The most money available to refund is %1.,The most money available to refund is %1.,, +Could not delete credit memo,Could not delete credit memo,, +Could not save credit memo,Could not save credit memo,, +This order already has associated customer account,This order already has associated customer account,, +Paid,Paid,, +We cannot register an existing invoice,We cannot register an existing invoice,, +We can't create creditmemo for the invoice.,We can't create creditmemo for the invoice.,, +Order Id is required for invoice document,Order Id is required for invoice document,, +"The quantity to invoice must not be greater than the uninvoiced quantity for product SKU ""%1"".","The quantity to invoice must not be greater than the uninvoiced quantity for product SKU ""%1"".",, +The invoice contains one or more items that are not part of the original order.,The invoice contains one or more items that are not part of the original order.,, +ID required,ID required,, +Unknown Status,Unknown Status,, +Ordered,Ordered,, +Shipped,Shipped,, +Invoiced,Invoiced,, +Backordered,Backordered,, +Returned,Returned,, +Partial,Partial,, +Mixed,Mixed,, +Registered a Void notification.,Registered a Void notification.,, +"If the invoice was created offline, try creating an offline credit memo.","If the invoice was created offline, try creating an offline credit memo.",, +We refunded %1 online.,We refunded %1 online.,, +We refunded %1 offline.,We refunded %1 offline.,, +"IPN ""Refunded"". Refund issued by merchant. Registered notification about refunded amount of %1. Transaction ID: ""%2"". Credit Memo has not been created. Please create offline Credit Memo.","IPN ""Refunded"". Refund issued by merchant. Registered notification about refunded amount of %1. Transaction ID: ""%2"". Credit Memo has not been created. Please create offline Credit Memo.",, +The credit memo has been created automatically.,The credit memo has been created automatically.,, +Registered notification about refunded amount of %1.,Registered notification about refunded amount of %1.,, +Canceled order online,Canceled order online,, +Canceled order offline,Canceled order offline,, +Approved the payment online.,Approved the payment online.,, +There is no need to approve this payment.,There is no need to approve this payment.,, +Denied the payment online,Denied the payment online,, +Registered update about approved payment.,Registered update about approved payment.,, +Registered update about denied payment.,Registered update about denied payment.,, +There is no update for the payment.,There is no update for the payment.,, +Voided authorization.,Voided authorization.,, +Amount: %1.,Amount: %1.,, +"Transaction ID: ""%1""","Transaction ID: ""%1""",, +The payment method you requested is not available.,The payment method you requested is not available.,, +The payment disallows storing objects.,The payment disallows storing objects.,, +"The transaction ""%1"" cannot be captured yet.","The transaction ""%1"" cannot be captured yet.",, +The order amount of %1 is pending approval on the payment gateway.,The order amount of %1 is pending approval on the payment gateway.,, +Ordered amount of %1,Ordered amount of %1,, +An amount of %1 will be captured after being approved at the payment gateway.,An amount of %1 will be captured after being approved at the payment gateway.,, +Registered notification about captured amount of %1.,Registered notification about captured amount of %1.,, +Order is suspended as its capture amount %1 is suspected to be fraudulent.,Order is suspended as its capture amount %1 is suspected to be fraudulent.,, +The parent transaction ID must have a transaction ID.,The parent transaction ID must have a transaction ID.,, +Payment transactions disallow storing objects.,Payment transactions disallow storing objects.,, +"The transaction ""%1"" (%2) is already closed.","The transaction ""%1"" (%2) is already closed.",, +Set order for existing transactions not allowed,Set order for existing transactions not allowed,, +"At minimum, you need to set a payment ID.","At minimum, you need to set a payment ID.",, +Order,Order,, +Authorization,Authorization,, +"We found an unsupported transaction type ""%1"".","We found an unsupported transaction type ""%1"".",, +Please set a proper payment and order id.,Please set a proper payment and order id.,, +Please enter a Transaction ID.,Please enter a Transaction ID.,, +You can't do this without a transaction object.,You can't do this without a transaction object.,, +Order # ,Order # ,, +Order Date: ,Order Date: ,, +Sold to:,Sold to:,, +Ship to:,Ship to:,, +Payment Method:,Payment Method:,, +Shipping Method:,Shipping Method:,, +Total Shipping Charges,Total Shipping Charges,, +Title,Title,, +Number,Number,, +We found an invalid renderer model.,We found an invalid renderer model.,, +Please define the PDF object before using.,Please define the PDF object before using.,, +"We don't recognize the draw line data. Please define the ""lines"" array.","We don't recognize the draw line data. Please define the ""lines"" array.",, +Products,Products,, +Total (ex),Total (ex),, +Qty,Qty,, +Tax,Tax,, +Total (inc),Total (inc),, +Credit Memo # ,Credit Memo # ,, +Invoice # ,Invoice # ,, +The order object is not specified.,The order object is not specified.,, +The source object is not specified.,The source object is not specified.,, +An item object is not specified.,An item object is not specified.,, +A PDF object is not specified.,A PDF object is not specified.,, +A PDF page object is not specified.,A PDF page object is not specified.,, +Excl. Tax,Excl. Tax,, +Incl. Tax,Incl. Tax,, +Packing Slip # ,Packing Slip # ,, +title,title,, +The PDF total model %1 must be or extend \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal.,The PDF total model %1 must be or extend \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal.,, +We cannot register an existing shipment,We cannot register an existing shipment,, +Parent shipment cannot be loaded for track object.,Parent shipment cannot be loaded for track object.,, +Order Id is required for shipment document,Order Id is required for shipment document,, +You can't create a shipment without products.,You can't create a shipment without products.,, +"The shipment contains product SKU ""%1"" that is not part of the original order.","The shipment contains product SKU ""%1"" that is not part of the original order.",, +"The quantity to ship must not be greater than the unshipped quantity for product SKU ""%1"".","The quantity to ship must not be greater than the unshipped quantity for product SKU ""%1"".",, +Please enter a tracking number.,Please enter a tracking number.,, +Could not delete shipment,Could not delete shipment,, +Could not save shipment,Could not save shipment,, +The last status can't be unassigned from its current state.,The last status can't be unassigned from its current state.,, +"Status can't be unassigned, because it is used by existing order(s).","Status can't be unassigned, because it is used by existing order(s).",, +The total model should be extended from \Magento\Sales\Model\Order\Total\AbstractTotal.,The total model should be extended from \Magento\Sales\Model\Order\Total\AbstractTotal.,, +An invoice cannot be created when an order has a status of %1,An invoice cannot be created when an order has a status of %1,, +A creditmemo can not be created when an order has a status of %1,A creditmemo can not be created when an order has a status of %1,, +The order does not allow a creditmemo to be created.,The order does not allow a creditmemo to be created.,, +A shipment cannot be created when an order has a status of %1,A shipment cannot be created when an order has a status of %1,, +The order does not allow a shipment to be created.,The order does not allow a shipment to be created.,, +"""Creditmemo Document Validation Error(s):\n"" .","""Creditmemo Document Validation Error(s):\n"" .",, +"Could not save a Creditmemo, see error log for details","Could not save a Creditmemo, see error log for details",, +We cannot determine the field name.,We cannot determine the field name.,, +City,City,, +Company,Company,, +Country,Country,, +Email,Email,, +First Name,First Name,, +Last Name,Last Name,, +State/Province,State/Province,, +Street Address,Street Address,, +Phone Number,Phone Number,, +Zip/Postal Code,Zip/Postal Code,, +We can't save the address:\n%1,We can't save the address:\n%1,, +Cannot save comment:\n%1,Cannot save comment:\n%1,, +We don't have enough information to save the parent transaction ID.,We don't have enough information to save the parent transaction ID.,, +We cannot create an empty shipment.,We cannot create an empty shipment.,, +Cannot save track:\n%1,Cannot save track:\n%1,, +Cannot unassign status from state,Cannot unassign status from state,, +New Orders,New Orders,, +Order #%1 created at %2,Order #%1 created at %2,, +Details for %1 #%2,Details for %1 #%2,, +Notified Date: %1,Notified Date: %1,, +Comment: %1
,Comment: %1
,, +Current Status: %1
,Current Status: %1
,, +Total: %1
,Total: %1
,, +Order # %1 Notification(s),Order # %1 Notification(s),, +You can not cancel Credit Memo,You can not cancel Credit Memo,, +Could not cancel creditmemo,Could not cancel creditmemo,, +We cannot register an existing credit memo.,We cannot register an existing credit memo.,, +"We found an invalid quantity to invoice item ""%1"".","We found an invalid quantity to invoice item ""%1"".",, +"The Order State ""%1"" must not be set manually.","The Order State ""%1"" must not be set manually.",, +"""Shipment Document Validation Error(s):\n"" .","""Shipment Document Validation Error(s):\n"" .",, +"Could not save a shipment, see error log for details","Could not save a shipment, see error log for details",, +VAT Request Identifier,VAT Request Identifier,, +VAT Request Date,VAT Request Date,, +Pending Payment,Pending Payment,, +Processing,Processing,, +On Hold,On Hold,, +Complete,Complete,, +Closed,Closed,, +Suspected Fraud,Suspected Fraud,, +Payment Review,Payment Review,, +New,New,, +test message,test message,, +Email has not been sent,Email has not been sent,, +Authorized amount of %1.,Authorized amount of %1.,, +We will authorize %1 after the payment is approved at the payment gateway.,We will authorize %1 after the payment is approved at the payment gateway.,, +Authorized amount of %1. Order is suspended as its authorizing amount %1 is suspected to be fraudulent.,Authorized amount of %1. Order is suspended as its authorizing amount %1 is suspected to be fraudulent.,, +Captured amount of %1 online.,Captured amount of %1 online.,, +Authorized amount of %1,Authorized amount of %1,, +" Transaction ID: ""%1"""," Transaction ID: ""%1""",, +View,View,, +Group was removed,Group was removed,, +"Changing address information will not recalculate shipping, tax or other order amount.","Changing address information will not recalculate shipping, tax or other order amount.",, +Comment Text,Comment Text,, +Notify Customer by Email,Notify Customer by Email,, +Visible on Storefront,Visible on Storefront,, +Customer,Customer,, +Notified,Notified,, +Not Notified,Not Notified,, +No Payment Methods,No Payment Methods,, +Order Comments,Order Comments,, +Apply Coupon Code,Apply Coupon Code,, +Apply,Apply,, +Remove Coupon Code,Remove Coupon Code,, +Remove,Remove,, +Address Information,Address Information,, +Payment &, Shipping Information,Payment &, Shipping Information +Order Total,Order Total,, +Order Currency:,Order Currency:,, +Same As Billing Address,Same As Billing Address,, +Select from existing customer addresses:,Select from existing customer addresses:,, +Add New Address,Add New Address,, +Save in address book,Save in address book,, +You don't need to select a shipping address.,You don't need to select a shipping address.,, +Gift Message for the Entire Order,Gift Message for the Entire Order,, +Leave this box blank if you don't want to leave a gift message for the entire order.,Leave this box blank if you don't want to leave a gift message for the entire order.,, +Row Subtotal,Row Subtotal,, +Action,Action,, +No ordered items,No ordered items,, +Update Items and Quantities,Update Items and Quantities,, +Total %1 product(s),Total %1 product(s),, +Subtotal:,Subtotal:,, +Tier Pricing,Tier Pricing,, +Custom Price,Custom Price,, +Please select,Please select,, +Move to Shopping Cart,Move to Shopping Cart,, +Move to Wish List,Move to Wish List,, +Subscribe to Newsletter,Subscribe to Newsletter,, +Click to change shipping method,Click to change shipping method,, +"Sorry, no quotes are available for this order.","Sorry, no quotes are available for this order.",, +Get shipping methods and rates,Get shipping methods and rates,, +You don't need to select a shipping method.,You don't need to select a shipping method.,, +Customer's Activities,Customer's Activities,, +Refresh,Refresh,, +Item,Item,, +Add To Order,Add To Order,, +Configure and Add to Order,Configure and Add to Order,, +No items,No items,, +Append Comments,Append Comments,, +Email Order Confirmation,Email Order Confirmation,, +Grand Total Excl. Tax,Grand Total Excl. Tax,, +Grand Total Incl. Tax,Grand Total Incl. Tax,, +Subtotal (Excl. Tax),Subtotal (Excl. Tax),, +Subtotal (Incl. Tax),Subtotal (Incl. Tax),, +Payment &, Shipping Method,Payment &, Shipping Method +Payment Information,Payment Information,, +The order was placed using %1.,The order was placed using %1.,, +Shipping Information,Shipping Information,, +Items to Refund,Items to Refund,, +Return to Stock,Return to Stock,, +Qty to Refund,Qty to Refund,, +Tax Amount,Tax Amount,, +Discount Amount,Discount Amount,, +Row Total,Row Total,, +No Items To Refund,No Items To Refund,, +Credit Memo Comments,Credit Memo Comments,, +Refund Totals,Refund Totals,, +Email Copy of Credit Memo,Email Copy of Credit Memo,, +Please enter a positive number in this field.,Please enter a positive number in this field.,, +Items Refunded,Items Refunded,, +No Items,No Items,, +Memo Total,Memo Total,, +Credit Memo History,Credit Memo History,, +Credit Memo Totals,Credit Memo Totals,, +Customer Name: %1,Customer Name: %1,, +Purchased From: %1,Purchased From: %1,, +Gift Message,Gift Message,, +From:,From:,, +To:,To:,, +Message:,Message:,, +Shipping &, Handling,Shipping &, Handling +Gift Options,Gift Options,, +Create Shipment,Create Shipment,, +Invoice and shipment types do not match for some items on this order. You can create a shipment only after creating the invoice.,Invoice and shipment types do not match for some items on this order. You can create a shipment only after creating the invoice.,, +%1,%1,, +Qty to Invoice,Qty to Invoice,, +Invoice History,Invoice History,, +Invoice Comments,Invoice Comments,, +Invoice Totals,Invoice Totals,, +Amount,Amount,, +Capture Online,Capture Online,, +Capture Offline,Capture Offline,, +Not Capture,Not Capture,, +The invoice will be created offline without the payment gateway.,The invoice will be created offline without the payment gateway.,, +Email Copy of Invoice,Email Copy of Invoice,, +Items Invoiced,Items Invoiced,, +Total Tax,Total Tax,, +From Name,From Name,, +To Name,To Name,, +Status,Status,, +Comment,Comment,, +Notification Not Applicable,Notification Not Applicable,, +Order & Account Information,Order & Account Information,, +The order confirmation email was sent,The order confirmation email was sent,, +The order confirmation email is not sent,The order confirmation email is not sent,, +Order Date,Order Date,, +Order Date (%1),Order Date (%1),, +Purchased From,Purchased From,, +Link to the New Order,Link to the New Order,, +Link to the Previous Order,Link to the Previous Order,, +Placed from IP,Placed from IP,, +%1 / %2 rate:,%1 / %2 rate:,, +Customer Name,Customer Name,, +Customer Group,Customer Group,, +Notes for this Order,Notes for this Order,, +Comment added,Comment added,, +Transaction Data,Transaction Data,, +Transaction ID,Transaction ID,, +Parent Transaction ID,Parent Transaction ID,, +Order ID,Order ID,, +Transaction Type,Transaction Type,, +Is Closed,Is Closed,, +Created At,Created At,, +Child Transactions,Child Transactions,, +Transaction Details,Transaction Details,, +Items,Items,, +Gift Message for this Order,Gift Message for this Order,, +Shipped By,Shipped By,, +Tracking Number,Tracking Number,, +Billing Last Name,Billing Last Name,, +Find Order By,Find Order By,, +ZIP Code,ZIP Code,, +Billing ZIP Code,Billing ZIP Code,, +Continue,Continue,, +Print All Refunds,Print All Refunds,, +Refund #,Refund #,, +Print Refund,Print Refund,, +Product Name,Product Name,, +Order #,Order #,, +Date,Date,, +Ship To,Ship To,, +Actions,Actions,, +View Order,View Order,, +You have placed no orders.,You have placed no orders.,, +No shipping information available,No shipping information available,, +Print Order,Print Order,, +Print All Invoices,Print All Invoices,, +Invoice #,Invoice #,, +Print Invoice,Print Invoice,, +Qty Invoiced,Qty Invoiced,, +Close,Close,, +About Your Order,About Your Order,, +"Order Date: %1","Order Date: %1",, +Refund #%1,Refund #%1,, +Shipment #%1,Shipment #%1,, +Qty Shipped,Qty Shipped,, +Recent Orders,Recent Orders,, +View All,View All,, +Gift Message for This Order,Gift Message for This Order,, +Recently Ordered,Recently Ordered,, +Add to Cart,Add to Cart,, +Search,Search,, +Credit memo for your %store_name order,Credit memo for your %store_name order,, +"%name,","%name,",, +Thank you for your order from %store_name.,Thank you for your order from %store_name.,, +"You can check the status of your order by logging into your account.","You can check the status of your order by logging into your account.",, +"If you have questions about your order, you can email us at %store_email","If you have questions about your order, you can email us at %store_email",, +"or call us at %store_phone","or call us at %store_phone",, +"Our hours are %store_hours.","Our hours are %store_hours.",, +Your Credit Memo #%creditmemo_id for Order #%order_id,Your Credit Memo #%creditmemo_id for Order #%order_id,, +Billing Info,Billing Info,, +Shipping Info,Shipping Info,, +Update to your %store_name credit memo,Update to your %store_name credit memo,, +Your order #%increment_id has been updated with a status of %order_status.,Your order #%increment_id has been updated with a status of %order_status.,, +Invoice for your %store_name order,Invoice for your %store_name order,, +Your Invoice #%invoice_id for Order #%order_id,Your Invoice #%invoice_id for Order #%order_id,, +Update to your %store_name invoice,Update to your %store_name invoice,, +Your %store_name order confirmation,Your %store_name order confirmation,, +"%customer_name,","%customer_name,",, +Once your package ships we will send you a tracking number.,Once your package ships we will send you a tracking number.,, +"Your Order #%increment_id","Your Order #%increment_id",, +"Placed on %created_at","Placed on %created_at",, +Once your package ships we will send an email with a link to track your order.,Once your package ships we will send an email with a link to track your order.,, +Update to your %store_name order,Update to your %store_name order,, +Your %store_name order has shipped,Your %store_name order has shipped,, +Your shipping confirmation is below. Thank you again for your business.,Your shipping confirmation is below. Thank you again for your business.,, +Your Shipment #%shipment_id for Order #%order_id,Your Shipment #%shipment_id for Order #%order_id,, +Update to your %store_name shipment,Update to your %store_name shipment,, +Gift Options for ,Gift Options for ,, +Add Products,Add Products,, +You have item changes,You have item changes,, +Ok,Ok,, +Operations,Operations,, +Create,Create,, +Send Order Email,Send Order Email,, +Accept or Deny Payment,Accept or Deny Payment,, +Send Sales Emails,Send Sales Emails,, +Sales Section,Sales Section,, +Sales Emails Section,Sales Emails Section,, +General,General,, +Hide Customer IP,Hide Customer IP,, +"Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos.","Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos.",, +Checkout Totals Sort Order,Checkout Totals Sort Order,, +Allow Reorder,Allow Reorder,, +Invoice and Packing Slip Design,Invoice and Packing Slip Design,, +Logo for PDF Print-outs (200x50),Logo for PDF Print-outs (200x50),, +"Your default logo will be used in PDF and HTML documents.
(jpeg, tiff, png) If your pdf image is distorted, try to use larger file-size image.","Your default logo will be used in PDF and HTML documents.
(jpeg, tiff, png) If your pdf image is distorted, try to use larger file-size image.",, +Logo for HTML Print View,Logo for HTML Print View,, +"Logo for HTML documents only. If empty, default will be used.
(jpeg, gif, png)","Logo for HTML documents only. If empty, default will be used.
(jpeg, gif, png)",, +Address,Address,, +Minimum Order Amount,Minimum Order Amount,, +Enable,Enable,, +Minimum Amount,Minimum Amount,, +Subtotal after discount,Subtotal after discount,, +Include Tax to Amount,Include Tax to Amount,, +Description Message,Description Message,, +This message will be shown in the shopping cart when the subtotal (after discount) is lower than the minimum allowed amount.,This message will be shown in the shopping cart when the subtotal (after discount) is lower than the minimum allowed amount.,, +Error to Show in Shopping Cart,Error to Show in Shopping Cart,, +Validate Each Address Separately in Multi-address Checkout,Validate Each Address Separately in Multi-address Checkout,, +Multi-address Description Message,Multi-address Description Message,, +We'll use the default description above if you leave this empty.,We'll use the default description above if you leave this empty.,, +Multi-address Error to Show in Shopping Cart,Multi-address Error to Show in Shopping Cart,, +We'll use the default error above if you leave this empty.,We'll use the default error above if you leave this empty.,, +Dashboard,Dashboard,, +Use Aggregated Data,Use Aggregated Data,, +Orders Cron Settings,Orders Cron Settings,, +Pending Payment Order Lifetime (minutes),Pending Payment Order Lifetime (minutes),, +Sales Emails,Sales Emails,, +Asynchronous sending,Asynchronous sending,, +Enabled,Enabled,, +New Order Confirmation Email Sender,New Order Confirmation Email Sender,, +New Order Confirmation Template,New Order Confirmation Template,, +"Email template chosen based on theme fallback when ""Default"" option is selected.","Email template chosen based on theme fallback when ""Default"" option is selected.",, +New Order Confirmation Template for Guest,New Order Confirmation Template for Guest,, +Send Order Email Copy To,Send Order Email Copy To,, +Comma-separated,Comma-separated,, +Send Order Email Copy Method,Send Order Email Copy Method,, +Order Comment Email Sender,Order Comment Email Sender,, +Order Comment Email Template,Order Comment Email Template,, +Order Comment Email Template for Guest,Order Comment Email Template for Guest,, +Send Order Comment Email Copy To,Send Order Comment Email Copy To,, +Send Order Comments Email Copy Method,Send Order Comments Email Copy Method,, +Invoice Email Sender,Invoice Email Sender,, +Invoice Email Template,Invoice Email Template,, +Invoice Email Template for Guest,Invoice Email Template for Guest,, +Send Invoice Email Copy To,Send Invoice Email Copy To,, +Send Invoice Email Copy Method,Send Invoice Email Copy Method,, +Invoice Comment Email Sender,Invoice Comment Email Sender,, +Invoice Comment Email Template,Invoice Comment Email Template,, +Invoice Comment Email Template for Guest,Invoice Comment Email Template for Guest,, +Send Invoice Comment Email Copy To,Send Invoice Comment Email Copy To,, +Send Invoice Comments Email Copy Method,Send Invoice Comments Email Copy Method,, +Shipment,Shipment,, +Shipment Email Sender,Shipment Email Sender,, +Shipment Email Template,Shipment Email Template,, +Shipment Email Template for Guest,Shipment Email Template for Guest,, +Send Shipment Email Copy To,Send Shipment Email Copy To,, +Send Shipment Email Copy Method,Send Shipment Email Copy Method,, +Shipment Comments,Shipment Comments,, +Shipment Comment Email Sender,Shipment Comment Email Sender,, +Shipment Comment Email Template,Shipment Comment Email Template,, +Shipment Comment Email Template for Guest,Shipment Comment Email Template for Guest,, +Send Shipment Comment Email Copy To,Send Shipment Comment Email Copy To,, +Send Shipment Comments Email Copy Method,Send Shipment Comments Email Copy Method,, +Credit Memo Email Sender,Credit Memo Email Sender,, +Credit Memo Email Template,Credit Memo Email Template,, +Credit Memo Email Template for Guest,Credit Memo Email Template for Guest,, +Send Credit Memo Email Copy To,Send Credit Memo Email Copy To,, +Send Credit Memo Email Copy Method,Send Credit Memo Email Copy Method,, +Credit Memo Comment Email Sender,Credit Memo Comment Email Sender,, +Credit Memo Comment Email Template,Credit Memo Comment Email Template,, +Credit Memo Comment Email Template for Guest,Credit Memo Comment Email Template for Guest,, +Send Credit Memo Comment Email Copy To,Send Credit Memo Comment Email Copy To,, +Send Credit Memo Comments Email Copy Method,Send Credit Memo Comments Email Copy Method,, +PDF Print-outs,PDF Print-outs,, +Display Order ID in Header,Display Order ID in Header,, +Customer Order Status Notification,Customer Order Status Notification,, +Asynchronous indexing,Asynchronous indexing,, +Orders and Returns Search Form,Orders and Returns Search Form,, +Anchor Custom Title,Anchor Custom Title,, +Template,Template,, +Default Template,Default Template,, +Name,Name,, +Phone,Phone,, +ZIP/Post Code,ZIP/Post Code,, +Signed-up Point,Signed-up Point,, +Website,Website,, +Bill-to Name,Bill-to Name,, +Created,Created,, +Invoice Date,Invoice Date,, +Ship-to Name,Ship-to Name,, +Ship Date,Ship Date,, +Total Quantity,Total Quantity,, +Default Status,Default Status,, +State Code and Title,State Code and Title,, +Item Status,Item Status,, +Original Price,Original Price,, +Tax Percent,Tax Percent,, +All Store Views,All Store Views,, +PDF Credit Memos,PDF Credit Memos,, +Purchase Point,Purchase Point,, +Print Invoices,Print Invoices,, +Print Packing Slips,Print Packing Slips,, +Print Credit Memos,Print Credit Memos,, +Print All,Print All,, +Print Shipping Labels,Print Shipping Labels,, +Purchase Date,Purchase Date,, +Grand Total (Base),Grand Total (Base),, +Grand Total (Purchased),Grand Total (Purchased),, +Customer Email,Customer Email,, +Shipping and Handling,Shipping and Handling,, +PDF Invoices,PDF Invoices,, +PDF Shipments,PDF Shipments,, +PDF Creditmemos,PDF Creditmemos,, +Refunds,Refunds,, +Allow Zero GrandTotal for Creditmemo,Allow Zero GrandTotal for Creditmemo,, +Allow Zero GrandTotal,Allow Zero GrandTotal,, +Email is required field for Admin order creation,Email is required field for Admin order creation,, +If set YES Email field will be required during Admin order creation for new Customer.,If set YES Email field will be required during Admin order creation for new Customer.,, From 339c4c2a507767e8cb3badaac5e278430cf63d23 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Tue, 17 Sep 2019 17:39:08 +0530 Subject: [PATCH 020/369] resolved Sales en_US.csv file conflict --- app/code/Magento/Sales/i18n/en_US.csv | 1602 ++++++++++++------------- 1 file changed, 801 insertions(+), 801 deletions(-) diff --git a/app/code/Magento/Sales/i18n/en_US.csv b/app/code/Magento/Sales/i18n/en_US.csv index fc1fac1311985..84cdfb2824334 100644 --- a/app/code/Magento/Sales/i18n/en_US.csv +++ b/app/code/Magento/Sales/i18n/en_US.csv @@ -1,801 +1,801 @@ -Credit Memos,Credit Memos,, -Orders,Orders,, -Invoices,Invoices,, -We can't get the order instance right now.,We can't get the order instance right now.,, -Create New Order,Create New Order,, -Save Order Address,Save Order Address,, -Shipping,Shipping,, -Billing,Billing,, -Edit Order %1 %2 Address,Edit Order %1 %2 Address,, -Order Address Information,Order Address Information,, -Please correct the parent block for this block.,Please correct the parent block for this block.,, -Submit Comment,Submit Comment,, -Submit Order,Submit Order,, -Are you sure you want to cancel this order?,Are you sure you want to cancel this order?,, -Cancel,Cancel,, -Billing Address,Billing Address,, -Payment Method,Payment Method,, -Order Comment,Order Comment,, -Coupons,Coupons,, -Please select a customer,Please select a customer,, -Create New Customer,Create New Customer,, -Account Information,Account Information,, -From,From,, -To,To,, -Message,Message,, -Edit Order #%1,Edit Order #%1,, -Create New Order for %1 in %2,Create New Order for %1 in %2,, -Create New Order in %1,Create New Order in %1,, -Create New Order for %1,Create New Order for %1,, -Create New Order for New Customer,Create New Order for New Customer,, -Items Ordered,Items Ordered,, -This product is disabled.,This product is disabled.,, -Buy %1 for price %2,Buy %1 for price %2,, -Item ordered qty,Item ordered qty,, -%1 with %2 discount each,%1 with %2 discount each,, -%1 for %2,%1 for %2,, -* - Enter custom price including tax,* - Enter custom price including tax,, -* - Enter custom price excluding tax,* - Enter custom price excluding tax,, -Configure,Configure,, -This product does not have any configurable options,This product does not have any configurable options,, -Newsletter Subscription,Newsletter Subscription,, -Please select products,Please select products,, -Add Selected Product(s) to Order,Add Selected Product(s) to Order,, -ID,ID,, -Product,Product,, -SKU,SKU,, -Price,Price,, -Select,Select,, -Quantity,Quantity,, -Shipping Address,Shipping Address,, -Shipping Method,Shipping Method,, -Update Changes,Update Changes,, -Shopping Cart,Shopping Cart,, -Are you sure you want to delete all items from shopping cart?,Are you sure you want to delete all items from shopping cart?,, -Clear Shopping Cart,Clear Shopping Cart,, -Products in Comparison List,Products in Comparison List,, -Recently Compared Products,Recently Compared Products,, -Recently Viewed Products,Recently Viewed Products,, -Last Ordered Items,Last Ordered Items,, -Recently Viewed,Recently Viewed,, -Wish List,Wish List,, -Please select a store,Please select a store,, -Order Totals,Order Totals,, -Shipping Incl. Tax (%1),Shipping Incl. Tax (%1),, -Shipping Excl. Tax (%1),Shipping Excl. Tax (%1),, -New Credit Memo for Invoice #%1,New Credit Memo for Invoice #%1,, -New Credit Memo for Order #%1,New Credit Memo for Order #%1,, -Refund Shipping (Incl. Tax),Refund Shipping (Incl. Tax),, -Refund Shipping (Excl. Tax),Refund Shipping (Excl. Tax),, -Refund Shipping,Refund Shipping,, -Update Qty's,Update Qty's,, -Refund,Refund,, -Refund Offline,Refund Offline,, -Paid Amount,Paid Amount,, -Refund Amount,Refund Amount,, -Shipping Amount,Shipping Amount,, -Shipping Refund,Shipping Refund,, -Order Grand Total,Order Grand Total,, -Adjustment Refund,Adjustment Refund,, -Adjustment Fee,Adjustment Fee,, -Send Email,Send Email,, -Are you sure you want to send a credit memo email to customer?,Are you sure you want to send a credit memo email to customer?,, -Void,Void,, -Print,Print,, -The credit memo email was sent.,The credit memo email was sent.,, -The credit memo email wasn't sent.,The credit memo email wasn't sent.,, -Credit Memo #%1 | %3 | %2 (%4),Credit Memo #%1 | %3 | %2 (%4),, -Total Refund,Total Refund,, -New Invoice and Shipment for Order #%1,New Invoice and Shipment for Order #%1,, -New Invoice for Order #%1,New Invoice for Order #%1,, -Submit Invoice and Shipment,Submit Invoice and Shipment,, -Submit Invoice,Submit Invoice,, -Are you sure you want to send an invoice email to customer?,Are you sure you want to send an invoice email to customer?,, -Credit Memo,Credit Memo,, -Capture,Capture,, -The invoice email was sent.,The invoice email was sent.,, -The invoice email wasn't sent.,The invoice email wasn't sent.,, -Invoice #%1 | %2 | %4 (%3),Invoice #%1 | %2 | %4 (%3),, -Invalid parent block for this block,Invalid parent block for this block,, -Order Statuses,Order Statuses,, -Create New Status,Create New Status,, -Assign Status to State,Assign Status to State,, -Save Status Assignment,Save Status Assignment,, -Assign Order Status to State,Assign Order Status to State,, -Assignment Information,Assignment Information,, -Order Status,Order Status,, -Order State,Order State,, -Use Order Status As Default,Use Order Status As Default,, -Visible On Storefront,Visible On Storefront,, -Edit Order Status,Edit Order Status,, -Save Status,Save Status,, -New Order Status,New Order Status,, -Order Status Information,Order Status Information,, -Status Code,Status Code,, -Status Label,Status Label,, -Store View Specific Labels,Store View Specific Labels,, -Total Paid,Total Paid,, -Total Refunded,Total Refunded,, -Total Due,Total Due,, -Edit,Edit,, -Are you sure you want to send an order email to customer?,Are you sure you want to send an order email to customer?,, -"This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you want to continue?","This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you want to continue?",, -Are you sure you want to void the payment?,Are you sure you want to void the payment?,, -Hold,Hold,, -hold,hold,, -Unhold,Unhold,, -unhold,unhold,, -Are you sure you want to accept this payment?,Are you sure you want to accept this payment?,, -Accept Payment,Accept Payment,, -Are you sure you want to deny this payment?,Are you sure you want to deny this payment?,, -Deny Payment,Deny Payment,, -Get Payment Update,Get Payment Update,, -Invoice and Ship,Invoice and Ship,, -Invoice,Invoice,, -Ship,Ship,, -Reorder,Reorder,, -Order # %1 %2 | %3,Order # %1 %2 | %3,, -"This order contains (%1) items and therefore cannot be edited through the admin interface. If you wish to continue editing, the (%2) items will be removed, the order will be canceled and a new order will be placed.","This order contains (%1) items and therefore cannot be edited through the admin interface. If you wish to continue editing, the (%2) items will be removed, the order will be canceled and a new order will be placed.",, -Are you sure? This order will be canceled and a new one will be created instead.,Are you sure? This order will be canceled and a new one will be created instead.,, -Save Gift Message,Save Gift Message,, - [deleted], [deleted],, -Order Credit Memos,Order Credit Memos,, -Credit memo #%1 created,Credit memo #%1 created,, -Credit memo #%1 comment added,Credit memo #%1 comment added,, -Shipment #%1 created,Shipment #%1 created,, -Shipment #%1 comment added,Shipment #%1 comment added,, -Invoice #%1 created,Invoice #%1 created,, -Invoice #%1 comment added,Invoice #%1 comment added,, -Tracking number %1 for %2 assigned,Tracking number %1 for %2 assigned,, -Comments History,Comments History,, -Order History,Order History,, -Information,Information,, -Order Information,Order Information,, -Order Invoices,Order Invoices,, -Shipments,Shipments,, -Order Shipments,Order Shipments,, -Transactions,Transactions,, -Order View,Order View,, -Any,Any,, -Specified,Specified,, -Applies to Any of the Specified Order Statuses except canceled orders,Applies to Any of the Specified Order Statuses except canceled orders,, -Cart Price Rule,Cart Price Rule,, -Yes,Yes,, -No,No,, -Show Actual Values,Show Actual Values,, -New Order RSS,New Order RSS,, -Subtotal,Subtotal,, -Shipping & Handling,Shipping & Handling,, -Discount (%1),Discount (%1),, -Discount,Discount,, -Grand Total,Grand Total,, -Back,Back,, -Fetch,Fetch,, -Transaction # %1 | %2,Transaction # %1 | %2,, -N/A,N/A,, -Key,Key,, -Value,Value,, -We found an invalid entity model.,We found an invalid entity model.,, -Order # %1,Order # %1,, -Back to My Orders,Back to My Orders,, -View Another Order,View Another Order,, -About Your Refund,About Your Refund,, -My Orders,My Orders,, -Subscribe to Order Status,Subscribe to Order Status,, -About Your Invoice,About Your Invoice,, -Print Order # %1,Print Order # %1,, -Grand Total to be Charged,Grand Total to be Charged,, -Unassign,Unassign,, -We can't add this item to your shopping cart right now.,We can't add this item to your shopping cart right now.,, -You sent the message.,You sent the message.,, -Sales,Sales,, -Invoice capturing error,Invoice capturing error,, -This order no longer exists.,This order no longer exists.,, -Please enter a comment.,Please enter a comment.,, -We cannot add order history.,We cannot add order history.,, -You updated the order address.,You updated the order address.,, -We can't update the order address right now.,We can't update the order address right now.,, -You have not canceled the item.,You have not canceled the item.,, -You canceled the order.,You canceled the order.,, -"""%1"" coupon code was not applied. Do not apply discount is selected for item(s)","""%1"" coupon code was not applied. Do not apply discount is selected for item(s)",, -"""%1"" coupon code is not valid.","""%1"" coupon code is not valid.",, -The coupon code has been accepted.,The coupon code has been accepted.,, -Quote item id is not received.,Quote item id is not received.,, -Quote item is not loaded.,Quote item is not loaded.,, -New Order,New Order,, -You created the order.,You created the order.,, -Order saving error: %1,Order saving error: %1,, -Cannot add new comment.,Cannot add new comment.,, -The credit memo has been canceled.,The credit memo has been canceled.,, -Credit memo has not been canceled.,Credit memo has not been canceled.,, -New Memo for #%1,New Memo for #%1,, -New Memo,New Memo,, -The credit memo's total must be positive.,The credit memo's total must be positive.,, -Cannot create online refund for Refund to Store Credit.,Cannot create online refund for Refund to Store Credit.,, -You created the credit memo.,You created the credit memo.,, -We can't save the credit memo right now.,We can't save the credit memo right now.,, -We can't update the item's quantity right now.,We can't update the item's quantity right now.,, -View Memo for #%1,View Memo for #%1,, -View Memo,View Memo,, -You voided the credit memo.,You voided the credit memo.,, -We can't void the credit memo.,We can't void the credit memo.,, -The order no longer exists.,The order no longer exists.,, -We can't create credit memo for the order.,We can't create credit memo for the order.,, -Edit Order,Edit Order,, -You sent the order email.,You sent the order email.,, -We can't send the email order right now.,We can't send the email order right now.,, -You have not put the order on hold.,You have not put the order on hold.,, -You put the order on hold.,You put the order on hold.,, -You canceled the invoice.,You canceled the invoice.,, -Invoice canceling error,Invoice canceling error,, -The invoice has been captured.,The invoice has been captured.,, -The order does not allow an invoice to be created.,The order does not allow an invoice to be created.,, -You can't create an invoice without products.,You can't create an invoice without products.,, -New Invoice,New Invoice,, -We can't save the invoice right now.,We can't save the invoice right now.,, -You created the invoice and shipment.,You created the invoice and shipment.,, -The invoice has been created.,The invoice has been created.,, -We can't send the invoice email right now.,We can't send the invoice email right now.,, -We can't send the shipment right now.,We can't send the shipment right now.,, -Cannot update item quantity.,Cannot update item quantity.,, -The invoice has been voided.,The invoice has been voided.,, -Invoice voiding error,Invoice voiding error,, -%1 order(s) cannot be canceled.,%1 order(s) cannot be canceled.,, -You cannot cancel the order(s).,You cannot cancel the order(s).,, -We canceled %1 order(s).,We canceled %1 order(s).,, -%1 order(s) were not put on hold.,%1 order(s) were not put on hold.,, -No order(s) were put on hold.,No order(s) were put on hold.,, -You have put %1 order(s) on hold.,You have put %1 order(s) on hold.,, -%1 order(s) were not released from on hold status.,%1 order(s) were not released from on hold status.,, -No order(s) were released from on hold status.,No order(s) were released from on hold status.,, -%1 order(s) have been released from on hold status.,%1 order(s) have been released from on hold status.,, -There are no printable documents related to selected orders.,There are no printable documents related to selected orders.,, -The payment has been accepted.,The payment has been accepted.,, -The payment has been denied.,The payment has been denied.,, -Transaction has been approved.,Transaction has been approved.,, -Transaction has been voided/declined.,Transaction has been voided/declined.,, -There is no update for the transaction.,There is no update for the transaction.,, -We can't update the payment right now.,We can't update the payment right now.,, -You assigned the order status.,You assigned the order status.,, -Something went wrong while assigning the order status.,Something went wrong while assigning the order status.,, -We can't find this order status.,We can't find this order status.,, -Create New Order Status,Create New Order Status,, -We found another order status with the same order status code.,We found another order status with the same order status code.,, -You saved the order status.,You saved the order status.,, -We can't add the order status right now.,We can't add the order status right now.,, -You have unassigned the order status.,You have unassigned the order status.,, -Something went wrong while unassigning the order.,Something went wrong while unassigning the order.,, -Can't unhold order.,Can't unhold order.,, -You released the order from holding status.,You released the order from holding status.,, -The order was not on hold.,The order was not on hold.,, -Exception occurred during order load,Exception occurred during order load,, -Something went wrong while saving the gift message.,Something went wrong while saving the gift message.,, -You saved the gift card message.,You saved the gift card message.,, -The payment has been voided.,The payment has been voided.,, -We can't void the payment right now.,We can't void the payment right now.,, -Please correct the transaction ID and try again.,Please correct the transaction ID and try again.,, -The transaction details have been updated.,The transaction details have been updated.,, -We can't update the transaction details.,We can't update the transaction details.,, -Orders and Returns,Orders and Returns,, -You entered incorrect data. Please try again.,You entered incorrect data. Please try again.,, -Home,Home,, -Go to Home Page,Go to Home Page,, -We can't find this wish list.,We can't find this wish list.,, -"We could not add a product to cart by the ID ""%1"".","We could not add a product to cart by the ID ""%1"".",, -There is an error in one of the option rows.,There is an error in one of the option rows.,, -Shipping Address: ,Shipping Address: ,, -Billing Address: ,Billing Address: ,, -Please specify order items.,Please specify order items.,, -Please specify a shipping method.,Please specify a shipping method.,, -Please specify a payment method.,Please specify a payment method.,, -This payment method is not available.,This payment method is not available.,, -Validation is failed.,Validation is failed.,, -You did not email your customer. Please check your email settings.,You did not email your customer. Please check your email settings.,, --- Please Select --,-- Please Select --,, -"Path ""%1"" is not part of allowed directory ""%2""","Path ""%1"" is not part of allowed directory ""%2""",, -Identifying Fields required,Identifying Fields required,, -Id required,Id required,, -"""Invoice Document Validation Error(s):\n"" .","""Invoice Document Validation Error(s):\n"" .",, -"Could not save an invoice, see error log for details","Could not save an invoice, see error log for details",, -A hold action is not available.,A hold action is not available.,, -You cannot remove the hold.,You cannot remove the hold.,, -We cannot cancel this order.,We cannot cancel this order.,, -Guest,Guest,, -Please enter the first name.,Please enter the first name.,, -Please enter the last name.,Please enter the last name.,, -Please enter the street.,Please enter the street.,, -Please enter the city.,Please enter the city.,, -Please enter the phone number.,Please enter the phone number.,, -Please enter the company.,Please enter the company.,, -Please enter the fax number.,Please enter the fax number.,, -Please enter the zip/postal code.,Please enter the zip/postal code.,, -Please enter the country.,Please enter the country.,, -Please enter the state/province.,Please enter the state/province.,, -Requested entity doesn't exist,Requested entity doesn't exist,, -Could not delete order address,Could not delete order address,, -Could not save order address,Could not save order address,, -Pending,Pending,, -Refunded,Refunded,, -Canceled,Canceled,, -Unknown State,Unknown State,, -"We found an invalid quantity to refund item ""%1"".","We found an invalid quantity to refund item ""%1"".",, -The creditmemo contains product item that is not part of the original order.,The creditmemo contains product item that is not part of the original order.,, -The quantity to refund must not be greater than the unrefunded quantity.,The quantity to refund must not be greater than the unrefunded quantity.,, -Maximum shipping amount allowed to refund is: %1,Maximum shipping amount allowed to refund is: %1,, -Order Id is required for creditmemo document,Order Id is required for creditmemo document,, -"The creditmemo contains product SKU ""%1"" that is not part of the original order.","The creditmemo contains product SKU ""%1"" that is not part of the original order.",, -"The quantity to creditmemo must not be greater than the unrefunded quantity for product SKU ""%1"".","The quantity to creditmemo must not be greater than the unrefunded quantity for product SKU ""%1"".",, -You can't create a creditmemo without products.,You can't create a creditmemo without products.,, -The most money available to refund is %1.,The most money available to refund is %1.,, -Could not delete credit memo,Could not delete credit memo,, -Could not save credit memo,Could not save credit memo,, -This order already has associated customer account,This order already has associated customer account,, -Paid,Paid,, -We cannot register an existing invoice,We cannot register an existing invoice,, -We can't create creditmemo for the invoice.,We can't create creditmemo for the invoice.,, -Order Id is required for invoice document,Order Id is required for invoice document,, -"The quantity to invoice must not be greater than the uninvoiced quantity for product SKU ""%1"".","The quantity to invoice must not be greater than the uninvoiced quantity for product SKU ""%1"".",, -The invoice contains one or more items that are not part of the original order.,The invoice contains one or more items that are not part of the original order.,, -ID required,ID required,, -Unknown Status,Unknown Status,, -Ordered,Ordered,, -Shipped,Shipped,, -Invoiced,Invoiced,, -Backordered,Backordered,, -Returned,Returned,, -Partial,Partial,, -Mixed,Mixed,, -Registered a Void notification.,Registered a Void notification.,, -"If the invoice was created offline, try creating an offline credit memo.","If the invoice was created offline, try creating an offline credit memo.",, -We refunded %1 online.,We refunded %1 online.,, -We refunded %1 offline.,We refunded %1 offline.,, -"IPN ""Refunded"". Refund issued by merchant. Registered notification about refunded amount of %1. Transaction ID: ""%2"". Credit Memo has not been created. Please create offline Credit Memo.","IPN ""Refunded"". Refund issued by merchant. Registered notification about refunded amount of %1. Transaction ID: ""%2"". Credit Memo has not been created. Please create offline Credit Memo.",, -The credit memo has been created automatically.,The credit memo has been created automatically.,, -Registered notification about refunded amount of %1.,Registered notification about refunded amount of %1.,, -Canceled order online,Canceled order online,, -Canceled order offline,Canceled order offline,, -Approved the payment online.,Approved the payment online.,, -There is no need to approve this payment.,There is no need to approve this payment.,, -Denied the payment online,Denied the payment online,, -Registered update about approved payment.,Registered update about approved payment.,, -Registered update about denied payment.,Registered update about denied payment.,, -There is no update for the payment.,There is no update for the payment.,, -Voided authorization.,Voided authorization.,, -Amount: %1.,Amount: %1.,, -"Transaction ID: ""%1""","Transaction ID: ""%1""",, -The payment method you requested is not available.,The payment method you requested is not available.,, -The payment disallows storing objects.,The payment disallows storing objects.,, -"The transaction ""%1"" cannot be captured yet.","The transaction ""%1"" cannot be captured yet.",, -The order amount of %1 is pending approval on the payment gateway.,The order amount of %1 is pending approval on the payment gateway.,, -Ordered amount of %1,Ordered amount of %1,, -An amount of %1 will be captured after being approved at the payment gateway.,An amount of %1 will be captured after being approved at the payment gateway.,, -Registered notification about captured amount of %1.,Registered notification about captured amount of %1.,, -Order is suspended as its capture amount %1 is suspected to be fraudulent.,Order is suspended as its capture amount %1 is suspected to be fraudulent.,, -The parent transaction ID must have a transaction ID.,The parent transaction ID must have a transaction ID.,, -Payment transactions disallow storing objects.,Payment transactions disallow storing objects.,, -"The transaction ""%1"" (%2) is already closed.","The transaction ""%1"" (%2) is already closed.",, -Set order for existing transactions not allowed,Set order for existing transactions not allowed,, -"At minimum, you need to set a payment ID.","At minimum, you need to set a payment ID.",, -Order,Order,, -Authorization,Authorization,, -"We found an unsupported transaction type ""%1"".","We found an unsupported transaction type ""%1"".",, -Please set a proper payment and order id.,Please set a proper payment and order id.,, -Please enter a Transaction ID.,Please enter a Transaction ID.,, -You can't do this without a transaction object.,You can't do this without a transaction object.,, -Order # ,Order # ,, -Order Date: ,Order Date: ,, -Sold to:,Sold to:,, -Ship to:,Ship to:,, -Payment Method:,Payment Method:,, -Shipping Method:,Shipping Method:,, -Total Shipping Charges,Total Shipping Charges,, -Title,Title,, -Number,Number,, -We found an invalid renderer model.,We found an invalid renderer model.,, -Please define the PDF object before using.,Please define the PDF object before using.,, -"We don't recognize the draw line data. Please define the ""lines"" array.","We don't recognize the draw line data. Please define the ""lines"" array.",, -Products,Products,, -Total (ex),Total (ex),, -Qty,Qty,, -Tax,Tax,, -Total (inc),Total (inc),, -Credit Memo # ,Credit Memo # ,, -Invoice # ,Invoice # ,, -The order object is not specified.,The order object is not specified.,, -The source object is not specified.,The source object is not specified.,, -An item object is not specified.,An item object is not specified.,, -A PDF object is not specified.,A PDF object is not specified.,, -A PDF page object is not specified.,A PDF page object is not specified.,, -Excl. Tax,Excl. Tax,, -Incl. Tax,Incl. Tax,, -Packing Slip # ,Packing Slip # ,, -title,title,, -The PDF total model %1 must be or extend \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal.,The PDF total model %1 must be or extend \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal.,, -We cannot register an existing shipment,We cannot register an existing shipment,, -Parent shipment cannot be loaded for track object.,Parent shipment cannot be loaded for track object.,, -Order Id is required for shipment document,Order Id is required for shipment document,, -You can't create a shipment without products.,You can't create a shipment without products.,, -"The shipment contains product SKU ""%1"" that is not part of the original order.","The shipment contains product SKU ""%1"" that is not part of the original order.",, -"The quantity to ship must not be greater than the unshipped quantity for product SKU ""%1"".","The quantity to ship must not be greater than the unshipped quantity for product SKU ""%1"".",, -Please enter a tracking number.,Please enter a tracking number.,, -Could not delete shipment,Could not delete shipment,, -Could not save shipment,Could not save shipment,, -The last status can't be unassigned from its current state.,The last status can't be unassigned from its current state.,, -"Status can't be unassigned, because it is used by existing order(s).","Status can't be unassigned, because it is used by existing order(s).",, -The total model should be extended from \Magento\Sales\Model\Order\Total\AbstractTotal.,The total model should be extended from \Magento\Sales\Model\Order\Total\AbstractTotal.,, -An invoice cannot be created when an order has a status of %1,An invoice cannot be created when an order has a status of %1,, -A creditmemo can not be created when an order has a status of %1,A creditmemo can not be created when an order has a status of %1,, -The order does not allow a creditmemo to be created.,The order does not allow a creditmemo to be created.,, -A shipment cannot be created when an order has a status of %1,A shipment cannot be created when an order has a status of %1,, -The order does not allow a shipment to be created.,The order does not allow a shipment to be created.,, -"""Creditmemo Document Validation Error(s):\n"" .","""Creditmemo Document Validation Error(s):\n"" .",, -"Could not save a Creditmemo, see error log for details","Could not save a Creditmemo, see error log for details",, -We cannot determine the field name.,We cannot determine the field name.,, -City,City,, -Company,Company,, -Country,Country,, -Email,Email,, -First Name,First Name,, -Last Name,Last Name,, -State/Province,State/Province,, -Street Address,Street Address,, -Phone Number,Phone Number,, -Zip/Postal Code,Zip/Postal Code,, -We can't save the address:\n%1,We can't save the address:\n%1,, -Cannot save comment:\n%1,Cannot save comment:\n%1,, -We don't have enough information to save the parent transaction ID.,We don't have enough information to save the parent transaction ID.,, -We cannot create an empty shipment.,We cannot create an empty shipment.,, -Cannot save track:\n%1,Cannot save track:\n%1,, -Cannot unassign status from state,Cannot unassign status from state,, -New Orders,New Orders,, -Order #%1 created at %2,Order #%1 created at %2,, -Details for %1 #%2,Details for %1 #%2,, -Notified Date: %1,Notified Date: %1,, -Comment: %1
,Comment: %1
,, -Current Status: %1
,Current Status: %1
,, -Total: %1
,Total: %1
,, -Order # %1 Notification(s),Order # %1 Notification(s),, -You can not cancel Credit Memo,You can not cancel Credit Memo,, -Could not cancel creditmemo,Could not cancel creditmemo,, -We cannot register an existing credit memo.,We cannot register an existing credit memo.,, -"We found an invalid quantity to invoice item ""%1"".","We found an invalid quantity to invoice item ""%1"".",, -"The Order State ""%1"" must not be set manually.","The Order State ""%1"" must not be set manually.",, -"""Shipment Document Validation Error(s):\n"" .","""Shipment Document Validation Error(s):\n"" .",, -"Could not save a shipment, see error log for details","Could not save a shipment, see error log for details",, -VAT Request Identifier,VAT Request Identifier,, -VAT Request Date,VAT Request Date,, -Pending Payment,Pending Payment,, -Processing,Processing,, -On Hold,On Hold,, -Complete,Complete,, -Closed,Closed,, -Suspected Fraud,Suspected Fraud,, -Payment Review,Payment Review,, -New,New,, -test message,test message,, -Email has not been sent,Email has not been sent,, -Authorized amount of %1.,Authorized amount of %1.,, -We will authorize %1 after the payment is approved at the payment gateway.,We will authorize %1 after the payment is approved at the payment gateway.,, -Authorized amount of %1. Order is suspended as its authorizing amount %1 is suspected to be fraudulent.,Authorized amount of %1. Order is suspended as its authorizing amount %1 is suspected to be fraudulent.,, -Captured amount of %1 online.,Captured amount of %1 online.,, -Authorized amount of %1,Authorized amount of %1,, -" Transaction ID: ""%1"""," Transaction ID: ""%1""",, -View,View,, -Group was removed,Group was removed,, -"Changing address information will not recalculate shipping, tax or other order amount.","Changing address information will not recalculate shipping, tax or other order amount.",, -Comment Text,Comment Text,, -Notify Customer by Email,Notify Customer by Email,, -Visible on Storefront,Visible on Storefront,, -Customer,Customer,, -Notified,Notified,, -Not Notified,Not Notified,, -No Payment Methods,No Payment Methods,, -Order Comments,Order Comments,, -Apply Coupon Code,Apply Coupon Code,, -Apply,Apply,, -Remove Coupon Code,Remove Coupon Code,, -Remove,Remove,, -Address Information,Address Information,, -Payment &, Shipping Information,Payment &, Shipping Information -Order Total,Order Total,, -Order Currency:,Order Currency:,, -Same As Billing Address,Same As Billing Address,, -Select from existing customer addresses:,Select from existing customer addresses:,, -Add New Address,Add New Address,, -Save in address book,Save in address book,, -You don't need to select a shipping address.,You don't need to select a shipping address.,, -Gift Message for the Entire Order,Gift Message for the Entire Order,, -Leave this box blank if you don't want to leave a gift message for the entire order.,Leave this box blank if you don't want to leave a gift message for the entire order.,, -Row Subtotal,Row Subtotal,, -Action,Action,, -No ordered items,No ordered items,, -Update Items and Quantities,Update Items and Quantities,, -Total %1 product(s),Total %1 product(s),, -Subtotal:,Subtotal:,, -Tier Pricing,Tier Pricing,, -Custom Price,Custom Price,, -Please select,Please select,, -Move to Shopping Cart,Move to Shopping Cart,, -Move to Wish List,Move to Wish List,, -Subscribe to Newsletter,Subscribe to Newsletter,, -Click to change shipping method,Click to change shipping method,, -"Sorry, no quotes are available for this order.","Sorry, no quotes are available for this order.",, -Get shipping methods and rates,Get shipping methods and rates,, -You don't need to select a shipping method.,You don't need to select a shipping method.,, -Customer's Activities,Customer's Activities,, -Refresh,Refresh,, -Item,Item,, -Add To Order,Add To Order,, -Configure and Add to Order,Configure and Add to Order,, -No items,No items,, -Append Comments,Append Comments,, -Email Order Confirmation,Email Order Confirmation,, -Grand Total Excl. Tax,Grand Total Excl. Tax,, -Grand Total Incl. Tax,Grand Total Incl. Tax,, -Subtotal (Excl. Tax),Subtotal (Excl. Tax),, -Subtotal (Incl. Tax),Subtotal (Incl. Tax),, -Payment &, Shipping Method,Payment &, Shipping Method -Payment Information,Payment Information,, -The order was placed using %1.,The order was placed using %1.,, -Shipping Information,Shipping Information,, -Items to Refund,Items to Refund,, -Return to Stock,Return to Stock,, -Qty to Refund,Qty to Refund,, -Tax Amount,Tax Amount,, -Discount Amount,Discount Amount,, -Row Total,Row Total,, -No Items To Refund,No Items To Refund,, -Credit Memo Comments,Credit Memo Comments,, -Refund Totals,Refund Totals,, -Email Copy of Credit Memo,Email Copy of Credit Memo,, -Please enter a positive number in this field.,Please enter a positive number in this field.,, -Items Refunded,Items Refunded,, -No Items,No Items,, -Memo Total,Memo Total,, -Credit Memo History,Credit Memo History,, -Credit Memo Totals,Credit Memo Totals,, -Customer Name: %1,Customer Name: %1,, -Purchased From: %1,Purchased From: %1,, -Gift Message,Gift Message,, -From:,From:,, -To:,To:,, -Message:,Message:,, -Shipping &, Handling,Shipping &, Handling -Gift Options,Gift Options,, -Create Shipment,Create Shipment,, -Invoice and shipment types do not match for some items on this order. You can create a shipment only after creating the invoice.,Invoice and shipment types do not match for some items on this order. You can create a shipment only after creating the invoice.,, -%1,%1,, -Qty to Invoice,Qty to Invoice,, -Invoice History,Invoice History,, -Invoice Comments,Invoice Comments,, -Invoice Totals,Invoice Totals,, -Amount,Amount,, -Capture Online,Capture Online,, -Capture Offline,Capture Offline,, -Not Capture,Not Capture,, -The invoice will be created offline without the payment gateway.,The invoice will be created offline without the payment gateway.,, -Email Copy of Invoice,Email Copy of Invoice,, -Items Invoiced,Items Invoiced,, -Total Tax,Total Tax,, -From Name,From Name,, -To Name,To Name,, -Status,Status,, -Comment,Comment,, -Notification Not Applicable,Notification Not Applicable,, -Order & Account Information,Order & Account Information,, -The order confirmation email was sent,The order confirmation email was sent,, -The order confirmation email is not sent,The order confirmation email is not sent,, -Order Date,Order Date,, -Order Date (%1),Order Date (%1),, -Purchased From,Purchased From,, -Link to the New Order,Link to the New Order,, -Link to the Previous Order,Link to the Previous Order,, -Placed from IP,Placed from IP,, -%1 / %2 rate:,%1 / %2 rate:,, -Customer Name,Customer Name,, -Customer Group,Customer Group,, -Notes for this Order,Notes for this Order,, -Comment added,Comment added,, -Transaction Data,Transaction Data,, -Transaction ID,Transaction ID,, -Parent Transaction ID,Parent Transaction ID,, -Order ID,Order ID,, -Transaction Type,Transaction Type,, -Is Closed,Is Closed,, -Created At,Created At,, -Child Transactions,Child Transactions,, -Transaction Details,Transaction Details,, -Items,Items,, -Gift Message for this Order,Gift Message for this Order,, -Shipped By,Shipped By,, -Tracking Number,Tracking Number,, -Billing Last Name,Billing Last Name,, -Find Order By,Find Order By,, -ZIP Code,ZIP Code,, -Billing ZIP Code,Billing ZIP Code,, -Continue,Continue,, -Print All Refunds,Print All Refunds,, -Refund #,Refund #,, -Print Refund,Print Refund,, -Product Name,Product Name,, -Order #,Order #,, -Date,Date,, -Ship To,Ship To,, -Actions,Actions,, -View Order,View Order,, -You have placed no orders.,You have placed no orders.,, -No shipping information available,No shipping information available,, -Print Order,Print Order,, -Print All Invoices,Print All Invoices,, -Invoice #,Invoice #,, -Print Invoice,Print Invoice,, -Qty Invoiced,Qty Invoiced,, -Close,Close,, -About Your Order,About Your Order,, -"Order Date: %1","Order Date: %1",, -Refund #%1,Refund #%1,, -Shipment #%1,Shipment #%1,, -Qty Shipped,Qty Shipped,, -Recent Orders,Recent Orders,, -View All,View All,, -Gift Message for This Order,Gift Message for This Order,, -Recently Ordered,Recently Ordered,, -Add to Cart,Add to Cart,, -Search,Search,, -Credit memo for your %store_name order,Credit memo for your %store_name order,, -"%name,","%name,",, -Thank you for your order from %store_name.,Thank you for your order from %store_name.,, -"You can check the status of your order by logging into your account.","You can check the status of your order by logging into your account.",, -"If you have questions about your order, you can email us at %store_email","If you have questions about your order, you can email us at %store_email",, -"or call us at %store_phone","or call us at %store_phone",, -"Our hours are %store_hours.","Our hours are %store_hours.",, -Your Credit Memo #%creditmemo_id for Order #%order_id,Your Credit Memo #%creditmemo_id for Order #%order_id,, -Billing Info,Billing Info,, -Shipping Info,Shipping Info,, -Update to your %store_name credit memo,Update to your %store_name credit memo,, -Your order #%increment_id has been updated with a status of %order_status.,Your order #%increment_id has been updated with a status of %order_status.,, -Invoice for your %store_name order,Invoice for your %store_name order,, -Your Invoice #%invoice_id for Order #%order_id,Your Invoice #%invoice_id for Order #%order_id,, -Update to your %store_name invoice,Update to your %store_name invoice,, -Your %store_name order confirmation,Your %store_name order confirmation,, -"%customer_name,","%customer_name,",, -Once your package ships we will send you a tracking number.,Once your package ships we will send you a tracking number.,, -"Your Order #%increment_id","Your Order #%increment_id",, -"Placed on %created_at","Placed on %created_at",, -Once your package ships we will send an email with a link to track your order.,Once your package ships we will send an email with a link to track your order.,, -Update to your %store_name order,Update to your %store_name order,, -Your %store_name order has shipped,Your %store_name order has shipped,, -Your shipping confirmation is below. Thank you again for your business.,Your shipping confirmation is below. Thank you again for your business.,, -Your Shipment #%shipment_id for Order #%order_id,Your Shipment #%shipment_id for Order #%order_id,, -Update to your %store_name shipment,Update to your %store_name shipment,, -Gift Options for ,Gift Options for ,, -Add Products,Add Products,, -You have item changes,You have item changes,, -Ok,Ok,, -Operations,Operations,, -Create,Create,, -Send Order Email,Send Order Email,, -Accept or Deny Payment,Accept or Deny Payment,, -Send Sales Emails,Send Sales Emails,, -Sales Section,Sales Section,, -Sales Emails Section,Sales Emails Section,, -General,General,, -Hide Customer IP,Hide Customer IP,, -"Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos.","Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos.",, -Checkout Totals Sort Order,Checkout Totals Sort Order,, -Allow Reorder,Allow Reorder,, -Invoice and Packing Slip Design,Invoice and Packing Slip Design,, -Logo for PDF Print-outs (200x50),Logo for PDF Print-outs (200x50),, -"Your default logo will be used in PDF and HTML documents.
(jpeg, tiff, png) If your pdf image is distorted, try to use larger file-size image.","Your default logo will be used in PDF and HTML documents.
(jpeg, tiff, png) If your pdf image is distorted, try to use larger file-size image.",, -Logo for HTML Print View,Logo for HTML Print View,, -"Logo for HTML documents only. If empty, default will be used.
(jpeg, gif, png)","Logo for HTML documents only. If empty, default will be used.
(jpeg, gif, png)",, -Address,Address,, -Minimum Order Amount,Minimum Order Amount,, -Enable,Enable,, -Minimum Amount,Minimum Amount,, -Subtotal after discount,Subtotal after discount,, -Include Tax to Amount,Include Tax to Amount,, -Description Message,Description Message,, -This message will be shown in the shopping cart when the subtotal (after discount) is lower than the minimum allowed amount.,This message will be shown in the shopping cart when the subtotal (after discount) is lower than the minimum allowed amount.,, -Error to Show in Shopping Cart,Error to Show in Shopping Cart,, -Validate Each Address Separately in Multi-address Checkout,Validate Each Address Separately in Multi-address Checkout,, -Multi-address Description Message,Multi-address Description Message,, -We'll use the default description above if you leave this empty.,We'll use the default description above if you leave this empty.,, -Multi-address Error to Show in Shopping Cart,Multi-address Error to Show in Shopping Cart,, -We'll use the default error above if you leave this empty.,We'll use the default error above if you leave this empty.,, -Dashboard,Dashboard,, -Use Aggregated Data,Use Aggregated Data,, -Orders Cron Settings,Orders Cron Settings,, -Pending Payment Order Lifetime (minutes),Pending Payment Order Lifetime (minutes),, -Sales Emails,Sales Emails,, -Asynchronous sending,Asynchronous sending,, -Enabled,Enabled,, -New Order Confirmation Email Sender,New Order Confirmation Email Sender,, -New Order Confirmation Template,New Order Confirmation Template,, -"Email template chosen based on theme fallback when ""Default"" option is selected.","Email template chosen based on theme fallback when ""Default"" option is selected.",, -New Order Confirmation Template for Guest,New Order Confirmation Template for Guest,, -Send Order Email Copy To,Send Order Email Copy To,, -Comma-separated,Comma-separated,, -Send Order Email Copy Method,Send Order Email Copy Method,, -Order Comment Email Sender,Order Comment Email Sender,, -Order Comment Email Template,Order Comment Email Template,, -Order Comment Email Template for Guest,Order Comment Email Template for Guest,, -Send Order Comment Email Copy To,Send Order Comment Email Copy To,, -Send Order Comments Email Copy Method,Send Order Comments Email Copy Method,, -Invoice Email Sender,Invoice Email Sender,, -Invoice Email Template,Invoice Email Template,, -Invoice Email Template for Guest,Invoice Email Template for Guest,, -Send Invoice Email Copy To,Send Invoice Email Copy To,, -Send Invoice Email Copy Method,Send Invoice Email Copy Method,, -Invoice Comment Email Sender,Invoice Comment Email Sender,, -Invoice Comment Email Template,Invoice Comment Email Template,, -Invoice Comment Email Template for Guest,Invoice Comment Email Template for Guest,, -Send Invoice Comment Email Copy To,Send Invoice Comment Email Copy To,, -Send Invoice Comments Email Copy Method,Send Invoice Comments Email Copy Method,, -Shipment,Shipment,, -Shipment Email Sender,Shipment Email Sender,, -Shipment Email Template,Shipment Email Template,, -Shipment Email Template for Guest,Shipment Email Template for Guest,, -Send Shipment Email Copy To,Send Shipment Email Copy To,, -Send Shipment Email Copy Method,Send Shipment Email Copy Method,, -Shipment Comments,Shipment Comments,, -Shipment Comment Email Sender,Shipment Comment Email Sender,, -Shipment Comment Email Template,Shipment Comment Email Template,, -Shipment Comment Email Template for Guest,Shipment Comment Email Template for Guest,, -Send Shipment Comment Email Copy To,Send Shipment Comment Email Copy To,, -Send Shipment Comments Email Copy Method,Send Shipment Comments Email Copy Method,, -Credit Memo Email Sender,Credit Memo Email Sender,, -Credit Memo Email Template,Credit Memo Email Template,, -Credit Memo Email Template for Guest,Credit Memo Email Template for Guest,, -Send Credit Memo Email Copy To,Send Credit Memo Email Copy To,, -Send Credit Memo Email Copy Method,Send Credit Memo Email Copy Method,, -Credit Memo Comment Email Sender,Credit Memo Comment Email Sender,, -Credit Memo Comment Email Template,Credit Memo Comment Email Template,, -Credit Memo Comment Email Template for Guest,Credit Memo Comment Email Template for Guest,, -Send Credit Memo Comment Email Copy To,Send Credit Memo Comment Email Copy To,, -Send Credit Memo Comments Email Copy Method,Send Credit Memo Comments Email Copy Method,, -PDF Print-outs,PDF Print-outs,, -Display Order ID in Header,Display Order ID in Header,, -Customer Order Status Notification,Customer Order Status Notification,, -Asynchronous indexing,Asynchronous indexing,, -Orders and Returns Search Form,Orders and Returns Search Form,, -Anchor Custom Title,Anchor Custom Title,, -Template,Template,, -Default Template,Default Template,, -Name,Name,, -Phone,Phone,, -ZIP/Post Code,ZIP/Post Code,, -Signed-up Point,Signed-up Point,, -Website,Website,, -Bill-to Name,Bill-to Name,, -Created,Created,, -Invoice Date,Invoice Date,, -Ship-to Name,Ship-to Name,, -Ship Date,Ship Date,, -Total Quantity,Total Quantity,, -Default Status,Default Status,, -State Code and Title,State Code and Title,, -Item Status,Item Status,, -Original Price,Original Price,, -Tax Percent,Tax Percent,, -All Store Views,All Store Views,, -PDF Credit Memos,PDF Credit Memos,, -Purchase Point,Purchase Point,, -Print Invoices,Print Invoices,, -Print Packing Slips,Print Packing Slips,, -Print Credit Memos,Print Credit Memos,, -Print All,Print All,, -Print Shipping Labels,Print Shipping Labels,, -Purchase Date,Purchase Date,, -Grand Total (Base),Grand Total (Base),, -Grand Total (Purchased),Grand Total (Purchased),, -Customer Email,Customer Email,, -Shipping and Handling,Shipping and Handling,, -PDF Invoices,PDF Invoices,, -PDF Shipments,PDF Shipments,, -PDF Creditmemos,PDF Creditmemos,, -Refunds,Refunds,, -Allow Zero GrandTotal for Creditmemo,Allow Zero GrandTotal for Creditmemo,, -Allow Zero GrandTotal,Allow Zero GrandTotal,, -Email is required field for Admin order creation,Email is required field for Admin order creation,, -If set YES Email field will be required during Admin order creation for new Customer.,If set YES Email field will be required during Admin order creation for new Customer.,, +"Credit Memos","Credit Memos" +Orders,Orders +Invoices,Invoices +"We can't get the order instance right now.","We can't get the order instance right now." +"Create New Order","Create New Order" +"Save Order Address","Save Order Address" +Shipping,Shipping +Billing,Billing +"Edit Order %1 %2 Address","Edit Order %1 %2 Address" +"Order Address Information","Order Address Information" +"Please correct the parent block for this block.","Please correct the parent block for this block." +"Submit Comment","Submit Comment" +"Submit Order","Submit Order" +"Are you sure you want to cancel this order?","Are you sure you want to cancel this order?" +Cancel,Cancel +"Billing Address","Billing Address" +"Payment Method","Payment Method" +"Order Comment","Order Comment" +Coupons,Coupons +"Please select a customer","Please select a customer" +"Create New Customer","Create New Customer" +"Account Information","Account Information" +From,From +To,To +Message,Message +"Edit Order #%1","Edit Order #%1" +"Create New Order for %1 in %2","Create New Order for %1 in %2" +"Create New Order in %1","Create New Order in %1" +"Create New Order for %1","Create New Order for %1" +"Create New Order for New Customer","Create New Order for New Customer" +"Items Ordered","Items Ordered" +"This product is disabled.","This product is disabled." +"Buy %1 for price %2","Buy %1 for price %2" +"Item ordered qty","Item ordered qty" +"%1 with %2 discount each","%1 with %2 discount each" +"%1 for %2","%1 for %2" +"* - Enter custom price including tax","* - Enter custom price including tax" +"* - Enter custom price excluding tax","* - Enter custom price excluding tax" +Configure,Configure +"This product does not have any configurable options","This product does not have any configurable options" +"Newsletter Subscription","Newsletter Subscription" +"Please select products","Please select products" +"Add Selected Product(s) to Order","Add Selected Product(s) to Order" +ID,ID +Product,Product +SKU,SKU +Price,Price +Select,Select +Quantity,Quantity +"Shipping Address","Shipping Address" +"Shipping Method","Shipping Method" +"Update Changes","Update Changes" +"Shopping Cart","Shopping Cart" +"Are you sure you want to delete all items from shopping cart?","Are you sure you want to delete all items from shopping cart?" +"Clear Shopping Cart","Clear Shopping Cart" +"Products in Comparison List","Products in Comparison List" +"Recently Compared Products","Recently Compared Products" +"Recently Viewed Products","Recently Viewed Products" +"Last Ordered Items","Last Ordered Items" +"Recently Viewed","Recently Viewed" +"Wish List","Wish List" +"Please select a store","Please select a store" +"Order Totals","Order Totals" +"Shipping Incl. Tax (%1)","Shipping Incl. Tax (%1)" +"Shipping Excl. Tax (%1)","Shipping Excl. Tax (%1)" +"New Credit Memo for Invoice #%1","New Credit Memo for Invoice #%1" +"New Credit Memo for Order #%1","New Credit Memo for Order #%1" +"Refund Shipping (Incl. Tax)","Refund Shipping (Incl. Tax)" +"Refund Shipping (Excl. Tax)","Refund Shipping (Excl. Tax)" +"Refund Shipping","Refund Shipping" +"Update Qty's","Update Qty's" +Refund,Refund +"Refund Offline","Refund Offline" +"Paid Amount","Paid Amount" +"Refund Amount","Refund Amount" +"Shipping Amount","Shipping Amount" +"Shipping Refund","Shipping Refund" +"Order Grand Total","Order Grand Total" +"Adjustment Refund","Adjustment Refund" +"Adjustment Fee","Adjustment Fee" +"Send Email","Send Email" +"Are you sure you want to send a credit memo email to customer?","Are you sure you want to send a credit memo email to customer?" +Void,Void +Print,Print +"The credit memo email was sent.","The credit memo email was sent." +"The credit memo email wasn't sent.","The credit memo email wasn't sent." +"Credit Memo #%1 | %3 | %2 (%4)","Credit Memo #%1 | %3 | %2 (%4)" +"Total Refund","Total Refund" +"New Invoice and Shipment for Order #%1","New Invoice and Shipment for Order #%1" +"New Invoice for Order #%1","New Invoice for Order #%1" +"Submit Invoice and Shipment","Submit Invoice and Shipment" +"Submit Invoice","Submit Invoice" +"Are you sure you want to send an invoice email to customer?","Are you sure you want to send an invoice email to customer?" +"Credit Memo","Credit Memo" +Capture,Capture +"The invoice email was sent.","The invoice email was sent." +"The invoice email wasn't sent.","The invoice email wasn't sent." +"Invoice #%1 | %2 | %4 (%3)","Invoice #%1 | %2 | %4 (%3)" +"Invalid parent block for this block","Invalid parent block for this block" +"Order Statuses","Order Statuses" +"Create New Status","Create New Status" +"Assign Status to State","Assign Status to State" +"Save Status Assignment","Save Status Assignment" +"Assign Order Status to State","Assign Order Status to State" +"Assignment Information","Assignment Information" +"Order Status","Order Status" +"Order State","Order State" +"Use Order Status As Default","Use Order Status As Default" +"Visible On Storefront","Visible On Storefront" +"Edit Order Status","Edit Order Status" +"Save Status","Save Status" +"New Order Status","New Order Status" +"Order Status Information","Order Status Information" +"Status Code","Status Code" +"Status Label","Status Label" +"Store View Specific Labels","Store View Specific Labels" +"Total Paid","Total Paid" +"Total Refunded","Total Refunded" +"Total Due","Total Due" +Edit,Edit +"Are you sure you want to send an order email to customer?","Are you sure you want to send an order email to customer?" +"This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you want to continue?","This will create an offline refund. To create an online refund, open an invoice and create credit memo for it. Do you want to continue?" +"Are you sure you want to void the payment?","Are you sure you want to void the payment?" +Hold,Hold +hold,hold +Unhold,Unhold +unhold,unhold +"Are you sure you want to accept this payment?","Are you sure you want to accept this payment?" +"Accept Payment","Accept Payment" +"Are you sure you want to deny this payment?","Are you sure you want to deny this payment?" +"Deny Payment","Deny Payment" +"Get Payment Update","Get Payment Update" +"Invoice and Ship","Invoice and Ship" +Invoice,Invoice +Ship,Ship +Reorder,Reorder +"Order # %1 %2 | %3","Order # %1 %2 | %3" +"This order contains (%1) items and therefore cannot be edited through the admin interface. If you wish to continue editing, the (%2) items will be removed, the order will be canceled and a new order will be placed.","This order contains (%1) items and therefore cannot be edited through the admin interface. If you wish to continue editing, the (%2) items will be removed, the order will be canceled and a new order will be placed." +"Are you sure? This order will be canceled and a new one will be created instead.","Are you sure? This order will be canceled and a new one will be created instead." +"Save Gift Message","Save Gift Message" +" [deleted]"," [deleted]" +"Order Credit Memos","Order Credit Memos" +"Credit memo #%1 created","Credit memo #%1 created" +"Credit memo #%1 comment added","Credit memo #%1 comment added" +"Shipment #%1 created","Shipment #%1 created" +"Shipment #%1 comment added","Shipment #%1 comment added" +"Invoice #%1 created","Invoice #%1 created" +"Invoice #%1 comment added","Invoice #%1 comment added" +"Tracking number %1 for %2 assigned","Tracking number %1 for %2 assigned" +"Comments History","Comments History" +"Order History","Order History" +Information,Information +"Order Information","Order Information" +"Order Invoices","Order Invoices" +Shipments,Shipments +"Order Shipments","Order Shipments" +Transactions,Transactions +"Order View","Order View" +Any,Any +Specified,Specified +"Applies to Any of the Specified Order Statuses except canceled orders","Applies to Any of the Specified Order Statuses except canceled orders" +"Cart Price Rule","Cart Price Rule" +Yes,Yes +No,No +"Show Actual Values","Show Actual Values" +"New Order RSS","New Order RSS" +Subtotal,Subtotal +"Shipping & Handling","Shipping & Handling" +"Discount (%1)","Discount (%1)" +Discount,Discount +"Grand Total","Grand Total" +Back,Back +Fetch,Fetch +"Transaction # %1 | %2","Transaction # %1 | %2" +N/A,N/A +Key,Key +Value,Value +"We found an invalid entity model.","We found an invalid entity model." +"Order # %1","Order # %1" +"Back to My Orders","Back to My Orders" +"View Another Order","View Another Order" +"About Your Refund","About Your Refund" +"My Orders","My Orders" +"Subscribe to Order Status","Subscribe to Order Status" +"About Your Invoice","About Your Invoice" +"Print Order # %1","Print Order # %1" +"Grand Total to be Charged","Grand Total to be Charged" +Unassign,Unassign +"We can't add this item to your shopping cart right now.","We can't add this item to your shopping cart right now." +"You sent the message.","You sent the message." +Sales,Sales +"Invoice capturing error","Invoice capturing error" +"This order no longer exists.","This order no longer exists." +"Please enter a comment.","Please enter a comment." +"We cannot add order history.","We cannot add order history." +"You updated the order address.","You updated the order address." +"We can't update the order address right now.","We can't update the order address right now." +"You have not canceled the item.","You have not canceled the item." +"You canceled the order.","You canceled the order." +"""%1"" coupon code was not applied. Do not apply discount is selected for item(s)","""%1"" coupon code was not applied. Do not apply discount is selected for item(s)" +"""%1"" coupon code is not valid.","""%1"" coupon code is not valid." +"The coupon code has been accepted.","The coupon code has been accepted." +"Quote item id is not received.","Quote item id is not received." +"Quote item is not loaded.","Quote item is not loaded." +"New Order","New Order" +"You created the order.","You created the order." +"Order saving error: %1","Order saving error: %1" +"Cannot add new comment.","Cannot add new comment." +"The credit memo has been canceled.","The credit memo has been canceled." +"Credit memo has not been canceled.","Credit memo has not been canceled." +"New Memo for #%1","New Memo for #%1" +"New Memo","New Memo" +"The credit memo's total must be positive.","The credit memo's total must be positive." +"Cannot create online refund for Refund to Store Credit.","Cannot create online refund for Refund to Store Credit." +"You created the credit memo.","You created the credit memo." +"We can't save the credit memo right now.","We can't save the credit memo right now." +"We can't update the item's quantity right now.","We can't update the item's quantity right now." +"View Memo for #%1","View Memo for #%1" +"View Memo","View Memo" +"You voided the credit memo.","You voided the credit memo." +"We can't void the credit memo.","We can't void the credit memo." +"The order no longer exists.","The order no longer exists." +"We can't create credit memo for the order.","We can't create credit memo for the order." +"Edit Order","Edit Order" +"You sent the order email.","You sent the order email." +"We can't send the email order right now.","We can't send the email order right now." +"You have not put the order on hold.","You have not put the order on hold." +"You put the order on hold.","You put the order on hold." +"You canceled the invoice.","You canceled the invoice." +"Invoice canceling error","Invoice canceling error" +"The invoice has been captured.","The invoice has been captured." +"The order does not allow an invoice to be created.","The order does not allow an invoice to be created." +"You can't create an invoice without products.","You can't create an invoice without products." +"New Invoice","New Invoice" +"We can't save the invoice right now.","We can't save the invoice right now." +"You created the invoice and shipment.","You created the invoice and shipment." +"The invoice has been created.","The invoice has been created." +"We can't send the invoice email right now.","We can't send the invoice email right now." +"We can't send the shipment right now.","We can't send the shipment right now." +"Cannot update item quantity.","Cannot update item quantity." +"The invoice has been voided.","The invoice has been voided." +"Invoice voiding error","Invoice voiding error" +"%1 order(s) cannot be canceled.","%1 order(s) cannot be canceled." +"You cannot cancel the order(s).","You cannot cancel the order(s)." +"We canceled %1 order(s).","We canceled %1 order(s)." +"%1 order(s) were not put on hold.","%1 order(s) were not put on hold." +"No order(s) were put on hold.","No order(s) were put on hold." +"You have put %1 order(s) on hold.","You have put %1 order(s) on hold." +"%1 order(s) were not released from on hold status.","%1 order(s) were not released from on hold status." +"No order(s) were released from on hold status.","No order(s) were released from on hold status." +"%1 order(s) have been released from on hold status.","%1 order(s) have been released from on hold status." +"There are no printable documents related to selected orders.","There are no printable documents related to selected orders." +"The payment has been accepted.","The payment has been accepted." +"The payment has been denied.","The payment has been denied." +"Transaction has been approved.","Transaction has been approved." +"Transaction has been voided/declined.","Transaction has been voided/declined." +"There is no update for the transaction.","There is no update for the transaction." +"We can't update the payment right now.","We can't update the payment right now." +"You assigned the order status.","You assigned the order status." +"Something went wrong while assigning the order status.","Something went wrong while assigning the order status." +"We can't find this order status.","We can't find this order status." +"Create New Order Status","Create New Order Status" +"We found another order status with the same order status code.","We found another order status with the same order status code." +"You saved the order status.","You saved the order status." +"We can't add the order status right now.","We can't add the order status right now." +"You have unassigned the order status.","You have unassigned the order status." +"Something went wrong while unassigning the order.","Something went wrong while unassigning the order." +"Can't unhold order.","Can't unhold order." +"You released the order from holding status.","You released the order from holding status." +"The order was not on hold.","The order was not on hold." +"Exception occurred during order load","Exception occurred during order load" +"Something went wrong while saving the gift message.","Something went wrong while saving the gift message." +"You saved the gift card message.","You saved the gift card message." +"The payment has been voided.","The payment has been voided." +"We can't void the payment right now.","We can't void the payment right now." +"Please correct the transaction ID and try again.","Please correct the transaction ID and try again." +"The transaction details have been updated.","The transaction details have been updated." +"We can't update the transaction details.","We can't update the transaction details." +"Orders and Returns","Orders and Returns" +"You entered incorrect data. Please try again.","You entered incorrect data. Please try again." +Home,Home +"Go to Home Page","Go to Home Page" +"We can't find this wish list.","We can't find this wish list." +"We could not add a product to cart by the ID ""%1"".","We could not add a product to cart by the ID ""%1""." +"There is an error in one of the option rows.","There is an error in one of the option rows." +"Shipping Address: ","Shipping Address: " +"Billing Address: ","Billing Address: " +"Please specify order items.","Please specify order items." +"Please specify a shipping method.","Please specify a shipping method." +"Please specify a payment method.","Please specify a payment method." +"This payment method is not available.","This payment method is not available." +"Validation is failed.","Validation is failed." +"You did not email your customer. Please check your email settings.","You did not email your customer. Please check your email settings." +"-- Please Select --","-- Please Select --" +"Path ""%1"" is not part of allowed directory ""%2""","Path ""%1"" is not part of allowed directory ""%2""" +"Identifying Fields required","Identifying Fields required" +"Id required","Id required" +"""Invoice Document Validation Error(s):\n"" .","""Invoice Document Validation Error(s):\n"" ." +"Could not save an invoice, see error log for details","Could not save an invoice, see error log for details" +"A hold action is not available.","A hold action is not available." +"You cannot remove the hold.","You cannot remove the hold." +"We cannot cancel this order.","We cannot cancel this order." +Guest,Guest +"Please enter the first name.","Please enter the first name." +"Please enter the last name.","Please enter the last name." +"Please enter the street.","Please enter the street." +"Please enter the city.","Please enter the city." +"Please enter the phone number.","Please enter the phone number." +"Please enter the company.","Please enter the company." +"Please enter the fax number.","Please enter the fax number." +"Please enter the zip/postal code.","Please enter the zip/postal code." +"Please enter the country.","Please enter the country." +"Please enter the state/province.","Please enter the state/province." +"Requested entity doesn't exist","Requested entity doesn't exist" +"Could not delete order address","Could not delete order address" +"Could not save order address","Could not save order address" +Pending,Pending +Refunded,Refunded +Canceled,Canceled +"Unknown State","Unknown State" +"We found an invalid quantity to refund item ""%1"".","We found an invalid quantity to refund item ""%1""." +"The creditmemo contains product item that is not part of the original order.","The creditmemo contains product item that is not part of the original order." +"The quantity to refund must not be greater than the unrefunded quantity.","The quantity to refund must not be greater than the unrefunded quantity." +"Maximum shipping amount allowed to refund is: %1","Maximum shipping amount allowed to refund is: %1" +"Order Id is required for creditmemo document","Order Id is required for creditmemo document" +"The creditmemo contains product SKU ""%1"" that is not part of the original order.","The creditmemo contains product SKU ""%1"" that is not part of the original order." +"The quantity to creditmemo must not be greater than the unrefunded quantity for product SKU ""%1"".","The quantity to creditmemo must not be greater than the unrefunded quantity for product SKU ""%1""." +"You can't create a creditmemo without products.","You can't create a creditmemo without products." +"The most money available to refund is %1.","The most money available to refund is %1." +"Could not delete credit memo","Could not delete credit memo" +"Could not save credit memo","Could not save credit memo" +"This order already has associated customer account","This order already has associated customer account" +Paid,Paid +"We cannot register an existing invoice","We cannot register an existing invoice" +"We can't create creditmemo for the invoice.","We can't create creditmemo for the invoice." +"Order Id is required for invoice document","Order Id is required for invoice document" +"The quantity to invoice must not be greater than the uninvoiced quantity for product SKU ""%1"".","The quantity to invoice must not be greater than the uninvoiced quantity for product SKU ""%1""." +"The invoice contains one or more items that are not part of the original order.","The invoice contains one or more items that are not part of the original order." +"ID required","ID required" +"Unknown Status","Unknown Status" +Ordered,Ordered +Shipped,Shipped +Invoiced,Invoiced +Backordered,Backordered +Returned,Returned +Partial,Partial +Mixed,Mixed +"Registered a Void notification.","Registered a Void notification." +"If the invoice was created offline, try creating an offline credit memo.","If the invoice was created offline, try creating an offline credit memo." +"We refunded %1 online.","We refunded %1 online." +"We refunded %1 offline.","We refunded %1 offline." +"IPN ""Refunded"". Refund issued by merchant. Registered notification about refunded amount of %1. Transaction ID: ""%2"". Credit Memo has not been created. Please create offline Credit Memo.","IPN ""Refunded"". Refund issued by merchant. Registered notification about refunded amount of %1. Transaction ID: ""%2"". Credit Memo has not been created. Please create offline Credit Memo." +"The credit memo has been created automatically.","The credit memo has been created automatically." +"Registered notification about refunded amount of %1.","Registered notification about refunded amount of %1." +"Canceled order online","Canceled order online" +"Canceled order offline","Canceled order offline" +"Approved the payment online.","Approved the payment online." +"There is no need to approve this payment.","There is no need to approve this payment." +"Denied the payment online","Denied the payment online" +"Registered update about approved payment.","Registered update about approved payment." +"Registered update about denied payment.","Registered update about denied payment." +"There is no update for the payment.","There is no update for the payment." +"Voided authorization.","Voided authorization." +"Amount: %1.","Amount: %1." +"Transaction ID: ""%1""","Transaction ID: ""%1""" +"The payment method you requested is not available.","The payment method you requested is not available." +"The payment disallows storing objects.","The payment disallows storing objects." +"The transaction ""%1"" cannot be captured yet.","The transaction ""%1"" cannot be captured yet." +"The order amount of %1 is pending approval on the payment gateway.","The order amount of %1 is pending approval on the payment gateway." +"Ordered amount of %1","Ordered amount of %1" +"An amount of %1 will be captured after being approved at the payment gateway.","An amount of %1 will be captured after being approved at the payment gateway." +"Registered notification about captured amount of %1.","Registered notification about captured amount of %1." +"Order is suspended as its capture amount %1 is suspected to be fraudulent.","Order is suspended as its capture amount %1 is suspected to be fraudulent." +"The parent transaction ID must have a transaction ID.","The parent transaction ID must have a transaction ID." +"Payment transactions disallow storing objects.","Payment transactions disallow storing objects." +"The transaction ""%1"" (%2) is already closed.","The transaction ""%1"" (%2) is already closed." +"Set order for existing transactions not allowed","Set order for existing transactions not allowed" +"At minimum, you need to set a payment ID.","At minimum, you need to set a payment ID." +Order,Order +Authorization,Authorization +"We found an unsupported transaction type ""%1"".","We found an unsupported transaction type ""%1""." +"Please set a proper payment and order id.","Please set a proper payment and order id." +"Please enter a Transaction ID.","Please enter a Transaction ID." +"You can't do this without a transaction object.","You can't do this without a transaction object." +"Order # ","Order # " +"Order Date: ","Order Date: " +"Sold to:","Sold to:" +"Ship to:","Ship to:" +"Payment Method:","Payment Method:" +"Shipping Method:","Shipping Method:" +"Total Shipping Charges","Total Shipping Charges" +Title,Title +Number,Number +"We found an invalid renderer model.","We found an invalid renderer model." +"Please define the PDF object before using.","Please define the PDF object before using." +"We don't recognize the draw line data. Please define the ""lines"" array.","We don't recognize the draw line data. Please define the ""lines"" array." +Products,Products +"Total (ex)","Total (ex)" +Qty,Qty +Tax,Tax +"Total (inc)","Total (inc)" +"Credit Memo # ","Credit Memo # " +"Invoice # ","Invoice # " +"The order object is not specified.","The order object is not specified." +"The source object is not specified.","The source object is not specified." +"An item object is not specified.","An item object is not specified." +"A PDF object is not specified.","A PDF object is not specified." +"A PDF page object is not specified.","A PDF page object is not specified." +"Excl. Tax","Excl. Tax" +"Incl. Tax","Incl. Tax" +"Packing Slip # ","Packing Slip # " +title,title +"The PDF total model %1 must be or extend \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal.","The PDF total model %1 must be or extend \Magento\Sales\Model\Order\Pdf\Total\DefaultTotal." +"We cannot register an existing shipment","We cannot register an existing shipment" +"Parent shipment cannot be loaded for track object.","Parent shipment cannot be loaded for track object." +"Order Id is required for shipment document","Order Id is required for shipment document" +"You can't create a shipment without products.","You can't create a shipment without products." +"The shipment contains product SKU ""%1"" that is not part of the original order.","The shipment contains product SKU ""%1"" that is not part of the original order." +"The quantity to ship must not be greater than the unshipped quantity for product SKU ""%1"".","The quantity to ship must not be greater than the unshipped quantity for product SKU ""%1""." +"Please enter a tracking number.","Please enter a tracking number." +"Could not delete shipment","Could not delete shipment" +"Could not save shipment","Could not save shipment" +"The last status can't be unassigned from its current state.","The last status can't be unassigned from its current state." +"Status can't be unassigned, because it is used by existing order(s).","Status can't be unassigned, because it is used by existing order(s)." +"The total model should be extended from \Magento\Sales\Model\Order\Total\AbstractTotal.","The total model should be extended from \Magento\Sales\Model\Order\Total\AbstractTotal." +"An invoice cannot be created when an order has a status of %1","An invoice cannot be created when an order has a status of %1" +"A creditmemo can not be created when an order has a status of %1","A creditmemo can not be created when an order has a status of %1" +"The order does not allow a creditmemo to be created.","The order does not allow a creditmemo to be created." +"A shipment cannot be created when an order has a status of %1","A shipment cannot be created when an order has a status of %1" +"The order does not allow a shipment to be created.","The order does not allow a shipment to be created." +"""Creditmemo Document Validation Error(s):\n"" .","""Creditmemo Document Validation Error(s):\n"" ." +"Could not save a Creditmemo, see error log for details","Could not save a Creditmemo, see error log for details" +"We cannot determine the field name.","We cannot determine the field name." +City,City +Company,Company +Country,Country +Email,Email +"First Name","First Name" +"Last Name","Last Name" +State/Province,State/Province +"Street Address","Street Address" +"Phone Number","Phone Number" +"Zip/Postal Code","Zip/Postal Code" +"We can't save the address:\n%1","We can't save the address:\n%1" +"Cannot save comment:\n%1","Cannot save comment:\n%1" +"We don't have enough information to save the parent transaction ID.","We don't have enough information to save the parent transaction ID." +"We cannot create an empty shipment.","We cannot create an empty shipment." +"Cannot save track:\n%1","Cannot save track:\n%1" +"Cannot unassign status from state","Cannot unassign status from state" +"New Orders","New Orders" +"Order #%1 created at %2","Order #%1 created at %2" +"Details for %1 #%2","Details for %1 #%2" +"Notified Date: %1","Notified Date: %1" +"Comment: %1
","Comment: %1
" +"Current Status: %1
","Current Status: %1
" +"Total: %1
","Total: %1
" +"Order # %1 Notification(s)","Order # %1 Notification(s)" +"You can not cancel Credit Memo","You can not cancel Credit Memo" +"Could not cancel creditmemo","Could not cancel creditmemo" +"We cannot register an existing credit memo.","We cannot register an existing credit memo." +"We found an invalid quantity to invoice item ""%1"".","We found an invalid quantity to invoice item ""%1""." +"The Order State ""%1"" must not be set manually.","The Order State ""%1"" must not be set manually." +"""Shipment Document Validation Error(s):\n"" .","""Shipment Document Validation Error(s):\n"" ." +"Could not save a shipment, see error log for details","Could not save a shipment, see error log for details" +"VAT Request Identifier","VAT Request Identifier" +"VAT Request Date","VAT Request Date" +"Pending Payment","Pending Payment" +Processing,Processing +"On Hold","On Hold" +Complete,Complete +Closed,Closed +"Suspected Fraud","Suspected Fraud" +"Payment Review","Payment Review" +New,New +"test message","test message" +"Email has not been sent","Email has not been sent" +"Authorized amount of %1.","Authorized amount of %1." +"We will authorize %1 after the payment is approved at the payment gateway.","We will authorize %1 after the payment is approved at the payment gateway." +"Authorized amount of %1. Order is suspended as its authorizing amount %1 is suspected to be fraudulent.","Authorized amount of %1. Order is suspended as its authorizing amount %1 is suspected to be fraudulent." +"Captured amount of %1 online.","Captured amount of %1 online." +"Authorized amount of %1","Authorized amount of %1" +" Transaction ID: ""%1"""," Transaction ID: ""%1""" +View,View +"Group was removed","Group was removed" +"Changing address information will not recalculate shipping, tax or other order amount.","Changing address information will not recalculate shipping, tax or other order amount." +"Comment Text","Comment Text" +"Notify Customer by Email","Notify Customer by Email" +"Visible on Storefront","Visible on Storefront" +Customer,Customer +Notified,Notified +"Not Notified","Not Notified" +"No Payment Methods","No Payment Methods" +"Order Comments","Order Comments" +"Apply Coupon Code","Apply Coupon Code" +Apply,Apply +"Remove Coupon Code","Remove Coupon Code" +Remove,Remove +"Address Information","Address Information" +"Payment & Shipping Information","Payment & Shipping Information" +"Order Total","Order Total" +"Order Currency:","Order Currency:" +"Same As Billing Address","Same As Billing Address" +"Select from existing customer addresses:","Select from existing customer addresses:" +"Add New Address","Add New Address" +"Save in address book","Save in address book" +"You don't need to select a shipping address.","You don't need to select a shipping address." +"Gift Message for the Entire Order","Gift Message for the Entire Order" +"Leave this box blank if you don't want to leave a gift message for the entire order.","Leave this box blank if you don't want to leave a gift message for the entire order." +"Row Subtotal","Row Subtotal" +Action,Action +"No ordered items","No ordered items" +"Update Items and Quantities","Update Items and Quantities" +"Total %1 product(s)","Total %1 product(s)" +Subtotal:,Subtotal: +"Tier Pricing","Tier Pricing" +"Custom Price","Custom Price" +"Please select","Please select" +"Move to Shopping Cart","Move to Shopping Cart" +"Move to Wish List","Move to Wish List" +"Subscribe to Newsletter","Subscribe to Newsletter" +"Click to change shipping method","Click to change shipping method" +"Sorry, no quotes are available for this order.","Sorry, no quotes are available for this order." +"Get shipping methods and rates","Get shipping methods and rates" +"You don't need to select a shipping method.","You don't need to select a shipping method." +"Customer's Activities","Customer's Activities" +Refresh,Refresh +Item,Item +"Add To Order","Add To Order" +"Configure and Add to Order","Configure and Add to Order" +"No items","No items" +"Append Comments","Append Comments" +"Email Order Confirmation","Email Order Confirmation" +"Grand Total Excl. Tax","Grand Total Excl. Tax" +"Grand Total Incl. Tax","Grand Total Incl. Tax" +"Subtotal (Excl. Tax)","Subtotal (Excl. Tax)" +"Subtotal (Incl. Tax)","Subtotal (Incl. Tax)" +"Payment & Shipping Method","Payment & Shipping Method" +"Payment Information","Payment Information" +"The order was placed using %1.","The order was placed using %1." +"Shipping Information","Shipping Information" +"Items to Refund","Items to Refund" +"Return to Stock","Return to Stock" +"Qty to Refund","Qty to Refund" +"Tax Amount","Tax Amount" +"Discount Amount","Discount Amount" +"Row Total","Row Total" +"No Items To Refund","No Items To Refund" +"Credit Memo Comments","Credit Memo Comments" +"Refund Totals","Refund Totals" +"Email Copy of Credit Memo","Email Copy of Credit Memo" +"Please enter a positive number in this field.","Please enter a positive number in this field." +"Items Refunded","Items Refunded" +"No Items","No Items" +"Memo Total","Memo Total" +"Credit Memo History","Credit Memo History" +"Credit Memo Totals","Credit Memo Totals" +"Customer Name: %1","Customer Name: %1" +"Purchased From: %1","Purchased From: %1" +"Gift Message","Gift Message" +From:,From: +To:,To: +Message:,Message: +"Shipping & Handling","Shipping & Handling" +"Gift Options","Gift Options" +"Create Shipment","Create Shipment" +"Invoice and shipment types do not match for some items on this order. You can create a shipment only after creating the invoice.","Invoice and shipment types do not match for some items on this order. You can create a shipment only after creating the invoice." +%1,%1 +"Qty to Invoice","Qty to Invoice" +"Invoice History","Invoice History" +"Invoice Comments","Invoice Comments" +"Invoice Totals","Invoice Totals" +Amount,Amount +"Capture Online","Capture Online" +"Capture Offline","Capture Offline" +"Not Capture","Not Capture" +"The invoice will be created offline without the payment gateway.","The invoice will be created offline without the payment gateway." +"Email Copy of Invoice","Email Copy of Invoice" +"Items Invoiced","Items Invoiced" +"Total Tax","Total Tax" +"From Name","From Name" +"To Name","To Name" +Status,Status +Comment,Comment +"Notification Not Applicable","Notification Not Applicable" +"Order & Account Information","Order & Account Information" +"The order confirmation email was sent","The order confirmation email was sent" +"The order confirmation email is not sent","The order confirmation email is not sent" +"Order Date","Order Date" +"Order Date (%1)","Order Date (%1)" +"Purchased From","Purchased From" +"Link to the New Order","Link to the New Order" +"Link to the Previous Order","Link to the Previous Order" +"Placed from IP","Placed from IP" +"%1 / %2 rate:","%1 / %2 rate:" +"Customer Name","Customer Name" +"Customer Group","Customer Group" +"Notes for this Order","Notes for this Order" +"Comment added","Comment added" +"Transaction Data","Transaction Data" +"Transaction ID","Transaction ID" +"Parent Transaction ID","Parent Transaction ID" +"Order ID","Order ID" +"Transaction Type","Transaction Type" +"Is Closed","Is Closed" +"Created At","Created At" +"Child Transactions","Child Transactions" +"Transaction Details","Transaction Details" +Items,Items +"Gift Message for this Order","Gift Message for this Order" +"Shipped By","Shipped By" +"Tracking Number","Tracking Number" +"Billing Last Name","Billing Last Name" +"Find Order By","Find Order By" +"ZIP Code","ZIP Code" +"Billing ZIP Code","Billing ZIP Code" +Continue,Continue +"Print All Refunds","Print All Refunds" +"Refund #","Refund #" +"Print Refund","Print Refund" +"Product Name","Product Name" +"Order #","Order #" +Date,Date +"Ship To","Ship To" +Actions,Actions +"View Order","View Order" +"You have placed no orders.","You have placed no orders." +"No shipping information available","No shipping information available" +"Print Order","Print Order" +"Print All Invoices","Print All Invoices" +"Invoice #","Invoice #" +"Print Invoice","Print Invoice" +"Qty Invoiced","Qty Invoiced" +Close,Close +"About Your Order","About Your Order" +"Order Date: %1","Order Date: %1" +"Refund #%1","Refund #%1" +"Shipment #%1","Shipment #%1" +"Qty Shipped","Qty Shipped" +"Recent Orders","Recent Orders" +"View All","View All" +"Gift Message for This Order","Gift Message for This Order" +"Recently Ordered","Recently Ordered" +"Add to Cart","Add to Cart" +Search,Search +"Credit memo for your %store_name order","Credit memo for your %store_name order" +"%name,","%name," +"Thank you for your order from %store_name.","Thank you for your order from %store_name." +"You can check the status of your order by logging into your account.","You can check the status of your order by logging into your account." +"If you have questions about your order, you can email us at %store_email","If you have questions about your order, you can email us at %store_email" +"or call us at %store_phone","or call us at %store_phone" +"Our hours are %store_hours.","Our hours are %store_hours." +"Your Credit Memo #%creditmemo_id for Order #%order_id","Your Credit Memo #%creditmemo_id for Order #%order_id" +"Billing Info","Billing Info" +"Shipping Info","Shipping Info" +"Update to your %store_name credit memo","Update to your %store_name credit memo" +"Your order #%increment_id has been updated with a status of %order_status.","Your order #%increment_id has been updated with a status of %order_status." +"Invoice for your %store_name order","Invoice for your %store_name order" +"Your Invoice #%invoice_id for Order #%order_id","Your Invoice #%invoice_id for Order #%order_id" +"Update to your %store_name invoice","Update to your %store_name invoice" +"Your %store_name order confirmation","Your %store_name order confirmation" +"%customer_name,","%customer_name," +"Once your package ships we will send you a tracking number.","Once your package ships we will send you a tracking number." +"Your Order #%increment_id","Your Order #%increment_id" +"Placed on %created_at","Placed on %created_at" +"Once your package ships we will send an email with a link to track your order.","Once your package ships we will send an email with a link to track your order." +"Update to your %store_name order","Update to your %store_name order" +"Your %store_name order has shipped","Your %store_name order has shipped" +"Your shipping confirmation is below. Thank you again for your business.","Your shipping confirmation is below. Thank you again for your business." +"Your Shipment #%shipment_id for Order #%order_id","Your Shipment #%shipment_id for Order #%order_id" +"Update to your %store_name shipment","Update to your %store_name shipment" +"Gift Options for ","Gift Options for " +"Add Products","Add Products" +"You have item changes","You have item changes" +Ok,Ok +Operations,Operations +Create,Create +"Send Order Email","Send Order Email" +"Accept or Deny Payment","Accept or Deny Payment" +"Send Sales Emails","Send Sales Emails" +"Sales Section","Sales Section" +"Sales Emails Section","Sales Emails Section" +General,General +"Hide Customer IP","Hide Customer IP" +"Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos.","Choose whether a customer IP is shown in orders, invoices, shipments, and credit memos." +"Checkout Totals Sort Order","Checkout Totals Sort Order" +"Allow Reorder","Allow Reorder" +"Invoice and Packing Slip Design","Invoice and Packing Slip Design" +"Logo for PDF Print-outs (200x50)","Logo for PDF Print-outs (200x50)" +"Your default logo will be used in PDF and HTML documents.
(jpeg, tiff, png) If your pdf image is distorted, try to use larger file-size image.","Your default logo will be used in PDF and HTML documents.
(jpeg, tiff, png) If your pdf image is distorted, try to use larger file-size image." +"Logo for HTML Print View","Logo for HTML Print View" +"Logo for HTML documents only. If empty, default will be used.
(jpeg, gif, png)","Logo for HTML documents only. If empty, default will be used.
(jpeg, gif, png)" +Address,Address +"Minimum Order Amount","Minimum Order Amount" +Enable,Enable +"Minimum Amount","Minimum Amount" +"Subtotal after discount","Subtotal after discount" +"Include Tax to Amount","Include Tax to Amount" +"Description Message","Description Message" +"This message will be shown in the shopping cart when the subtotal (after discount) is lower than the minimum allowed amount.","This message will be shown in the shopping cart when the subtotal (after discount) is lower than the minimum allowed amount." +"Error to Show in Shopping Cart","Error to Show in Shopping Cart" +"Validate Each Address Separately in Multi-address Checkout","Validate Each Address Separately in Multi-address Checkout" +"Multi-address Description Message","Multi-address Description Message" +"We'll use the default description above if you leave this empty.","We'll use the default description above if you leave this empty." +"Multi-address Error to Show in Shopping Cart","Multi-address Error to Show in Shopping Cart" +"We'll use the default error above if you leave this empty.","We'll use the default error above if you leave this empty." +Dashboard,Dashboard +"Use Aggregated Data","Use Aggregated Data" +"Orders Cron Settings","Orders Cron Settings" +"Pending Payment Order Lifetime (minutes)","Pending Payment Order Lifetime (minutes)" +"Sales Emails","Sales Emails" +"Asynchronous sending","Asynchronous sending" +Enabled,Enabled +"New Order Confirmation Email Sender","New Order Confirmation Email Sender" +"New Order Confirmation Template","New Order Confirmation Template" +"Email template chosen based on theme fallback when ""Default"" option is selected.","Email template chosen based on theme fallback when ""Default"" option is selected." +"New Order Confirmation Template for Guest","New Order Confirmation Template for Guest" +"Send Order Email Copy To","Send Order Email Copy To" +Comma-separated,Comma-separated +"Send Order Email Copy Method","Send Order Email Copy Method" +"Order Comment Email Sender","Order Comment Email Sender" +"Order Comment Email Template","Order Comment Email Template" +"Order Comment Email Template for Guest","Order Comment Email Template for Guest" +"Send Order Comment Email Copy To","Send Order Comment Email Copy To" +"Send Order Comments Email Copy Method","Send Order Comments Email Copy Method" +"Invoice Email Sender","Invoice Email Sender" +"Invoice Email Template","Invoice Email Template" +"Invoice Email Template for Guest","Invoice Email Template for Guest" +"Send Invoice Email Copy To","Send Invoice Email Copy To" +"Send Invoice Email Copy Method","Send Invoice Email Copy Method" +"Invoice Comment Email Sender","Invoice Comment Email Sender" +"Invoice Comment Email Template","Invoice Comment Email Template" +"Invoice Comment Email Template for Guest","Invoice Comment Email Template for Guest" +"Send Invoice Comment Email Copy To","Send Invoice Comment Email Copy To" +"Send Invoice Comments Email Copy Method","Send Invoice Comments Email Copy Method" +Shipment,Shipment +"Shipment Email Sender","Shipment Email Sender" +"Shipment Email Template","Shipment Email Template" +"Shipment Email Template for Guest","Shipment Email Template for Guest" +"Send Shipment Email Copy To","Send Shipment Email Copy To" +"Send Shipment Email Copy Method","Send Shipment Email Copy Method" +"Shipment Comments","Shipment Comments" +"Shipment Comment Email Sender","Shipment Comment Email Sender" +"Shipment Comment Email Template","Shipment Comment Email Template" +"Shipment Comment Email Template for Guest","Shipment Comment Email Template for Guest" +"Send Shipment Comment Email Copy To","Send Shipment Comment Email Copy To" +"Send Shipment Comments Email Copy Method","Send Shipment Comments Email Copy Method" +"Credit Memo Email Sender","Credit Memo Email Sender" +"Credit Memo Email Template","Credit Memo Email Template" +"Credit Memo Email Template for Guest","Credit Memo Email Template for Guest" +"Send Credit Memo Email Copy To","Send Credit Memo Email Copy To" +"Send Credit Memo Email Copy Method","Send Credit Memo Email Copy Method" +"Credit Memo Comment Email Sender","Credit Memo Comment Email Sender" +"Credit Memo Comment Email Template","Credit Memo Comment Email Template" +"Credit Memo Comment Email Template for Guest","Credit Memo Comment Email Template for Guest" +"Send Credit Memo Comment Email Copy To","Send Credit Memo Comment Email Copy To" +"Send Credit Memo Comments Email Copy Method","Send Credit Memo Comments Email Copy Method" +"PDF Print-outs","PDF Print-outs" +"Display Order ID in Header","Display Order ID in Header" +"Customer Order Status Notification","Customer Order Status Notification" +"Asynchronous indexing","Asynchronous indexing" +"Orders and Returns Search Form","Orders and Returns Search Form" +"Anchor Custom Title","Anchor Custom Title" +Template,Template +"Default Template","Default Template" +Name,Name +Phone,Phone +"ZIP/Post Code","ZIP/Post Code" +"Signed-up Point","Signed-up Point" +Website,Website +"Bill-to Name","Bill-to Name" +Created,Created +"Invoice Date","Invoice Date" +"Ship-to Name","Ship-to Name" +"Ship Date","Ship Date" +"Total Quantity","Total Quantity" +"Default Status","Default Status" +"State Code and Title","State Code and Title" +"Item Status","Item Status" +"Original Price","Original Price" +"Tax Percent","Tax Percent" +"All Store Views","All Store Views" +"PDF Credit Memos","PDF Credit Memos" +"Purchase Point","Purchase Point" +"Print Invoices","Print Invoices" +"Print Packing Slips","Print Packing Slips" +"Print Credit Memos","Print Credit Memos" +"Print All","Print All" +"Print Shipping Labels","Print Shipping Labels" +"Purchase Date","Purchase Date" +"Grand Total (Base)","Grand Total (Base)" +"Grand Total (Purchased)","Grand Total (Purchased)" +"Customer Email","Customer Email" +"Shipping and Handling","Shipping and Handling" +"PDF Invoices","PDF Invoices" +"PDF Shipments","PDF Shipments" +"PDF Creditmemos","PDF Creditmemos" +Refunds,Refunds +"Allow Zero GrandTotal for Creditmemo","Allow Zero GrandTotal for Creditmemo" +"Allow Zero GrandTotal","Allow Zero GrandTotal" +Email is required field for Admin order creation,Email is required field for Admin order creation +If set YES Email field will be required during Admin order creation for new Customer.,If set YES Email field will be required during Admin order creation for new Customer. From 0627db9895e20c2152efb4684dd60f625c42fcc9 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Sat, 21 Sep 2019 16:55:25 +0530 Subject: [PATCH 021/369] boolean function starts with is --- app/code/Magento/Sales/Model/AdminOrder/Create.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) mode change 100644 => 100755 app/code/Magento/Sales/Model/AdminOrder/Create.php diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php old mode 100644 new mode 100755 index fd1fb472719d4..bc4c7a1ab47cf --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -2039,7 +2039,7 @@ protected function _getNewCustomerEmail() { $email = $this->getData('account/email'); - if ($email || $this->getIsEmailRequired()) { + if ($email || $this->isEmailRequired()) { return $email; } @@ -2051,7 +2051,7 @@ protected function _getNewCustomerEmail() * * @return bool */ - private function getIsEmailRequired(): bool + private function isEmailRequired(): bool { return (bool)$this->_scopeConfig->getValue( self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, From 1e9e9407dee36b5d6daf18c50bc96923f00928f3 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Sat, 21 Sep 2019 17:08:57 +0530 Subject: [PATCH 022/369] discarding customer language file --- app/code/Magento/Customer/i18n/en_US.csv | 768 +++++++++++------------ 1 file changed, 384 insertions(+), 384 deletions(-) diff --git a/app/code/Magento/Customer/i18n/en_US.csv b/app/code/Magento/Customer/i18n/en_US.csv index 4ab5e2b7068fb..3495feb925cb3 100644 --- a/app/code/Magento/Customer/i18n/en_US.csv +++ b/app/code/Magento/Customer/i18n/en_US.csv @@ -1,23 +1,23 @@ -Sign Out,Sign Out -Sign In,Sign In -You are subscribed to our newsletter.,You are subscribed to our newsletter. -You aren't subscribed to our newsletter.,You aren't subscribed to our newsletter. -You have not set a default shipping address.,You have not set a default shipping address. -You have not set a default billing address.,You have not set a default billing address. -Address Book,Address Book -Edit Address,Edit Address -Add New Address,Add New Address +"Sign Out","Sign Out" +"Sign In","Sign In" +"You are subscribed to our newsletter.","You are subscribed to our newsletter." +"You aren't subscribed to our newsletter.","You aren't subscribed to our newsletter." +"You have not set a default shipping address.","You have not set a default shipping address." +"You have not set a default billing address.","You have not set a default billing address." +"Address Book","Address Book" +"Edit Address","Edit Address" +"Add New Address","Add New Address" region,region -Create Order,Create Order -Save Customer,Save Customer -Delete Customer,Delete Customer -Reset Password,Reset Password -Are you sure you want to revoke the customer's tokens?,Are you sure you want to revoke the customer's tokens? -Force Sign-In,Force Sign-In -New Customer,New Customer -Save and Continue Edit,Save and Continue Edit +"Create Order","Create Order" +"Save Customer","Save Customer" +"Delete Customer","Delete Customer" +"Reset Password","Reset Password" +"Are you sure you want to revoke the customer's tokens?","Are you sure you want to revoke the customer's tokens?" +"Force Sign-In","Force Sign-In" +"New Customer","New Customer" +"Save and Continue Edit","Save and Continue Edit" Back,Back -Please select,Please select +"Please select","Please select" Reset,Reset ID,ID Product,Product @@ -28,186 +28,186 @@ Total,Total Action,Action Configure,Configure Delete,Delete -Shopping Cart from %1,Shopping Cart from %1 +"Shopping Cart from %1","Shopping Cart from %1" Newsletter,Newsletter -Newsletter Information,Newsletter Information -Subscribed to Newsletter,Subscribed to Newsletter -Last Date Subscribed,Last Date Subscribed -Last Date Unsubscribed,Last Date Unsubscribed -No Newsletter Found,No Newsletter Found -Start date,Start date -End Date,End Date -Receive Date,Receive Date +"Newsletter Information","Newsletter Information" +"Subscribed to Newsletter","Subscribed to Newsletter" +"Last Date Subscribed","Last Date Subscribed" +"Last Date Unsubscribed","Last Date Unsubscribed" +"No Newsletter Found","No Newsletter Found" +"Start date","Start date" +"End Date","End Date" +"Receive Date","Receive Date" Subject,Subject Status,Status Sent,Sent Cancel,Cancel -Not Sent,Not Sent +"Not Sent","Not Sent" Sending,Sending Paused,Paused View,View Unknown,Unknown -Order #,Order # +"Order #","Order #" Purchased,Purchased -Bill-to Name,Bill-to Name -Ship-to Name,Ship-to Name -Order Total,Order Total -Purchase Point,Purchase Point -Customer View,Customer View -There are no items in customer's shopping cart.,There are no items in customer's shopping cart. +"Bill-to Name","Bill-to Name" +"Ship-to Name","Ship-to Name" +"Order Total","Order Total" +"Purchase Point","Purchase Point" +"Customer View","Customer View" +"There are no items in customer's shopping cart.","There are no items in customer's shopping cart." Qty,Qty Confirmed,Confirmed -Confirmation Required,Confirmation Required -Confirmation Not Required,Confirmation Not Required +"Confirmation Required","Confirmation Required" +"Confirmation Not Required","Confirmation Not Required" Indeterminate,Indeterminate -The customer does not have default billing address.,The customer does not have default billing address. +"The customer does not have default billing address.","The customer does not have default billing address." Offline,Offline Online,Online Never,Never Unlocked,Unlocked Locked,Locked -Deleted Stores,Deleted Stores -Add Locale,Add Locale -Add Date,Add Date -Days in Wish List,Days in Wish List +"Deleted Stores","Deleted Stores" +"Add Locale","Add Locale" +"Add Date","Add Date" +"Days in Wish List","Days in Wish List" Unlock,Unlock No,No Yes,Yes -Delete File,Delete File +"Delete File","Delete File" Download,Download -Delete Image,Delete Image -View Full Size,View Full Size -All countries,All countries -Customer Groups,Customer Groups -Add New Customer Group,Add New Customer Group -Save Customer Group,Save Customer Group -Delete Customer Group,Delete Customer Group -New Customer Group,New Customer Group +"Delete Image","Delete Image" +"View Full Size","View Full Size" +"All countries","All countries" +"Customer Groups","Customer Groups" +"Add New Customer Group","Add New Customer Group" +"Save Customer Group","Save Customer Group" +"Delete Customer Group","Delete Customer Group" +"New Customer Group","New Customer Group" "Edit Customer Group ""%1""","Edit Customer Group ""%1""" -Group Information,Group Information -Group Name,Group Name -Maximum length must be less then %1 characters.,Maximum length must be less then %1 characters. -Tax Class,Tax Class -The customer is now assigned to Customer Group %s.,The customer is now assigned to Customer Group %s. -Would you like to change the Customer Group for this order?,Would you like to change the Customer Group for this order? -The VAT ID is valid.,The VAT ID is valid. -The VAT ID entered (%s) is not a valid VAT ID.,The VAT ID entered (%s) is not a valid VAT ID. -The VAT ID is valid. The current Customer Group will be used.,The VAT ID is valid. The current Customer Group will be used. -The VAT ID is valid but no Customer Group is assigned for it.,The VAT ID is valid but no Customer Group is assigned for it. +"Group Information","Group Information" +"Group Name","Group Name" +"Maximum length must be less then %1 characters.","Maximum length must be less then %1 characters." +"Tax Class","Tax Class" +"The customer is now assigned to Customer Group %s.","The customer is now assigned to Customer Group %s." +"Would you like to change the Customer Group for this order?","Would you like to change the Customer Group for this order?" +"The VAT ID is valid.","The VAT ID is valid." +"The VAT ID entered (%s) is not a valid VAT ID.","The VAT ID entered (%s) is not a valid VAT ID." +"The VAT ID is valid. The current Customer Group will be used.","The VAT ID is valid. The current Customer Group will be used." +"The VAT ID is valid but no Customer Group is assigned for it.","The VAT ID is valid but no Customer Group is assigned for it." "Based on the VAT ID, the customer belongs to the Customer Group %s.","Based on the VAT ID, the customer belongs to the Customer Group %s." -Something went wrong while validating the VAT ID.,Something went wrong while validating the VAT ID. -The customer would belong to Customer Group %s.,The customer would belong to Customer Group %s. -There was an error detecting Customer Group.,There was an error detecting Customer Group. -Validate VAT Number,Validate VAT Number -Customer Login,Customer Login -Create New Customer Account,Create New Customer Account -Date of Birth,Date of Birth -Bad request.,Bad request. -This confirmation key is invalid or has expired.,This confirmation key is invalid or has expired. -There was an error confirming the account,There was an error confirming the account +"Something went wrong while validating the VAT ID.","Something went wrong while validating the VAT ID." +"The customer would belong to Customer Group %s.","The customer would belong to Customer Group %s." +"There was an error detecting Customer Group.","There was an error detecting Customer Group." +"Validate VAT Number","Validate VAT Number" +"Customer Login","Customer Login" +"Create New Customer Account","Create New Customer Account" +"Date of Birth","Date of Birth" +"Bad request.","Bad request." +"This confirmation key is invalid or has expired.","This confirmation key is invalid or has expired." +"There was an error confirming the account","There was an error confirming the account" "If you are a registered VAT customer, please click here to enter your shipping address for proper VAT calculation.","If you are a registered VAT customer, please click here to enter your shipping address for proper VAT calculation." "If you are a registered VAT customer, please click here to enter your billing address for proper VAT calculation.","If you are a registered VAT customer, please click here to enter your billing address for proper VAT calculation." -Thank you for registering with %1.,Thank you for registering with %1. -Please check your email for confirmation key.,Please check your email for confirmation key. -This email does not require confirmation.,This email does not require confirmation. -Wrong email.,Wrong email. -Your password reset link has expired.,Your password reset link has expired. +"Thank you for registering with %1.","Thank you for registering with %1." +"Please check your email for confirmation key.","Please check your email for confirmation key." +"This email does not require confirmation.","This email does not require confirmation." +"Wrong email.","Wrong email." +"Your password reset link has expired.","Your password reset link has expired." "You must confirm your account. Please check your email for the confirmation link or click here for a new link.","You must confirm your account. Please check your email for the confirmation link or click here for a new link." "There is already an account with this email address. If you are sure that it is your email address, click here to get your password and access your account.","There is already an account with this email address. If you are sure that it is your email address, click here to get your password and access your account." -We can't save the customer.,We can't save the customer. -Please make sure your passwords match.,Please make sure your passwords match. +"We can't save the customer.","We can't save the customer." +"Please make sure your passwords match.","Please make sure your passwords match." "If you are a registered VAT customer, please click here to enter your shipping address for proper VAT calculation.","If you are a registered VAT customer, please click here to enter your shipping address for proper VAT calculation." "If you are a registered VAT customer, please click here to enter your billing address for proper VAT calculation.","If you are a registered VAT customer, please click here to enter your billing address for proper VAT calculation." -Account Information,Account Information -You saved the account information.,You saved the account information. -You did not sign in correctly or your account is temporarily disabled.,You did not sign in correctly or your account is temporarily disabled. -Password confirmation doesn't match entered password.,Password confirmation doesn't match entered password. -The password doesn't match this account.,The password doesn't match this account. -Please correct the email address.,Please correct the email address. -We're unable to send the password reset email.,We're unable to send the password reset email. -Please enter your email.,Please enter your email. -If there is an account associated with %1 you will receive an email with a link to reset your password.,If there is an account associated with %1 you will receive an email with a link to reset your password. -My Account,My Account +"Account Information","Account Information" +"You saved the account information.","You saved the account information." +"You did not sign in correctly or your account is temporarily disabled.","You did not sign in correctly or your account is temporarily disabled." +"Password confirmation doesn't match entered password.","Password confirmation doesn't match entered password." +"The password doesn't match this account.","The password doesn't match this account." +"Please correct the email address.","Please correct the email address." +"We're unable to send the password reset email.","We're unable to send the password reset email." +"Please enter your email.","Please enter your email." +"If there is an account associated with %1 you will receive an email with a link to reset your password.","If there is an account associated with %1 you will receive an email with a link to reset your password." +"My Account","My Account" "This account is not confirmed. Click here to resend confirmation email.","This account is not confirmed. Click here to resend confirmation email." -An unspecified error occurred. Please contact us for assistance.,An unspecified error occurred. Please contact us for assistance. -A login and a password are required.,A login and a password are required. -New Password and Confirm New Password values didn't match.,New Password and Confirm New Password values didn't match. -Please enter a new password.,Please enter a new password. -You updated your password.,You updated your password. -Something went wrong while saving the new password.,Something went wrong while saving the new password. -You deleted the address.,You deleted the address. -We can't delete the address right now.,We can't delete the address right now. -You saved the address.,You saved the address. -We can't save the address.,We can't save the address. -No customer ID defined.,No customer ID defined. -Please correct the quote items and try again.,Please correct the quote items and try again. -You have revoked the customer's tokens.,You have revoked the customer's tokens. -We can't find a customer to revoke.,We can't find a customer to revoke. -Something went wrong while saving file.,Something went wrong while saving file. -You deleted the customer group.,You deleted the customer group. -The customer group no longer exists.,The customer group no longer exists. +"An unspecified error occurred. Please contact us for assistance.","An unspecified error occurred. Please contact us for assistance." +"A login and a password are required.","A login and a password are required." +"New Password and Confirm New Password values didn't match.","New Password and Confirm New Password values didn't match." +"Please enter a new password.","Please enter a new password." +"You updated your password.","You updated your password." +"Something went wrong while saving the new password.","Something went wrong while saving the new password." +"You deleted the address.","You deleted the address." +"We can't delete the address right now.","We can't delete the address right now." +"You saved the address.","You saved the address." +"We can't save the address.","We can't save the address." +"No customer ID defined.","No customer ID defined." +"Please correct the quote items and try again.","Please correct the quote items and try again." +"You have revoked the customer's tokens.","You have revoked the customer's tokens." +"We can't find a customer to revoke.","We can't find a customer to revoke." +"Something went wrong while saving file.","Something went wrong while saving file." +"You deleted the customer group.","You deleted the customer group." +"The customer group no longer exists.","The customer group no longer exists." Customers,Customers -New Group,New Group -New Customer Groups,New Customer Groups -Edit Group,Edit Group -Edit Customer Groups,Edit Customer Groups -You saved the customer group.,You saved the customer group. -Please select customer(s).,Please select customer(s). -Customer could not be deleted.,Customer could not be deleted. -You deleted the customer.,You deleted the customer. -Something went wrong while editing the customer.,Something went wrong while editing the customer. -Manage Customers,Manage Customers -Please correct the data sent.,Please correct the data sent. -A total of %1 record(s) were updated.,A total of %1 record(s) were updated. -A total of %1 record(s) were deleted.,A total of %1 record(s) were deleted. -The customer will receive an email with a link to reset password.,The customer will receive an email with a link to reset password. -Something went wrong while resetting customer password.,Something went wrong while resetting customer password. -You saved the customer.,You saved the customer. -Something went wrong while saving the customer.,Something went wrong while saving the customer. -Page not found.,Page not found. -Customer has been unlocked successfully.,Customer has been unlocked successfully. -Online Customers,Online Customers -Customers Now Online,Customers Now Online -Please define Wish List item ID.,Please define Wish List item ID. -Please load Wish List item.,Please load Wish List item. -Login successful.,Login successful. -Invalid login or password.,Invalid login or password. +"New Group","New Group" +"New Customer Groups","New Customer Groups" +"Edit Group","Edit Group" +"Edit Customer Groups","Edit Customer Groups" +"You saved the customer group.","You saved the customer group." +"Please select customer(s).","Please select customer(s)." +"Customer could not be deleted.","Customer could not be deleted." +"You deleted the customer.","You deleted the customer." +"Something went wrong while editing the customer.","Something went wrong while editing the customer." +"Manage Customers","Manage Customers" +"Please correct the data sent.","Please correct the data sent." +"A total of %1 record(s) were updated.","A total of %1 record(s) were updated." +"A total of %1 record(s) were deleted.","A total of %1 record(s) were deleted." +"The customer will receive an email with a link to reset password.","The customer will receive an email with a link to reset password." +"Something went wrong while resetting customer password.","Something went wrong while resetting customer password." +"You saved the customer.","You saved the customer." +"Something went wrong while saving the customer.","Something went wrong while saving the customer." +"Page not found.","Page not found." +"Customer has been unlocked successfully.","Customer has been unlocked successfully." +"Online Customers","Online Customers" +"Customers Now Online","Customers Now Online" +"Please define Wish List item ID.","Please define Wish List item ID." +"Please load Wish List item.","Please load Wish List item." +"Login successful.","Login successful." +"Invalid login or password.","Invalid login or password." """%1"" section source is not supported","""%1"" section source is not supported" -%1 doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface,%1 doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface -No confirmation needed.,No confirmation needed. -Account already active,Account already active -Invalid confirmation token,Invalid confirmation token -The account is locked.,The account is locked. -This account is not confirmed.,This account is not confirmed. +"%1 doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface","%1 doesn't extend \Magento\Customer\CustomerData\SectionSourceInterface" +"No confirmation needed.","No confirmation needed." +"Account already active","Account already active" +"Invalid confirmation token","Invalid confirmation token" +"The account is locked.","The account is locked." +"This account is not confirmed.","This account is not confirmed." "Invalid value of ""%value"" provided for the %fieldName field.","Invalid value of ""%value"" provided for the %fieldName field." -Please enter a password with at most %1 characters.,Please enter a password with at most %1 characters. -Please enter a password with at least %1 characters.,Please enter a password with at least %1 characters. -The password can't begin or end with a space.,The password can't begin or end with a space. +"Please enter a password with at most %1 characters.","Please enter a password with at most %1 characters." +"Please enter a password with at least %1 characters.","Please enter a password with at least %1 characters." +"The password can't begin or end with a space.","The password can't begin or end with a space." "Minimum of different classes of characters in password is %1. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.","Minimum of different classes of characters in password is %1. Classes of characters: Lower Case, Upper Case, Digits, Special Characters." -Password cannot be the same as email address.,Password cannot be the same as email address. -This customer already exists in this store.,This customer already exists in this store. -A customer with the same email already exists in an associated website.,A customer with the same email already exists in an associated website. -%fieldName is a required field.,%fieldName is a required field. -Reset password token mismatch.,Reset password token mismatch. -Reset password token expired.,Reset password token expired. -Please correct the transactional account email type.,Please correct the transactional account email type. +"Password cannot be the same as email address.","Password cannot be the same as email address." +"This customer already exists in this store.","This customer already exists in this store." +"A customer with the same email already exists in an associated website.","A customer with the same email already exists in an associated website." +"%fieldName is a required field.","%fieldName is a required field." +"Reset password token mismatch.","Reset password token mismatch." +"Reset password token expired.","Reset password token expired." +"Please correct the transactional account email type.","Please correct the transactional account email type." """%1"" is a required value.","""%1"" is a required value." Global,Global -Per Website,Per Website -We can't share customer accounts globally when the accounts share identical email addresses on more than one website.,We can't share customer accounts globally when the accounts share identical email addresses on more than one website. -Billing Address,Billing Address -Shipping Address,Shipping Address --- Please Select --,-- Please Select -- -Please enter a valid password reset token.,Please enter a valid password reset token. -The password can not begin or end with a space.,The password can not begin or end with a space. +"Per Website","Per Website" +"We can't share customer accounts globally when the accounts share identical email addresses on more than one website.","We can't share customer accounts globally when the accounts share identical email addresses on more than one website." +"Billing Address","Billing Address" +"Shipping Address","Shipping Address" +"-- Please Select --","-- Please Select --" +"Please enter a valid password reset token.","Please enter a valid password reset token." +"The password can not begin or end with a space.","The password can not begin or end with a space." Admin,Admin -ALL GROUPS,ALL GROUPS +"ALL GROUPS","ALL GROUPS" "No such entity with %fieldName = %fieldValue, %field2Name = %field2Value","No such entity with %fieldName = %fieldValue, %field2Name = %field2Value" -File can not be saved to the destination folder.,File can not be saved to the destination folder. -Unable to create directory %1.,Unable to create directory %1. -Destination folder is not writable or does not exists.,Destination folder is not writable or does not exists. -Something went wrong while saving the file.,Something went wrong while saving the file. -Attribute object is undefined,Attribute object is undefined +"File can not be saved to the destination folder.","File can not be saved to the destination folder." +"Unable to create directory %1.","Unable to create directory %1." +"Destination folder is not writable or does not exists.","Destination folder is not writable or does not exists." +"Something went wrong while saving the file.","Something went wrong while saving the file." +"Attribute object is undefined","Attribute object is undefined" """%1"" invalid type entered.","""%1"" invalid type entered." """%1"" contains non-alphabetic or non-numeric characters.","""%1"" contains non-alphabetic or non-numeric characters." """%1"" is an empty string.","""%1"" is an empty string." @@ -217,19 +217,19 @@ Attribute object is undefined,Attribute object is undefined """%1"" is not a valid hostname.","""%1"" is not a valid hostname." """%1"" uses too many characters.","""%1"" uses too many characters." "'%value%' looks like an IP address, which is not an acceptable format.","'%value%' looks like an IP address, which is not an acceptable format." -'%value%' looks like a DNS hostname but we cannot match the TLD against known list.,'%value%' looks like a DNS hostname but we cannot match the TLD against known list. -'%value%' looks like a DNS hostname but contains a dash in an invalid position.,'%value%' looks like a DNS hostname but contains a dash in an invalid position. -'%value%' looks like a DNS hostname but we cannot match it against the hostname schema for TLD '%tld%'.,'%value%' looks like a DNS hostname but we cannot match it against the hostname schema for TLD '%tld%'. -'%value%' looks like a DNS hostname but cannot extract TLD part.,'%value%' looks like a DNS hostname but cannot extract TLD part. -'%value%' does not look like a valid local network name.,'%value%' does not look like a valid local network name. +"'%value%' looks like a DNS hostname but we cannot match the TLD against known list.","'%value%' looks like a DNS hostname but we cannot match the TLD against known list." +"'%value%' looks like a DNS hostname but contains a dash in an invalid position.","'%value%' looks like a DNS hostname but contains a dash in an invalid position." +"'%value%' looks like a DNS hostname but we cannot match it against the hostname schema for TLD '%tld%'.","'%value%' looks like a DNS hostname but we cannot match it against the hostname schema for TLD '%tld%'." +"'%value%' looks like a DNS hostname but cannot extract TLD part.","'%value%' looks like a DNS hostname but cannot extract TLD part." +"'%value%' does not look like a valid local network name.","'%value%' does not look like a valid local network name." "'%value%' looks like a local network name, which is not an acceptable format.","'%value%' looks like a local network name, which is not an acceptable format." "'%value%' appears to be a DNS hostname, but the given punycode notation cannot be decoded.","'%value%' appears to be a DNS hostname, but the given punycode notation cannot be decoded." """%1"" is not a valid URL.","""%1"" is not a valid URL." """%1"" is not a valid date.","""%1"" is not a valid date." """%1"" does not fit the entered date format.","""%1"" does not fit the entered date format." -Please enter a valid date between %1 and %2 at %3.,Please enter a valid date between %1 and %2 at %3. -Please enter a valid date equal to or greater than %1 at %2.,Please enter a valid date equal to or greater than %1 at %2. -Please enter a valid date less than or equal to %1 at %2.,Please enter a valid date less than or equal to %1 at %2. +"Please enter a valid date between %1 and %2 at %3.","Please enter a valid date between %1 and %2 at %3." +"Please enter a valid date equal to or greater than %1 at %2.","Please enter a valid date equal to or greater than %1 at %2." +"Please enter a valid date less than or equal to %1 at %2.","Please enter a valid date less than or equal to %1 at %2." """%1"" is not a valid file extension.","""%1"" is not a valid file extension." """%1"" is not a valid file.","""%1"" is not a valid file." """%1"" exceeds the allowed file size.","""%1"" exceeds the allowed file size." @@ -239,203 +239,203 @@ Please enter a valid date less than or equal to %1 at %2.,Please enter a valid d """%1"" length must be equal or greater than %2 characters.","""%1"" length must be equal or greater than %2 characters." """%1"" length must be equal or less than %2 characters.","""%1"" length must be equal or less than %2 characters." label,label -Please enter a customer email.,Please enter a customer email. -A customer website ID must be specified when using the website scope.,A customer website ID must be specified when using the website scope. -Customer Group,Customer Group +"Please enter a customer email.","Please enter a customer email." +"A customer website ID must be specified when using the website scope.","A customer website ID must be specified when using the website scope." +"Customer Group","Customer Group" "You can't delete group ""%1"".","You can't delete group ""%1""." -Customer Group already exists.,Customer Group already exists. -Cannot delete group.,Cannot delete group. -Error during VAT Number verification.,Error during VAT Number verification. -PHP SOAP extension is required.,PHP SOAP extension is required. -VAT Number is valid.,VAT Number is valid. -Please enter a valid VAT number.,Please enter a valid VAT number. -Your VAT ID was successfully validated.,Your VAT ID was successfully validated. -You will be charged tax.,You will be charged tax. -You will not be charged tax.,You will not be charged tax. -The VAT ID entered (%1) is not a valid VAT ID.,The VAT ID entered (%1) is not a valid VAT ID. -Your Tax ID cannot be validated.,Your Tax ID cannot be validated. +"Customer Group already exists.","Customer Group already exists." +"Cannot delete group.","Cannot delete group." +"Error during VAT Number verification.","Error during VAT Number verification." +"PHP SOAP extension is required.","PHP SOAP extension is required." +"VAT Number is valid.","VAT Number is valid." +"Please enter a valid VAT number.","Please enter a valid VAT number." +"Your VAT ID was successfully validated.","Your VAT ID was successfully validated." +"You will be charged tax.","You will be charged tax." +"You will not be charged tax.","You will not be charged tax." +"The VAT ID entered (%1) is not a valid VAT ID.","The VAT ID entered (%1) is not a valid VAT ID." +"Your Tax ID cannot be validated.","Your Tax ID cannot be validated." "If you believe this is an error, please contact us at %1","If you believe this is an error, please contact us at %1" -No such entity with %fieldName = %fieldValue,No such entity with %fieldName = %fieldValue +"No such entity with %fieldName = %fieldValue","No such entity with %fieldName = %fieldValue" Male,Male Female,Female -Not Specified,Not Specified -Thank you for registering with,Thank you for registering with -enter your billing address for proper VAT calculation,enter your billing address for proper VAT calculation -enter your shipping address for proper VAT calculation,enter your shipping address for proper VAT calculation -Please enter new password.,Please enter new password. +"Not Specified","Not Specified" +"Thank you for registering with","Thank you for registering with" +"enter your billing address for proper VAT calculation","enter your billing address for proper VAT calculation" +"enter your shipping address for proper VAT calculation","enter your shipping address for proper VAT calculation" +"Please enter new password.","Please enter new password." message,message NoSuchEntityException,NoSuchEntityException Exception,Exception InputException.,InputException. InputException,InputException -Exception message,Exception message -Validator Exception,Validator Exception -Localized Exception,Localized Exception -some error,some error +"Exception message","Exception message" +"Validator Exception","Validator Exception" +"Localized Exception","Localized Exception" +"some error","some error" frontend_label,frontend_label -NOT LOGGED IN,NOT LOGGED IN -Please enter the state/province.,Please enter the state/province. -region is a required field.,region is a required field. -regionId is a required field.,regionId is a required field. +"NOT LOGGED IN","NOT LOGGED IN" +"Please enter the state/province.","Please enter the state/province." +"region is a required field.","region is a required field." +"regionId is a required field.","regionId is a required field." Label,Label Select...,Select... Edit,Edit Visitor,Visitor Customer,Customer -No item specified.,No item specified. -Are you sure you want to remove this item?,Are you sure you want to remove this item? -Personal Information,Personal Information -Last Logged In:,Last Logged In: -Last Logged In (%1):,Last Logged In (%1): -Account Lock:,Account Lock: -Confirmed email:,Confirmed email: -Account Created:,Account Created: -Account Created on (%1):,Account Created on (%1): -Account Created in:,Account Created in: -Customer Group:,Customer Group: -Default Billing Address,Default Billing Address -Sales Statistics,Sales Statistics -Web Site,Web Site +"No item specified.","No item specified." +"Are you sure you want to remove this item?","Are you sure you want to remove this item?" +"Personal Information","Personal Information" +"Last Logged In:","Last Logged In:" +"Last Logged In (%1):","Last Logged In (%1):" +"Account Lock:","Account Lock:" +"Confirmed email:","Confirmed email:" +"Account Created:","Account Created:" +"Account Created on (%1):","Account Created on (%1):" +"Account Created in:","Account Created in:" +"Customer Group:","Customer Group:" +"Default Billing Address","Default Billing Address" +"Sales Statistics","Sales Statistics" +"Web Site","Web Site" Store,Store -Store View,Store View -Lifetime Sales,Lifetime Sales -Average Sale,Average Sale -All Store Views,All Store Views +"Store View","Store View" +"Lifetime Sales","Lifetime Sales" +"Average Sale","Average Sale" +"All Store Views","All Store Views" Change,Change -Manage Addresses,Manage Addresses -Default Shipping Address,Default Shipping Address -Contact Information,Contact Information -Change Password,Change Password +"Manage Addresses","Manage Addresses" +"Default Shipping Address","Default Shipping Address" +"Contact Information","Contact Information" +"Change Password","Change Password" Newsletters,Newsletters "You are subscribed to ""General Subscription"".","You are subscribed to ""General Subscription""." or,or -Default Addresses,Default Addresses -Change Billing Address,Change Billing Address -You have no default billing address in your address book.,You have no default billing address in your address book. -Change Shipping Address,Change Shipping Address -You have no default shipping address in your address book.,You have no default shipping address in your address book. -Additional Address Entries,Additional Address Entries -Delete Address,Delete Address -You have no other address entries in your address book.,You have no other address entries in your address book. -* Required Fields,* Required Fields +"Default Addresses","Default Addresses" +"Change Billing Address","Change Billing Address" +"You have no default billing address in your address book.","You have no default billing address in your address book." +"Change Shipping Address","Change Shipping Address" +"You have no default shipping address in your address book.","You have no default shipping address in your address book." +"Additional Address Entries","Additional Address Entries" +"Delete Address","Delete Address" +"You have no other address entries in your address book.","You have no other address entries in your address book." +"* Required Fields","* Required Fields" Address,Address -Street Address,Street Address -Street Address %1,Street Address %1 -VAT Number,VAT Number +"Street Address","Street Address" +"Street Address %1","Street Address %1" +"VAT Number","VAT Number" City,City State/Province,State/Province "Please select a region, state or province.","Please select a region, state or province." -Zip/Postal Code,Zip/Postal Code +"Zip/Postal Code","Zip/Postal Code" Country,Country -It's a default billing address.,It's a default billing address. -Use as my default billing address,Use as my default billing address -It's a default shipping address.,It's a default shipping address. -Use as my default shipping address,Use as my default shipping address -Save Address,Save Address -Go back,Go back -Please enter your email below and we will send you the confirmation link.,Please enter your email below and we will send you the confirmation link. +"It's a default billing address.","It's a default billing address." +"Use as my default billing address","Use as my default billing address" +"It's a default shipping address.","It's a default shipping address." +"Use as my default shipping address","Use as my default shipping address" +"Save Address","Save Address" +"Go back","Go back" +"Please enter your email below and we will send you the confirmation link.","Please enter your email below and we will send you the confirmation link." Email,Email -Send confirmation link,Send confirmation link -Back to Sign In,Back to Sign In -Change Email,Change Email -Change Email and Password,Change Email and Password -Current Password,Current Password -New Password,New Password -Password Strength,Password Strength -No Password,No Password -Confirm New Password,Confirm New Password +"Send confirmation link","Send confirmation link" +"Back to Sign In","Back to Sign In" +"Change Email","Change Email" +"Change Email and Password","Change Email and Password" +"Current Password","Current Password" +"New Password","New Password" +"Password Strength","Password Strength" +"No Password","No Password" +"Confirm New Password","Confirm New Password" Save,Save -Please enter your email address below to receive a password reset link.,Please enter your email address below to receive a password reset link. -Reset My Password,Reset My Password -Registered Customers,Registered Customers +"Please enter your email address below to receive a password reset link.","Please enter your email address below to receive a password reset link." +"Reset My Password","Reset My Password" +"Registered Customers","Registered Customers" "If you have an account, sign in with your email address.","If you have an account, sign in with your email address." Password,Password -Forgot Your Password?,Forgot Your Password? -Subscription option,Subscription option -General Subscription,General Subscription -Sign Up for Newsletter,Sign Up for Newsletter -Address Information,Address Information -Sign-in Information,Sign-in Information -Confirm Password,Confirm Password -Create an Account,Create an Account -Set a New Password,Set a New Password -You have signed out and will go to our homepage in 5 seconds.,You have signed out and will go to our homepage in 5 seconds. -New Customers,New Customers +"Forgot Your Password?","Forgot Your Password?" +"Subscription option","Subscription option" +"General Subscription","General Subscription" +"Sign Up for Newsletter","Sign Up for Newsletter" +"Address Information","Address Information" +"Sign-in Information","Sign-in Information" +"Confirm Password","Confirm Password" +"Create an Account","Create an Account" +"Set a New Password","Set a New Password" +"You have signed out and will go to our homepage in 5 seconds.","You have signed out and will go to our homepage in 5 seconds." +"New Customers","New Customers" "Creating an account has many benefits: check out faster, keep more than one address, track orders and more.","Creating an account has many benefits: check out faster, keep more than one address, track orders and more." Company,Company Fax,Fax Gender,Gender Name,Name -Tax/VAT number,Tax/VAT number -Phone Number,Phone Number -Welcome to %store_name,Welcome to %store_name +"Tax/VAT number","Tax/VAT number" +"Phone Number","Phone Number" +"Welcome to %store_name","Welcome to %store_name" "%name,","%name," -Welcome to %store_name.,Welcome to %store_name. +"Welcome to %store_name.","Welcome to %store_name." "To sign in to our site, use these credentials during checkout or on the My Account page:","To sign in to our site, use these credentials during checkout or on the My Account page:" Email:,Email: Password:,Password: -Password you set when creating account,Password you set when creating account +"Password you set when creating account","Password you set when creating account" "Forgot your account password? Click here to reset it.","Forgot your account password? Click here to reset it." "When you sign in to your account, you will be able to:","When you sign in to your account, you will be able to:" -Proceed through checkout faster,Proceed through checkout faster -Check the status of orders,Check the status of orders -View past orders,View past orders -Store alternative addresses (for shipping to multiple family members and friends),Store alternative addresses (for shipping to multiple family members and friends) -Please confirm your %store_name account,Please confirm your %store_name account -You must confirm your %customer_email email before you can sign in (link is only valid once):,You must confirm your %customer_email email before you can sign in (link is only valid once): -Confirm Your Account,Confirm Your Account -Thank you for confirming your %store_name account.,Thank you for confirming your %store_name account. +"Proceed through checkout faster","Proceed through checkout faster" +"Check the status of orders","Check the status of orders" +"View past orders","View past orders" +"Store alternative addresses (for shipping to multiple family members and friends)","Store alternative addresses (for shipping to multiple family members and friends)" +"Please confirm your %store_name account","Please confirm your %store_name account" +"You must confirm your %customer_email email before you can sign in (link is only valid once):","You must confirm your %customer_email email before you can sign in (link is only valid once):" +"Confirm Your Account","Confirm Your Account" +"Thank you for confirming your %store_name account.","Thank you for confirming your %store_name account." "To sign in to our site and set a password, click on the link:","To sign in to our site and set a password, click on the link:" -Your %store_name email has been changed,Your %store_name email has been changed +"Your %store_name email has been changed","Your %store_name email has been changed" "Hello,","Hello," -We have received a request to change the following information associated with your account at %store_name: email.,We have received a request to change the following information associated with your account at %store_name: email. +"We have received a request to change the following information associated with your account at %store_name: email.","We have received a request to change the following information associated with your account at %store_name: email." "If you have not authorized this action, please contact us immediately at %store_email","If you have not authorized this action, please contact us immediately at %store_email" "or call us at %store_phone","or call us at %store_phone" "Thanks,
%store_name","Thanks,
%store_name" -Your %store_name email and password has been changed,Your %store_name email and password has been changed +"Your %store_name email and password has been changed","Your %store_name email and password has been changed" "We have received a request to change the following information associated with your account at %store_name: email, password.","We have received a request to change the following information associated with your account at %store_name: email, password." -Reset your %store_name password,Reset your %store_name password -There was recently a request to change the password for your account.,There was recently a request to change the password for your account. +"Reset your %store_name password","Reset your %store_name password" +"There was recently a request to change the password for your account.","There was recently a request to change the password for your account." "If you requested this change, set a new password here:","If you requested this change, set a new password here:" "If you did not make this request, you can ignore this email and your password will remain the same.","If you did not make this request, you can ignore this email and your password will remain the same." -Your %store_name password has been changed,Your %store_name password has been changed -We have received a request to change the following information associated with your account at %store_name: password.,We have received a request to change the following information associated with your account at %store_name: password. -Checkout as a new customer,Checkout as a new customer -Creating an account has many benefits:,Creating an account has many benefits: -See order and shipping status,See order and shipping status -Track order history,Track order history -Check out faster,Check out faster -Checkout using your account,Checkout using your account -Email Address,Email Address -Are you sure you want to do this?,Are you sure you want to do this? -Are you sure you want to delete this address?,Are you sure you want to delete this address? +"Your %store_name password has been changed","Your %store_name password has been changed" +"We have received a request to change the following information associated with your account at %store_name: password.","We have received a request to change the following information associated with your account at %store_name: password." +"Checkout as a new customer","Checkout as a new customer" +"Creating an account has many benefits:","Creating an account has many benefits:" +"See order and shipping status","See order and shipping status" +"Track order history","Track order history" +"Check out faster","Check out faster" +"Checkout using your account","Checkout using your account" +"Email Address","Email Address" +"Are you sure you want to do this?","Are you sure you want to do this?" +"Are you sure you want to delete this address?","Are you sure you want to delete this address?" Weak,Weak Medium,Medium Strong,Strong -Very Strong,Very Strong -Guest checkout is disabled.,Guest checkout is disabled. -All Customers,All Customers -Now Online,Now Online -Customers Section,Customers Section -Customer Configuration,Customer Configuration -Account Sharing Options,Account Sharing Options -Share Customer Accounts,Share Customer Accounts -Create New Account Options,Create New Account Options -Enable Automatic Assignment to Customer Group,Enable Automatic Assignment to Customer Group -Tax Calculation Based On,Tax Calculation Based On -Default Group,Default Group -Group for Valid VAT ID - Domestic,Group for Valid VAT ID - Domestic -Group for Valid VAT ID - Intra-Union,Group for Valid VAT ID - Intra-Union -Group for Invalid VAT ID,Group for Invalid VAT ID -Validation Error Group,Validation Error Group -Validate on Each Transaction,Validate on Each Transaction -Default Value for Disable Automatic Group Changes Based on VAT ID,Default Value for Disable Automatic Group Changes Based on VAT ID -Show VAT Number on Storefront,Show VAT Number on Storefront +"Very Strong","Very Strong" +"Guest checkout is disabled.","Guest checkout is disabled." +"All Customers","All Customers" +"Now Online","Now Online" +"Customers Section","Customers Section" +"Customer Configuration","Customer Configuration" +"Account Sharing Options","Account Sharing Options" +"Share Customer Accounts","Share Customer Accounts" +"Create New Account Options","Create New Account Options" +"Enable Automatic Assignment to Customer Group","Enable Automatic Assignment to Customer Group" +"Tax Calculation Based On","Tax Calculation Based On" +"Default Group","Default Group" +"Group for Valid VAT ID - Domestic","Group for Valid VAT ID - Domestic" +"Group for Valid VAT ID - Intra-Union","Group for Valid VAT ID - Intra-Union" +"Group for Invalid VAT ID","Group for Invalid VAT ID" +"Validation Error Group","Validation Error Group" +"Validate on Each Transaction","Validate on Each Transaction" +"Default Value for Disable Automatic Group Changes Based on VAT ID","Default Value for Disable Automatic Group Changes Based on VAT ID" +"Show VAT Number on Storefront","Show VAT Number on Storefront" "To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes.","To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes." -Default Email Domain,Default Email Domain -Default Welcome Email,Default Welcome Email +"Default Email Domain","Default Email Domain" +"Default Welcome Email","Default Welcome Email" "Email template chosen based on theme fallback when ""Default"" option is selected.","Email template chosen based on theme fallback when ""Default"" option is selected." -Default Welcome Email Without Password,Default Welcome Email Without Password +"Default Welcome Email Without Password","Default Welcome Email Without Password" " This email will be sent instead of the Default Welcome Email, if a customer was created without password.

Email template chosen based on theme fallback when ""Default"" option is selected. @@ -443,10 +443,10 @@ Default Welcome Email Without Password,Default Welcome Email Without Password This email will be sent instead of the Default Welcome Email, if a customer was created without password.

Email template chosen based on theme fallback when ""Default"" option is selected. " -Email Sender,Email Sender -Require Emails Confirmation,Require Emails Confirmation -Confirmation Link Email,Confirmation Link Email -Welcome Email,Welcome Email +"Email Sender","Email Sender" +"Require Emails Confirmation","Require Emails Confirmation" +"Confirmation Link Email","Confirmation Link Email" +"Welcome Email","Welcome Email" " This email will be sent instead of the Default Welcome Email, after account confirmation.

Email template chosen based on theme fallback when ""Default"" option is selected. @@ -454,88 +454,88 @@ Welcome Email,Welcome Email This email will be sent instead of the Default Welcome Email, after account confirmation.

Email template chosen based on theme fallback when ""Default"" option is selected. " -Generate Human-Friendly Customer ID,Generate Human-Friendly Customer ID -Password Options,Password Options -Forgot Email Template,Forgot Email Template -Remind Email Template,Remind Email Template -Reset Password Template,Reset Password Template -Password Template Email Sender,Password Template Email Sender -Recovery Link Expiration Period (hours),Recovery Link Expiration Period (hours) -Please enter a number 1 or greater in this field.,Please enter a number 1 or greater in this field. -Number of Required Character Classes,Number of Required Character Classes +"Generate Human-Friendly Customer ID","Generate Human-Friendly Customer ID" +"Password Options","Password Options" +"Forgot Email Template","Forgot Email Template" +"Remind Email Template","Remind Email Template" +"Reset Password Template","Reset Password Template" +"Password Template Email Sender","Password Template Email Sender" +"Recovery Link Expiration Period (hours)","Recovery Link Expiration Period (hours)" +"Please enter a number 1 or greater in this field.","Please enter a number 1 or greater in this field." +"Number of Required Character Classes","Number of Required Character Classes" "Number of different character classes required in password: Lowercase, Uppercase, Digits, Special Characters.","Number of different character classes required in password: Lowercase, Uppercase, Digits, Special Characters." -Minimum Password Length,Minimum Password Length -Maximum Login Failures to Lockout Account,Maximum Login Failures to Lockout Account -Use 0 to disable account locking.,Use 0 to disable account locking. -Lockout Time (minutes),Lockout Time (minutes) -Enable Autocomplete on login/forgot password forms,Enable Autocomplete on login/forgot password forms -Account Information Options,Account Information Options -Change Email Template,Change Email Template -Change Email and Password Template,Change Email and Password Template -Name and Address Options,Name and Address Options -Number of Lines in a Street Address,Number of Lines in a Street Address -Leave empty for default (2). Valid range: 1-4,Leave empty for default (2). Valid range: 1-4 -Show Prefix,Show Prefix +"Minimum Password Length","Minimum Password Length" +"Maximum Login Failures to Lockout Account","Maximum Login Failures to Lockout Account" +"Use 0 to disable account locking.","Use 0 to disable account locking." +"Lockout Time (minutes)","Lockout Time (minutes)" +"Enable Autocomplete on login/forgot password forms","Enable Autocomplete on login/forgot password forms" +"Account Information Options","Account Information Options" +"Change Email Template","Change Email Template" +"Change Email and Password Template","Change Email and Password Template" +"Name and Address Options","Name and Address Options" +"Number of Lines in a Street Address","Number of Lines in a Street Address" +"Leave empty for default (2). Valid range: 1-4","Leave empty for default (2). Valid range: 1-4" +"Show Prefix","Show Prefix" "The title that goes before name (Mr., Mrs., etc.)","The title that goes before name (Mr., Mrs., etc.)" -Prefix Dropdown Options,Prefix Dropdown Options +"Prefix Dropdown Options","Prefix Dropdown Options" " Semicolon (;) separated values.
Leave empty for open text field. "," Semicolon (;) separated values.
Leave empty for open text field. " -Show Middle Name (initial),Show Middle Name (initial) -Always optional.,Always optional. -Show Suffix,Show Suffix +"Show Middle Name (initial)","Show Middle Name (initial)" +"Always optional.","Always optional." +"Show Suffix","Show Suffix" "The suffix that goes after name (Jr., Sr., etc.)","The suffix that goes after name (Jr., Sr., etc.)" -Suffix Dropdown Options,Suffix Dropdown Options -Show Date of Birth,Show Date of Birth -Show Tax/VAT Number,Show Tax/VAT Number -Show Gender,Show Gender -Show Telephone,Show Telephone -Show Company,Show Company -Show Fax,Show Fax -Login Options,Login Options -Redirect Customer to Account Dashboard after Logging in,Redirect Customer to Account Dashboard after Logging in +"Suffix Dropdown Options","Suffix Dropdown Options" +"Show Date of Birth","Show Date of Birth" +"Show Tax/VAT Number","Show Tax/VAT Number" +"Show Gender","Show Gender" +"Show Telephone","Show Telephone" +"Show Company","Show Company" +"Show Fax","Show Fax" +"Login Options","Login Options" +"Redirect Customer to Account Dashboard after Logging in","Redirect Customer to Account Dashboard after Logging in" "Customer will stay on the current page if ""No"" is selected.","Customer will stay on the current page if ""No"" is selected." -Address Templates,Address Templates -Online Customers Options,Online Customers Options -Online Minutes Interval,Online Minutes Interval +"Address Templates","Address Templates" +"Online Customers Options","Online Customers Options" +"Online Minutes Interval","Online Minutes Interval" "Only 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub', 'sup', 'ul' tags are allowed","Only 'b', 'br', 'em', 'i', 'li', 'ol', 'p', 'strong', 'sub', 'sup', 'ul' tags are allowed" -Leave empty for default (15 minutes).,Leave empty for default (15 minutes). -Customer Notification,Customer Notification -Customer Grid,Customer Grid -Rebuild Customer grid index,Rebuild Customer grid index +"Leave empty for default (15 minutes).","Leave empty for default (15 minutes)." +"Customer Notification","Customer Notification" +"Customer Grid","Customer Grid" +"Rebuild Customer grid index","Rebuild Customer grid index" Group,Group -Add New Customer,Add New Customer -Are you sure you want to delete the selected customers?,Are you sure you want to delete the selected customers? -Delete items,Delete items -Subscribe to Newsletter,Subscribe to Newsletter -Are you sure you want to unsubscribe the selected customers from the newsletter?,Are you sure you want to unsubscribe the selected customers from the newsletter? -Unsubscribe from Newsletter,Unsubscribe from Newsletter -Assign a Customer Group,Assign a Customer Group +"Add New Customer","Add New Customer" +"Are you sure you want to delete the selected customers?","Are you sure you want to delete the selected customers?" +"Delete items","Delete items" +"Subscribe to Newsletter","Subscribe to Newsletter" +"Are you sure you want to unsubscribe the selected customers from the newsletter?","Are you sure you want to unsubscribe the selected customers from the newsletter?" +"Unsubscribe from Newsletter","Unsubscribe from Newsletter" +"Assign a Customer Group","Assign a Customer Group" Phone,Phone ZIP,ZIP -Customer Since,Customer Since -Confirmed email,Confirmed email -Account Created in,Account Created in -Tax VAT Number,Tax VAT Number -Billing Firstname,Billing Firstname -Billing Lastname,Billing Lastname -Account Lock,Account Lock -First Name,First Name -Last Name,Last Name -Last Activity,Last Activity +"Customer Since","Customer Since" +"Confirmed email","Confirmed email" +"Account Created in","Account Created in" +"Tax VAT Number","Tax VAT Number" +"Billing Firstname","Billing Firstname" +"Billing Lastname","Billing Lastname" +"Account Lock","Account Lock" +"First Name","First Name" +"Last Name","Last Name" +"Last Activity","Last Activity" Type,Type -Customer Information,Customer Information +"Customer Information","Customer Information" "If your Magento installation has multiple websites, you can edit the scope to associate the customer with a specific site.","If your Magento installation has multiple websites, you can edit the scope to associate the customer with a specific site." -Disable Automatic Group Change Based on VAT ID,Disable Automatic Group Change Based on VAT ID -Send Welcome Email From,Send Welcome Email From -Are you sure you want to delete this item?,Are you sure you want to delete this item? +"Disable Automatic Group Change Based on VAT ID","Disable Automatic Group Change Based on VAT ID" +"Send Welcome Email From","Send Welcome Email From" +"Are you sure you want to delete this item?","Are you sure you want to delete this item?" Addresses,Addresses -Edit Account Information,Edit Account Information -Password forgotten,Password forgotten -You are signed out,You are signed out -Associate to Website,Associate to Website -Prefix,Prefix -Middle Name/Initial,Middle Name/Initial -Suffix,Suffix +"Edit Account Information","Edit Account Information" +"Password forgotten","Password forgotten" +"You are signed out","You are signed out" +"Associate to Website","Associate to Website" +"Prefix","Prefix" +"Middle Name/Initial","Middle Name/Initial" +"Suffix","Suffix" From 6dcdd8af62b234b8c14cf221757b3489822188e2 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Sat, 21 Sep 2019 17:18:10 +0530 Subject: [PATCH 023/369] Discard NonComposerComponentRegistration file --- app/etc/NonComposerComponentRegistration.php | 33 -------------------- 1 file changed, 33 deletions(-) delete mode 100755 app/etc/NonComposerComponentRegistration.php diff --git a/app/etc/NonComposerComponentRegistration.php b/app/etc/NonComposerComponentRegistration.php deleted file mode 100755 index a7377ebfca3af..0000000000000 --- a/app/etc/NonComposerComponentRegistration.php +++ /dev/null @@ -1,33 +0,0 @@ - Date: Sat, 21 Sep 2019 17:25:30 +0530 Subject: [PATCH 024/369] discard files --- app/etc/db_schema.xml | 17 - app/etc/di.xml | 1784 ----------------------- app/etc/registration_globlist.php | 19 - app/etc/vendor_path.php | 5 - generated/.htaccess | 8 - pub/media/.htaccess | 134 -- pub/media/customer/.htaccess | 8 - pub/media/downloadable/.htaccess | 8 - pub/media/import/.htaccess | 8 - pub/media/theme_customization/.htaccess | 10 - pub/static/.htaccess | 133 -- var/.htaccess | 8 - 12 files changed, 2142 deletions(-) delete mode 100755 app/etc/db_schema.xml delete mode 100755 app/etc/di.xml delete mode 100755 app/etc/registration_globlist.php delete mode 100755 app/etc/vendor_path.php delete mode 100755 generated/.htaccess delete mode 100755 pub/media/.htaccess delete mode 100755 pub/media/customer/.htaccess delete mode 100755 pub/media/downloadable/.htaccess delete mode 100755 pub/media/import/.htaccess delete mode 100755 pub/media/theme_customization/.htaccess delete mode 100755 pub/static/.htaccess delete mode 100755 var/.htaccess diff --git a/app/etc/db_schema.xml b/app/etc/db_schema.xml deleted file mode 100755 index d7af9091b238d..0000000000000 --- a/app/etc/db_schema.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - -
-
diff --git a/app/etc/di.xml b/app/etc/di.xml deleted file mode 100755 index 882d1be623988..0000000000000 --- a/app/etc/di.xml +++ /dev/null @@ -1,1784 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - system/currency/installed - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Magento\Framework\Acl\Builder\Proxy - - - - - Magento\Framework\Filesystem\Driver\File - - - - - Magento\Framework\Filesystem\Driver\File - - - - - - - - Magento\WebapiAsync\Code\Generator\Config\RemoteServiceReader\Communication - 0 - - - Magento\Framework\Communication\Config\Reader\XmlReader - 10 - - - Magento\Framework\Communication\Config\Reader\EnvReader - 20 - - - Magento\Framework\MessageQueue\Code\Generator\Config\RemoteServiceReader\Communication - 5 - - - - - - - main - - Magento\Framework\Logger\Handler\System - Magento\Framework\Logger\Handler\Debug - Magento\Framework\Logger\Handler\Syslog - - - - - - Magento - - - - - Magento\Framework\Model\ActionValidator\RemoveAction\Proxy - - - - - - - - - Magento\Framework\Message\Session\Proxy - Magento\Framework\Message\ExceptionMessageLookupFactory - - - - - - Magento\Backend\App\Request\PathInfoProcessor\Proxy - - - - - Magento\Framework\Session\Config\ConfigInterface\Proxy - - - - - - - Magento\Framework\Session\SaveHandler\DbTable - Magento\Framework\Session\SaveHandler\Redis - - - - - - - Magento\Framework\App\Feed - - - - - - Cm\RedisSession\Handler\ConfigInterface - Cm\RedisSession\Handler\LoggerInterface - - - - - global - - - - - Magento\Framework\App\State::PARAM_MODE - - - - - Magento\Framework\App\State::PARAM_MODE - - - - - Magento\Framework\App\State::PARAM_MODE - - - - - Magento\Framework\App\State::PARAM_MODE - - - - - Magento\Framework\App\Cache\Frontend\Factory::PARAM_CACHE_FORCED_OPTIONS - - - Magento\Framework\Cache\Frontend\Decorator\TagScope - - MAGE - - - - Magento\Framework\Cache\Frontend\Decorator\Logger - - - Magento\Framework\App\ResourceConnection\Proxy - - - - - Magento\Backend\Setup\ConfigOptionsList::CONFIG_PATH_BACKEND_FRONTNAME - - - - - Magento\Framework\App\Cache\State::PARAM_BAN_CACHE - - - - - Magento\Store\Model\StoreManager::PARAM_RUN_CODE - Magento\Store\Model\StoreManager::PARAM_RUN_TYPE - - - - - Magento\Framework\App\Cache\Type\Translate - Magento\Framework\Locale\Resolver\Proxy - Magento\Framework\Translate\ResourceInterface\Proxy - Magento\Framework\App\Request\Http\Proxy - - - - - Magento\Framework\Translate\InlineInterface\Proxy - - - - - - - Magento\Store\Model\StoreManagerInterface\Proxy - - - - - - Magento\Framework\App\Cache\Type\Config - Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy - - - - - Magento\Framework\App\Cache\Type\Config - - - - - config_cache - Magento\Framework\Cache\Config\Reader\Proxy - - - - - Magento\Framework\App\Cache\Type\Config - Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy - interception - - - - - Magento\Framework\App\Cache\Type\Config - - - - - Magento\Framework\App\Cache\Type\Config - Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy - plugin-list - - global - - - - - - Magento\Framework\App\ResourceConnection\ConnectionFactory - - - - - Magento\Framework\App\ResourceConnection\Config\Reader\Proxy - Magento\Framework\App\Cache\Type\Config\Proxy - - - - - Magento\Framework\App\Config\FileResolver\Proxy - - - - - primary - Magento\Framework\App\AreaList\Proxy - - - - - Magento\Framework\Session\Generic\Proxy - Magento\Store\Model\ScopeInterface::SCOPE_STORE - - - - - - Magento\Framework\View\Layout\Argument\Interpreter\Options - layoutArrayArgumentReaderInterpreterProxy - Magento\Framework\Data\Argument\Interpreter\Boolean - Magento\Framework\Data\Argument\Interpreter\Number - Magento\Framework\Data\Argument\Interpreter\StringUtils - Magento\Framework\Data\Argument\Interpreter\NullType - Magento\Framework\View\Layout\Argument\Interpreter\Passthrough - Magento\Framework\View\Layout\Argument\Interpreter\Passthrough - Magento\Framework\View\Layout\Argument\Interpreter\Passthrough - - Magento\Framework\View\Model\Layout\Merge::TYPE_ATTRIBUTE - - - - - - Magento\Framework\View\Layout\Argument\Interpreter\Options - layoutArrayArgumentGeneratorInterpreterProxy - Magento\Framework\Data\Argument\Interpreter\Boolean - Magento\Framework\Data\Argument\Interpreter\Number - Magento\Framework\Data\Argument\Interpreter\StringUtils - Magento\Framework\Data\Argument\Interpreter\NullType - layoutObjectArgumentInterpreter - Magento\Framework\View\Layout\Argument\Interpreter\Url - Magento\Framework\View\Layout\Argument\Interpreter\HelperMethod - - Magento\Framework\View\Model\Layout\Merge::TYPE_ATTRIBUTE - - - - - layoutArgumentGeneratorInterpreterInternal - - - - - layoutArgumentReaderInterpreter - - - - - layoutArgumentGeneratorInterpreterInternal - - - - - - layoutArrayArgumentReaderInterpreter - - - - - layoutArrayArgumentGeneratorInterpreter - - - - - Magento\Framework\View\Element\Block\ArgumentInterface - - - - - Magento\Framework\Data\Argument\Interpreter\StringUtils - - - - - - Magento\Framework\View\Layout\Reader\Container - Magento\Framework\View\Layout\Reader\Block - Magento\Framework\View\Layout\Reader\UiComponent - - - - - - containerRenderPool - - - - - - Magento\Framework\View\Layout\Reader\Container - Magento\Framework\View\Layout\Reader\Block - Magento\Framework\View\Layout\Reader\Move - Magento\Framework\View\Layout\Reader\UiComponent - - - - - - blockRenderPool - Magento\Store\Model\ScopeInterface::SCOPE_STORE - layoutArgumentReaderInterpreter - - - - - blockRenderPool - - - - - Magento\Store\Model\ScopeInterface::SCOPE_STORE - - - - - - Magento\Framework\View\Layout\Reader\Container - Magento\Framework\View\Layout\Reader\Block - Magento\Framework\View\Layout\Reader\Move - Magento\Framework\View\Layout\Reader\UiComponent - - - - - - bodyRenderPool - - - - - - Magento\Framework\View\Page\Config\Reader\Html - Magento\Framework\View\Page\Config\Reader\Head - Magento\Framework\View\Page\Config\Reader\Body - Magento\Framework\View\Layout\Reader\Container - Magento\Framework\View\Layout\Reader\Block - Magento\Framework\View\Layout\Reader\Move - Magento\Framework\View\Layout\Reader\UiComponent - - - - - - commonRenderPool - Magento\Framework\App\Cache\Type\Layout - - - - - - Magento\Framework\View\Layout\Reader\Container - Magento\Framework\View\Layout\Reader\Block - Magento\Framework\View\Layout\Reader\Move - Magento\Framework\View\Layout\Reader\UiComponent - - - - - - genericLayoutRenderPool - - - - - - Magento\Framework\View\Page\Config\Reader\Html - Magento\Framework\View\Page\Config\Reader\Head - Magento\Framework\View\Page\Config\Reader\Body - - - - - - - Magento\Framework\View\Page\Config\Generator\Head - Magento\Framework\View\Page\Config\Generator\Body - Magento\Framework\View\Layout\Generator\Block - Magento\Framework\View\Layout\Generator\Container - Magento\Framework\View\Layout\Generator\UiComponent - - - - - - - Magento\Framework\View\Page\Config\Generator\Head - Magento\Framework\View\Page\Config\Generator\Body - Magento\Framework\View\Layout\Generator\Block - Magento\Framework\View\Layout\Generator\Container - Magento\Framework\View\Layout\Generator\UiComponent - - - - - - pageConfigRenderPool - pageLayoutGeneratorPool - Magento_Theme::root.phtml - - - - - layoutArgumentGeneratorInterpreter - - - - - Magento\Indexer\Model\Mview\View\State - Magento\Framework\Mview\View\Changelog - - - - - Magento\Framework\Mview\Config\Data\Proxy - - - - - Magento\Framework\Mview\View\State\CollectionInterface - - - - - - - AlternativeSourceProcessors - - - - - - Magento\MediaStorage\Model\File\Storage\Response - developerPublisher - - - - - Magento\Framework\View\Asset\PreProcessor\MinificationFilenameResolver - alternative-source-css - Magento\Framework\View\Asset\LockerProcess - - - Magento\Framework\Css\PreProcessor\Adapter\Less\Processor - - - - - - - developerMaterialization - - - - - - Magento\Framework\App\View\Asset\MaterializationStrategy\Symlink - Magento\Framework\App\View\Asset\MaterializationStrategy\Copy - - - - - - Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple - - - - - Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple - - - - - Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple - - - - - - viewFileFallbackResolver - - - - - - viewFileMinifiedFallbackResolver - - - - - - \Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceFactoryGenerator - \Magento\Framework\ObjectManager\Code\Generator\Factory - \Magento\Framework\ObjectManager\Code\Generator\Proxy - \Magento\Framework\Interception\Code\Generator\Interceptor - \Magento\Framework\ObjectManager\Profiler\Code\Generator\Logger - \Magento\Framework\Api\Code\Generator\Mapper - \Magento\Framework\ObjectManager\Code\Generator\Persistor - \Magento\Framework\ObjectManager\Code\Generator\Repository - \Magento\Framework\ObjectManager\Code\Generator\Converter - \Magento\Framework\Api\Code\Generator\SearchResults - \Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator - \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator - \Magento\Framework\MessageQueue\Code\Generator\RemoteServiceGenerator - \Magento\Framework\Async\Code\Generator\ProxyDeferredGenerator - - - - - - - - - page_cache - - - - - - - - - page_cache - - - - - - Magento\Framework\Translate\Inline\ParserInterface\Proxy - - - - - - - - Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder\Term - Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder\Range - Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder\Dynamic - - - - - - - Magento\Framework\Search\Dynamic\Algorithm\Auto - Magento\Framework\Search\Dynamic\Algorithm\Manual - Magento\Framework\Search\Dynamic\Algorithm\Improved - - - - - - Magento\Framework\View\Layout\File\Collector\Aggregated\Proxy - pageLayoutFileCollectorAggregated - Magento\Framework\App\Cache\Type\Layout - Magento\Framework\View\Layout\LayoutCacheKeyInterface - - - - - false - - - - - - core - index - index - - - - - - Magento\Framework\App\Cache\Type\Collection - collection_ - 86400 - - - - - Magento\Framework\Event\Config\Reader\Proxy - - - - - - layout - - - - - layoutFileSourceBase - - - - - layoutFileSourceBaseFiltered - - - - - - layout - - - - - layoutFileSourceTheme - - - - - layoutFileSourceThemeFiltered - - - - - - layout/override/base - - - - - layoutFileSourceOverrideBase - - - - - layoutFileSourceOverrideBaseFiltered - - - - - - layout/override/theme - - - - - layoutFileSourceOverrideTheme - - - - - layoutFileSourceOverrideThemeFiltered - - - - - layoutFileSourceBaseSorted - layoutFileSourceThemeSorted - layoutFileSourceOverrideBaseSorted - layoutFileSourceOverrideThemeSorted - - - - - page_layout - - - - - pageLayoutFileSourceBase - - - - - pageLayoutFileSourceBaseFiltered - - - - - page_layout - - - - - pageLayoutFileSourceTheme - - - - - pageLayoutFileSourceThemeFiltered - - - - - - page_layout/override/base - - - - - pageLayoutFileSourceOverrideBase - - - - - pageLayoutFileSourceOverrideBaseFiltered - - - - - - page_layout/override/theme - - - - - pageLayoutFileSourceOverrideTheme - - - - - pageLayoutFileSourceOverrideThemeFiltered - - - - - - pageLayoutFileSourceBaseSorted - pageLayoutFileSourceThemeSorted - pageLayoutFileSourceOverrideBaseSorted - pageLayoutFileSourceOverrideThemeSorted - - - - - - - pageFileSourceBase - - - - - pageFileSourceBaseFiltered - - - - - - - pageFileSourceTheme - - - - - pageFileSourceThemeFiltered - - - - - - page/override - - - - - pageFileSourceOverrideBase - - - - - pageFileSourceOverrideBaseFiltered - - - - - - override/theme - - - - - pageFileSourceOverrideTheme - - - - - pageFileSourceOverrideThemeFiltered - - - - - - Magento\Framework\View\Layout\Reader\Container - Magento\Framework\View\Layout\Reader\Move - - - - - - pageLayoutFileCollectorAggregated - pageLayoutRenderPool - - - - - pageFileSourceBaseSorted - pageFileSourceThemeSorted - pageFileSourceOverrideBaseSorted - pageFileSourceOverrideThemeSorted - - - - - Magento\Framework\View\Design\Theme\Image\Uploader\Proxy - - - - - Magento\Framework\App\Config\Initial\Reader\Proxy - - - - - Magento\Framework\App\Config\Initial\Converter - - - - - Magento\Framework\App\Route\Config\Reader\Proxy - - - - - Magento\Store\Model\ScopeInterface::SCOPE_STORE - - Shockwave Flash - - - - - - - Magento\Framework\DataObject\Copy\Config\Data\Proxy - - - - - fieldset.xml - Magento\Framework\DataObject\Copy\Config\SchemaLocator - - - - - urn:magento:framework:DataObject/etc/fieldset.xsd - urn:magento:framework:DataObject/etc/fieldset_file.xsd - - - - - Magento\Framework\DataObject\Copy\Config\Reader\Proxy - fieldset_config - - - - - Magento\Framework\Image\Adapter\Gd2 - - - - - page_types.xml - Magento\Framework\View\Layout\PageType\Config\Converter - Magento\Framework\View\Layout\PageType\Config\SchemaLocator - frontend - - - - - Magento\Framework\View\Layout\PageType\Config\Reader - page_types_config - - - - - Magento\Framework\View\Layout\PageType\Config\Data - - - - - message - - - - - Magento\Framework\Message\Session\Storage - - - - - frontend - - - - - - Magento\Framework\Filesystem\Driver\File - - - - - - [a-z]+[_a-z\d]*?\/[a-z]+[_a-z\d]*?)::.*?$/sui]]> - [a-z]+[_a-z\d]*?\/[a-z]+[_a-z\d]*?)\".*?}}/sui]]> - [a-z]+[_a-z\d]*?\/[a-z]+[_a-z\d]*?)\".*?>/sui]]> - s:\d+:"(?P[a-z]+[_a-z\d]*?/[a-z]+[_a-z\d]*?)")#sui]]> - - - - - - - - application/json - Magento\Framework\Webapi\Rest\Request\Deserializer\Json - - - application/xml - Magento\Framework\Webapi\Rest\Request\Deserializer\Xml - - - application/xhtml+xml - Magento\Framework\Webapi\Rest\Request\Deserializer\Xml - - - text/xml - Magento\Framework\Webapi\Rest\Request\Deserializer\Xml - - - - - - - Magento\Framework\App\Cache\Type\Config - - - - - - Magento\Framework\Reflection\ExtensionAttributesProcessor\Proxy - Magento\Framework\Reflection\CustomAttributesProcessor\Proxy - - - - - Magento\Framework\UrlInterface - - - - - - - view.xml - Magento\Framework\Config\Converter - Magento\Framework\Config\SchemaLocator - Magento\Framework\Config\FileResolver - - - - - Magento\Framework\DB\Select\RendererProxy - - - - - Magento\Framework\Locale\ResolverInterface - - - - - - - Magento\Framework\DB\Select\DistinctRenderer - 100 - distinct - - - Magento\Framework\DB\Select\ColumnsRenderer - 200 - columns - - - Magento\Framework\DB\Select\UnionRenderer - 300 - union - - - Magento\Framework\DB\Select\FromRenderer - 400 - from - - - Magento\Framework\DB\Select\WhereRenderer - 500 - where - - - Magento\Framework\DB\Select\GroupRenderer - 600 - group - - - Magento\Framework\DB\Select\HavingRenderer - 700 - having - - - Magento\Framework\DB\Select\OrderRenderer - 800 - order - - - Magento\Framework\DB\Select\LimitRenderer - 900 - limitcount - - - Magento\Framework\DB\Select\ForUpdateRenderer - 1000 - forupdate - - - - - - - - - Magento\Framework\EntityManager\Operation\CheckIfExists - Magento\Framework\EntityManager\Operation\Read - Magento\Framework\EntityManager\Operation\Create - Magento\Framework\EntityManager\Operation\Update - Magento\Framework\EntityManager\Operation\Delete - - - - - - - - Magento\Framework\App\Cache\Type\Block::TYPE_IDENTIFIER - Magento\Framework\App\Cache\Type\Collection::TYPE_IDENTIFIER - - - - - - - Magento\Framework\EntityManager\Mapper - - - - - - - - Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor - Magento\Framework\Api\SearchCriteria\CollectionProcessor\SortingProcessor - Magento\Framework\Api\SearchCriteria\CollectionProcessor\PaginationProcessor - - - - - - - Magento\Framework\DB\Select\InQueryModifier - Magento\Framework\DB\Select\LikeQueryModifier - Magento\Framework\DB\Select\CompositeQueryModifier - - - - - - - HASH - BTREE - - - INNODB - MEMORY - MYISAM - - - - - - Magento\Framework\DB\FieldDataConverter::BATCH_SIZE_VARIABLE_NAME - - - - - - - true - - - - - - - Magento\Framework\View\Asset\PreProcessor\Passthrough - - - - - Magento\Framework\App\Filesystem\DirectoryList::STATIC_VIEW - deployed_version.txt - - - - - Magento\Directory\Helper\Data::XML_PATH_DEFAULT_LOCALE - Magento\Framework\App\ScopeInterface::SCOPE_DEFAULT - - - - - - Magento\Framework\View\Element\Message\Renderer\EscapeRenderer - Magento\Framework\View\Element\Message\Renderer\BlockRenderer - - - - - - - - \Magento\Framework\View\Element\Message\Renderer\EscapeRenderer::CODE - - - - - - - Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_OUTPUT - Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_LOG_EVERYTHING - Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_QUERY_TIME_THRESHOLD - Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_INCLUDE_STACKTRACE - - - - - Magento\Config\App\Config\Source\EnvironmentConfigSource - - - - - Magento\Framework\Message\ExceptionMessageFactory - - - - - - updated_at - - - - - - - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Table - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Real - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Real - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Real - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Integer - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Integer - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Integer - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Integer - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Date - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Timestamp - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Timestamp - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\LongText - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\MediumText - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Text - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\StringBinary - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\StringBinary - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Blob - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\MediumBlob - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\LongBlob - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Boolean - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Unique - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Primary - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Foreign - \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Index - - - - - - - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Boolean - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Integer - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Integer - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Integer - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Integer - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Real - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Real - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Real - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Timestamp - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Date - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Timestamp - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\StringBinary - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\StringBinary - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\StringBinary - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\StringBinary - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Index - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Constraints\Internal - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Constraints\Internal - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Constraints\Internal - \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Constraints\ForeignKey - - - - - - - Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DDL\Triggers\MigrateDataFrom - - - - - - - Magento\Framework\Setup\Declaration\Schema\FileSystem\XmlReader - - - - - - urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd - - - - - Magento\Framework\Config\FileResolverByModule - Magento\Framework\Setup\Declaration\Schema\Config\Converter - Magento\Framework\Setup\Declaration\Schema\Config\SchemaLocator - db_schema.xml - - name - name - referenceId - referenceId - name - name - - - - - - - Magento\Framework\Setup\Declaration\Schema\Operations\ReCreateTable - Magento\Framework\Setup\Declaration\Schema\Operations\CreateTable - Magento\Framework\Setup\Declaration\Schema\Operations\DropTable - Magento\Framework\Setup\Declaration\Schema\Operations\DropReference - Magento\Framework\Setup\Declaration\Schema\Operations\ModifyColumn - Magento\Framework\Setup\Declaration\Schema\Operations\AddColumn - Magento\Framework\Setup\Declaration\Schema\Operations\DropElement - Magento\Framework\Setup\Declaration\Schema\Operations\AddComplexElement - Magento\Framework\Setup\Declaration\Schema\Operations\ModifyTable - - - Magento\Framework\Setup\Declaration\Schema\DataSavior\TableSavior - Magento\Framework\Setup\Declaration\Schema\DataSavior\ColumnSavior - - - - - - - default - - - - - - - Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\CheckReferenceColumnHasIndex - Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\RealTypes - Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\PrimaryKeyCanBeCreated - Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\IncosistentReferenceDefinition - Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\AutoIncrementColumnValidation - - - - - - - Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition - Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition - Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition - Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition - Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition - Magento\Framework\Setup\SchemaListenerDefinition\RealDefinition - Magento\Framework\Setup\SchemaListenerDefinition\RealDefinition - Magento\Framework\Setup\SchemaListenerDefinition\RealDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TimestampDefinition - Magento\Framework\Setup\SchemaListenerDefinition\TimestampDefinition - Magento\Framework\Setup\SchemaListenerDefinition\DateDefinition - Magento\Framework\Setup\SchemaListenerDefinition\BooleanDefinition - - - - - - schema - - - - - data - - - - - \Magento\Framework\Setup\Patch\DataPatchReader - \Magento\Framework\Setup\Patch\SchemaPatchReader - - - - - \Magento\Framework\Setup\Patch\DataPatchReader - - - - - \Magento\Framework\Setup\Patch\SchemaPatchReader - - - - - - - Magento\Framework\MessageQueue\Config\Reader\Xml - 10 - - - Magento\Framework\MessageQueue\Config\Reader\Env - 20 - - - - - - - - - Magento\Framework\MessageQueue\Config\Reader\Xml\Converter\TopicConfig - 20 - - - - - - - Magento\Framework\MessageQueue\Consumer\Config\CompositeReader - - - - - - Magento\Framework\MessageQueue\Consumer\Config\Xml\Reader - Magento\Framework\MessageQueue\Consumer\Config\Env\Reader - - - - - - - Magento\Framework\MessageQueue\Consumer\Config\Validator\RequiredFields - Magento\Framework\MessageQueue\Consumer\Config\Validator\FieldsTypes - Magento\Framework\MessageQueue\Consumer\Config\Validator\Handlers - Magento\Framework\MessageQueue\Consumer\Config\Validator\ConsumerInstance - - - - - - - Magento\Framework\MessageQueue\Publisher\Config\Validator\Format - Magento\Framework\MessageQueue\Publisher\Config\Validator\EnabledConnection - - - - - - - Magento\WebapiAsync\Code\Generator\Config\RemoteServiceReader\Publisher - Magento\Framework\MessageQueue\Publisher\Config\RemoteService\Reader - Magento\Framework\MessageQueue\Publisher\Config\Xml\Reader - Magento\Framework\MessageQueue\Publisher\Config\Env\Reader - - - - - - - Magento\Framework\MessageQueue\Topology\Config\Validator\Format - Magento\Framework\MessageQueue\Topology\Config\Validator\FieldsTypes - Magento\Framework\MessageQueue\Topology\Config\Validator\DependentFields - - - - - - - Magento\Framework\MessageQueue\Topology\Config\RemoteService\Reader - Magento\Framework\MessageQueue\Topology\Config\Xml\Reader - - - - - - - Magento\Framework\Amqp\Topology\BindingInstallerType\Queue - Magento\Framework\Amqp\Topology\BindingInstallerType\Exchange - - - - - - - magento - magento-db - - - - - - - amqp - db - - - - - - - Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DDL\Triggers\MigrateDataFromAnotherTable - - - - - - - - Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT - - - - - - - CsrfRequestValidator - - Magento\Framework\App\Request\HttpMethodValidator - - - - - - - - - \Magento\Framework\App\Action\HttpOptionsActionInterface - \Magento\Framework\App\Action\HttpGetActionInterface - \Magento\Framework\App\Action\HttpGetActionInterface - \Magento\Framework\App\Action\HttpPostActionInterface - \Magento\Framework\App\Action\HttpPutActionInterface - \Magento\Framework\App\Action\HttpPatchActionInterface - \Magento\Framework\App\Action\HttpDeleteActionInterface - \Magento\Framework\App\Action\HttpConnectActionInterface - \Magento\Framework\App\Action\HttpPropfindActionInterface - \Magento\Framework\App\Action\HttpTraceActionInterface - - - - - - - Magento\Framework\App\ScopeResolver - - - - - - Magento\Framework\Lock\Backend\Cache - 10000 - 20 - - - - - - - - - - - - - - diff --git a/app/etc/registration_globlist.php b/app/etc/registration_globlist.php deleted file mode 100755 index 23caae00cb303..0000000000000 --- a/app/etc/registration_globlist.php +++ /dev/null @@ -1,19 +0,0 @@ - - order allow,deny - deny from all - -= 2.4> - Require all denied - - diff --git a/pub/media/.htaccess b/pub/media/.htaccess deleted file mode 100755 index d68d163d7a6b5..0000000000000 --- a/pub/media/.htaccess +++ /dev/null @@ -1,134 +0,0 @@ -Options -Indexes - - -php_flag engine 0 - - - -php_flag engine 0 - - -AddHandler cgi-script .php .pl .py .jsp .asp .htm .shtml .sh .cgi -Options -ExecCGI - - -SetHandler default-handler - - - - -############################################ -## enable rewrites - - Options +FollowSymLinks - RewriteEngine on - - ## you can put here your pub/media folder path relative to web root - #RewriteBase /magento/pub/media/ - -############################################ -## never rewrite for existing files - RewriteCond %{REQUEST_FILENAME} !-f - -############################################ -## rewrite everything else to get.php - - RewriteRule .* ../get.php [L] - - -############################################ -## setting MIME types - -# JavaScript -AddType application/javascript js jsonp -AddType application/json json - -# CSS -AddType text/css css - -# Images and icons -AddType image/x-icon ico -AddType image/gif gif -AddType image/png png -AddType image/jpeg jpg -AddType image/jpeg jpeg - -# SVG -AddType image/svg+xml svg - -# Fonts -AddType application/vnd.ms-fontobject eot -AddType application/x-font-ttf ttf -AddType application/x-font-otf otf -AddType application/x-font-woff woff -AddType application/font-woff2 woff2 - -# Flash -AddType application/x-shockwave-flash swf - -# Archives and exports -AddType application/zip gzip -AddType application/x-gzip gz gzip -AddType application/x-bzip2 bz2 -AddType text/csv csv -AddType application/xml xml - - - - - Header append Cache-Control public - - - - Header append Cache-Control no-store - - - - - - -############################################ -## Add default Expires header -## http://developer.yahoo.com/performance/rules.html#expires - - ExpiresActive On - - # Data - - ExpiresDefault "access plus 0 seconds" - - ExpiresByType text/xml "access plus 0 seconds" - ExpiresByType text/csv "access plus 0 seconds" - ExpiresByType application/json "access plus 0 seconds" - ExpiresByType application/zip "access plus 0 seconds" - ExpiresByType application/x-gzip "access plus 0 seconds" - ExpiresByType application/x-bzip2 "access plus 0 seconds" - - # CSS, JavaScript - - ExpiresDefault "access plus 1 year" - - ExpiresByType text/css "access plus 1 year" - ExpiresByType application/javascript "access plus 1 year" - - # Favicon, images, flash - - ExpiresDefault "access plus 1 year" - - ExpiresByType image/gif "access plus 1 year" - ExpiresByType image/png "access plus 1 year" - ExpiresByType image/jpg "access plus 1 year" - ExpiresByType image/jpeg "access plus 1 year" - ExpiresByType image/svg+xml "access plus 1 year" - - # Fonts - - ExpiresDefault "access plus 1 year" - - ExpiresByType application/vnd.ms-fontobject "access plus 1 year" - ExpiresByType application/x-font-ttf "access plus 1 year" - ExpiresByType application/x-font-otf "access plus 1 year" - ExpiresByType application/x-font-woff "access plus 1 year" - ExpiresByType application/font-woff2 "access plus 1 year" - - diff --git a/pub/media/customer/.htaccess b/pub/media/customer/.htaccess deleted file mode 100755 index 707c26b075e16..0000000000000 --- a/pub/media/customer/.htaccess +++ /dev/null @@ -1,8 +0,0 @@ - - order allow,deny - deny from all - -= 2.4> - Require all denied - - diff --git a/pub/media/downloadable/.htaccess b/pub/media/downloadable/.htaccess deleted file mode 100755 index 707c26b075e16..0000000000000 --- a/pub/media/downloadable/.htaccess +++ /dev/null @@ -1,8 +0,0 @@ - - order allow,deny - deny from all - -= 2.4> - Require all denied - - diff --git a/pub/media/import/.htaccess b/pub/media/import/.htaccess deleted file mode 100755 index 707c26b075e16..0000000000000 --- a/pub/media/import/.htaccess +++ /dev/null @@ -1,8 +0,0 @@ - - order allow,deny - deny from all - -= 2.4> - Require all denied - - diff --git a/pub/media/theme_customization/.htaccess b/pub/media/theme_customization/.htaccess deleted file mode 100755 index 2b93da6b4c079..0000000000000 --- a/pub/media/theme_customization/.htaccess +++ /dev/null @@ -1,10 +0,0 @@ -Options -Indexes - - - order allow,deny - deny from all - - = 2.4> - Require all denied - - diff --git a/pub/static/.htaccess b/pub/static/.htaccess deleted file mode 100755 index a5aa6fb0d5cfd..0000000000000 --- a/pub/static/.htaccess +++ /dev/null @@ -1,133 +0,0 @@ - -php_flag engine 0 - - - -php_flag engine 0 - - -# To avoid situation when web server automatically adds extension to path -Options -MultiViews - - - RewriteEngine On - - ## you can put here your pub/static folder path relative to web root - #RewriteBase /magento/pub/static/ - - # Remove signature of the static files that is used to overcome the browser cache - RewriteRule ^version.+?/(.+)$ $1 [L] - - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-l - - RewriteRule .* ../static.php?resource=$0 [L] - # Detects if moxieplayer request with uri params and redirects to uri without params - - RewriteCond %{QUERY_STRING} !^$ - RewriteRule ^(.*)$ %{REQUEST_URI}? [R=301,L] - - - -############################################ -## setting MIME types - -# JavaScript -AddType application/javascript js jsonp -AddType application/json json - -# HTML - -AddType text/html html - -# CSS -AddType text/css css - -# Images and icons -AddType image/x-icon ico -AddType image/gif gif -AddType image/png png -AddType image/jpeg jpg -AddType image/jpeg jpeg - -# SVG -AddType image/svg+xml svg - -# Fonts -AddType application/vnd.ms-fontobject eot -AddType application/x-font-ttf ttf -AddType application/x-font-otf otf -AddType application/x-font-woff woff -AddType application/font-woff2 woff2 - -# Flash -AddType application/x-shockwave-flash swf - -# Archives and exports -AddType application/zip gzip -AddType application/x-gzip gz gzip -AddType application/x-bzip2 bz2 -AddType text/csv csv -AddType application/xml xml - - - - - Header append Cache-Control public - - - - Header append Cache-Control no-store - - - - - - -############################################ -## Add default Expires header -## http://developer.yahoo.com/performance/rules.html#expires - - ExpiresActive On - - # Data - - ExpiresDefault "access plus 0 seconds" - - ExpiresByType text/xml "access plus 0 seconds" - ExpiresByType text/csv "access plus 0 seconds" - ExpiresByType application/json "access plus 0 seconds" - ExpiresByType application/zip "access plus 0 seconds" - ExpiresByType application/x-gzip "access plus 0 seconds" - ExpiresByType application/x-bzip2 "access plus 0 seconds" - - # CSS, JavaScript, html - - ExpiresDefault "access plus 1 year" - - ExpiresByType text/css "access plus 1 year" - ExpiresByType text/html "access plus 1 year" - ExpiresByType application/javascript "access plus 1 year" - ExpiresByType application/json "access plus 1 year" - - # Favicon, images, flash - - ExpiresDefault "access plus 1 year" - - ExpiresByType image/gif "access plus 1 year" - ExpiresByType image/png "access plus 1 year" - ExpiresByType image/jpg "access plus 1 year" - ExpiresByType image/jpeg "access plus 1 year" - ExpiresByType image/svg+xml "access plus 1 year" - - # Fonts - - ExpiresDefault "access plus 1 year" - - ExpiresByType application/vnd.ms-fontobject "access plus 1 year" - ExpiresByType application/x-font-ttf "access plus 1 year" - ExpiresByType application/x-font-otf "access plus 1 year" - ExpiresByType application/x-font-woff "access plus 1 year" - ExpiresByType application/font-woff2 "access plus 1 year" - - diff --git a/var/.htaccess b/var/.htaccess deleted file mode 100755 index 707c26b075e16..0000000000000 --- a/var/.htaccess +++ /dev/null @@ -1,8 +0,0 @@ - - order allow,deny - deny from all - -= 2.4> - Require all denied - - From d4bcc7f18efa9f3e9feb4d837872c2f0c9162431 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Mon, 23 Sep 2019 10:50:49 +0530 Subject: [PATCH 025/369] file permission change for account.php and create.php file --- .../Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php | 0 app/code/Magento/Sales/Model/AdminOrder/Create.php | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php mode change 100755 => 100644 app/code/Magento/Sales/Model/AdminOrder/Create.php diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php old mode 100755 new mode 100644 diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php old mode 100755 new mode 100644 From 4e4e6a296ab4176cdf1c525a0128a66137147451 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Mon, 23 Sep 2019 12:49:37 +0530 Subject: [PATCH 026/369] added required discared files --- app/etc/NonComposerComponentRegistration.php | 33 + app/etc/db_schema.xml | 17 + app/etc/di.xml | 1780 ++++++++++++++++++ app/etc/registration_globlist.php | 19 + app/etc/vendor_path.php | 5 + generated/.htaccess | 8 + pub/media/.htaccess | 134 ++ pub/media/customer/.htaccess | 8 + pub/media/downloadable/.htaccess | 8 + pub/media/import/.htaccess | 8 + pub/media/theme_customization/.htaccess | 10 + pub/static/.htaccess | 133 ++ var/.htaccess | 8 + 13 files changed, 2171 insertions(+) create mode 100644 app/etc/NonComposerComponentRegistration.php create mode 100644 app/etc/db_schema.xml create mode 100644 app/etc/di.xml create mode 100644 app/etc/registration_globlist.php create mode 100644 app/etc/vendor_path.php create mode 100644 generated/.htaccess create mode 100644 pub/media/.htaccess create mode 100644 pub/media/customer/.htaccess create mode 100644 pub/media/downloadable/.htaccess create mode 100644 pub/media/import/.htaccess create mode 100644 pub/media/theme_customization/.htaccess create mode 100644 pub/static/.htaccess create mode 100644 var/.htaccess diff --git a/app/etc/NonComposerComponentRegistration.php b/app/etc/NonComposerComponentRegistration.php new file mode 100644 index 0000000000000..a7377ebfca3af --- /dev/null +++ b/app/etc/NonComposerComponentRegistration.php @@ -0,0 +1,33 @@ + + + + + + + + + +
+
diff --git a/app/etc/di.xml b/app/etc/di.xml new file mode 100644 index 0000000000000..1a74fd9d7f840 --- /dev/null +++ b/app/etc/di.xml @@ -0,0 +1,1780 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + system/currency/installed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Magento\Framework\Acl\Builder\Proxy + + + + + Magento\Framework\Filesystem\Driver\File + + + + + Magento\Framework\Filesystem\Driver\File + + + + + + + + Magento\WebapiAsync\Code\Generator\Config\RemoteServiceReader\Communication + 0 + + + Magento\Framework\Communication\Config\Reader\XmlReader + 10 + + + Magento\Framework\Communication\Config\Reader\EnvReader + 20 + + + Magento\Framework\MessageQueue\Code\Generator\Config\RemoteServiceReader\Communication + 5 + + + + + + + main + + Magento\Framework\Logger\Handler\System + Magento\Framework\Logger\Handler\Debug + Magento\Framework\Logger\Handler\Syslog + + + + + + Magento + + + + + Magento\Framework\Model\ActionValidator\RemoveAction\Proxy + + + + + + + + + Magento\Framework\Message\Session\Proxy + Magento\Framework\Message\ExceptionMessageLookupFactory + + + + + + Magento\Backend\App\Request\PathInfoProcessor\Proxy + + + + + Magento\Framework\Session\Config\ConfigInterface\Proxy + + + + + + + Magento\Framework\Session\SaveHandler\DbTable + Magento\Framework\Session\SaveHandler\Redis + + + + + + + Magento\Framework\App\Feed + + + + + + Cm\RedisSession\Handler\ConfigInterface + Cm\RedisSession\Handler\LoggerInterface + + + + + global + + + + + Magento\Framework\App\State::PARAM_MODE + + + + + Magento\Framework\App\State::PARAM_MODE + + + + + Magento\Framework\App\State::PARAM_MODE + + + + + Magento\Framework\App\State::PARAM_MODE + + + + + Magento\Framework\App\Cache\Frontend\Factory::PARAM_CACHE_FORCED_OPTIONS + + + Magento\Framework\Cache\Frontend\Decorator\TagScope + + MAGE + + + + Magento\Framework\Cache\Frontend\Decorator\Logger + + + Magento\Framework\App\ResourceConnection\Proxy + + + + + Magento\Backend\Setup\ConfigOptionsList::CONFIG_PATH_BACKEND_FRONTNAME + + + + + Magento\Framework\App\Cache\State::PARAM_BAN_CACHE + + + + + Magento\Store\Model\StoreManager::PARAM_RUN_CODE + Magento\Store\Model\StoreManager::PARAM_RUN_TYPE + + + + + Magento\Framework\App\Cache\Type\Translate + Magento\Framework\Locale\Resolver\Proxy + Magento\Framework\Translate\ResourceInterface\Proxy + Magento\Framework\App\Request\Http\Proxy + + + + + Magento\Framework\Translate\InlineInterface\Proxy + + + + + + + Magento\Store\Model\StoreManagerInterface\Proxy + + + + + + Magento\Framework\App\Cache\Type\Config + Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy + + + + + Magento\Framework\App\Cache\Type\Config + + + + + config_cache + Magento\Framework\Cache\Config\Reader\Proxy + + + + + Magento\Framework\App\Cache\Type\Config + Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy + interception + + + + + Magento\Framework\App\Cache\Type\Config + + + + + Magento\Framework\App\Cache\Type\Config + Magento\Framework\ObjectManager\Config\Reader\Dom\Proxy + plugin-list + + global + + + + + + Magento\Framework\App\ResourceConnection\ConnectionFactory + + + + + Magento\Framework\App\ResourceConnection\Config\Reader\Proxy + Magento\Framework\App\Cache\Type\Config\Proxy + + + + + Magento\Framework\App\Config\FileResolver\Proxy + + + + + primary + Magento\Framework\App\AreaList\Proxy + + + + + Magento\Framework\Session\Generic\Proxy + Magento\Store\Model\ScopeInterface::SCOPE_STORE + + + + + + Magento\Framework\View\Layout\Argument\Interpreter\Options + layoutArrayArgumentReaderInterpreterProxy + Magento\Framework\Data\Argument\Interpreter\Boolean + Magento\Framework\Data\Argument\Interpreter\Number + Magento\Framework\Data\Argument\Interpreter\StringUtils + Magento\Framework\Data\Argument\Interpreter\NullType + Magento\Framework\View\Layout\Argument\Interpreter\Passthrough + Magento\Framework\View\Layout\Argument\Interpreter\Passthrough + Magento\Framework\View\Layout\Argument\Interpreter\Passthrough + + Magento\Framework\View\Model\Layout\Merge::TYPE_ATTRIBUTE + + + + + + Magento\Framework\View\Layout\Argument\Interpreter\Options + layoutArrayArgumentGeneratorInterpreterProxy + Magento\Framework\Data\Argument\Interpreter\Boolean + Magento\Framework\Data\Argument\Interpreter\Number + Magento\Framework\Data\Argument\Interpreter\StringUtils + Magento\Framework\Data\Argument\Interpreter\NullType + layoutObjectArgumentInterpreter + Magento\Framework\View\Layout\Argument\Interpreter\Url + Magento\Framework\View\Layout\Argument\Interpreter\HelperMethod + + Magento\Framework\View\Model\Layout\Merge::TYPE_ATTRIBUTE + + + + + layoutArgumentGeneratorInterpreterInternal + + + + + layoutArgumentReaderInterpreter + + + + + layoutArgumentGeneratorInterpreterInternal + + + + + + layoutArrayArgumentReaderInterpreter + + + + + layoutArrayArgumentGeneratorInterpreter + + + + + Magento\Framework\View\Element\Block\ArgumentInterface + + + + + Magento\Framework\Data\Argument\Interpreter\StringUtils + + + + + + Magento\Framework\View\Layout\Reader\Container + Magento\Framework\View\Layout\Reader\Block + Magento\Framework\View\Layout\Reader\UiComponent + + + + + + containerRenderPool + + + + + + Magento\Framework\View\Layout\Reader\Container + Magento\Framework\View\Layout\Reader\Block + Magento\Framework\View\Layout\Reader\Move + Magento\Framework\View\Layout\Reader\UiComponent + + + + + + blockRenderPool + Magento\Store\Model\ScopeInterface::SCOPE_STORE + layoutArgumentReaderInterpreter + + + + + blockRenderPool + + + + + Magento\Store\Model\ScopeInterface::SCOPE_STORE + + + + + + Magento\Framework\View\Layout\Reader\Container + Magento\Framework\View\Layout\Reader\Block + Magento\Framework\View\Layout\Reader\Move + Magento\Framework\View\Layout\Reader\UiComponent + + + + + + bodyRenderPool + + + + + + Magento\Framework\View\Page\Config\Reader\Html + Magento\Framework\View\Page\Config\Reader\Head + Magento\Framework\View\Page\Config\Reader\Body + Magento\Framework\View\Layout\Reader\Container + Magento\Framework\View\Layout\Reader\Block + Magento\Framework\View\Layout\Reader\Move + Magento\Framework\View\Layout\Reader\UiComponent + + + + + + commonRenderPool + Magento\Framework\App\Cache\Type\Layout + + + + + + Magento\Framework\View\Layout\Reader\Container + Magento\Framework\View\Layout\Reader\Block + Magento\Framework\View\Layout\Reader\Move + Magento\Framework\View\Layout\Reader\UiComponent + + + + + + genericLayoutRenderPool + + + + + + Magento\Framework\View\Page\Config\Reader\Html + Magento\Framework\View\Page\Config\Reader\Head + Magento\Framework\View\Page\Config\Reader\Body + + + + + + + Magento\Framework\View\Page\Config\Generator\Head + Magento\Framework\View\Page\Config\Generator\Body + Magento\Framework\View\Layout\Generator\Block + Magento\Framework\View\Layout\Generator\Container + Magento\Framework\View\Layout\Generator\UiComponent + + + + + + + Magento\Framework\View\Page\Config\Generator\Head + Magento\Framework\View\Page\Config\Generator\Body + Magento\Framework\View\Layout\Generator\Block + Magento\Framework\View\Layout\Generator\Container + Magento\Framework\View\Layout\Generator\UiComponent + + + + + + pageConfigRenderPool + pageLayoutGeneratorPool + Magento_Theme::root.phtml + + + + + layoutArgumentGeneratorInterpreter + + + + + Magento\Indexer\Model\Mview\View\State + Magento\Framework\Mview\View\Changelog + + + + + Magento\Framework\Mview\Config\Data\Proxy + + + + + Magento\Framework\Mview\View\State\CollectionInterface + + + + + + + AlternativeSourceProcessors + + + + + + Magento\MediaStorage\Model\File\Storage\Response + developerPublisher + + + + + Magento\Framework\View\Asset\PreProcessor\MinificationFilenameResolver + alternative-source-css + Magento\Framework\View\Asset\LockerProcess + + + Magento\Framework\Css\PreProcessor\Adapter\Less\Processor + + + + + + + developerMaterialization + + + + + + Magento\Framework\App\View\Asset\MaterializationStrategy\Symlink + Magento\Framework\App\View\Asset\MaterializationStrategy\Copy + + + + + + Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple + + + + + Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple + + + + + Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple + + + + + + viewFileFallbackResolver + + + + + + viewFileMinifiedFallbackResolver + + + + + + \Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceFactoryGenerator + \Magento\Framework\ObjectManager\Code\Generator\Factory + \Magento\Framework\ObjectManager\Code\Generator\Proxy + \Magento\Framework\Interception\Code\Generator\Interceptor + \Magento\Framework\ObjectManager\Profiler\Code\Generator\Logger + \Magento\Framework\Api\Code\Generator\Mapper + \Magento\Framework\ObjectManager\Code\Generator\Persistor + \Magento\Framework\ObjectManager\Code\Generator\Repository + \Magento\Framework\ObjectManager\Code\Generator\Converter + \Magento\Framework\Api\Code\Generator\SearchResults + \Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator + \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator + \Magento\Framework\MessageQueue\Code\Generator\RemoteServiceGenerator + \Magento\Framework\Async\Code\Generator\ProxyDeferredGenerator + + + + + + + + + page_cache + + + + + + + + + page_cache + + + + + + Magento\Framework\Translate\Inline\ParserInterface\Proxy + + + + + + + + Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder\Term + Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder\Range + Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder\Dynamic + + + + + + + Magento\Framework\Search\Dynamic\Algorithm\Auto + Magento\Framework\Search\Dynamic\Algorithm\Manual + Magento\Framework\Search\Dynamic\Algorithm\Improved + + + + + + Magento\Framework\View\Layout\File\Collector\Aggregated\Proxy + pageLayoutFileCollectorAggregated + Magento\Framework\App\Cache\Type\Layout + Magento\Framework\View\Layout\LayoutCacheKeyInterface + + + + + false + + + + + + core + index + index + + + + + + Magento\Framework\App\Cache\Type\Collection + collection_ + 86400 + + + + + Magento\Framework\Event\Config\Reader\Proxy + + + + + + layout + + + + + layoutFileSourceBase + + + + + layoutFileSourceBaseFiltered + + + + + + layout + + + + + layoutFileSourceTheme + + + + + layoutFileSourceThemeFiltered + + + + + + layout/override/base + + + + + layoutFileSourceOverrideBase + + + + + layoutFileSourceOverrideBaseFiltered + + + + + + layout/override/theme + + + + + layoutFileSourceOverrideTheme + + + + + layoutFileSourceOverrideThemeFiltered + + + + + layoutFileSourceBaseSorted + layoutFileSourceThemeSorted + layoutFileSourceOverrideBaseSorted + layoutFileSourceOverrideThemeSorted + + + + + page_layout + + + + + pageLayoutFileSourceBase + + + + + pageLayoutFileSourceBaseFiltered + + + + + page_layout + + + + + pageLayoutFileSourceTheme + + + + + pageLayoutFileSourceThemeFiltered + + + + + + page_layout/override/base + + + + + pageLayoutFileSourceOverrideBase + + + + + pageLayoutFileSourceOverrideBaseFiltered + + + + + + page_layout/override/theme + + + + + pageLayoutFileSourceOverrideTheme + + + + + pageLayoutFileSourceOverrideThemeFiltered + + + + + + pageLayoutFileSourceBaseSorted + pageLayoutFileSourceThemeSorted + pageLayoutFileSourceOverrideBaseSorted + pageLayoutFileSourceOverrideThemeSorted + + + + + + + pageFileSourceBase + + + + + pageFileSourceBaseFiltered + + + + + + + pageFileSourceTheme + + + + + pageFileSourceThemeFiltered + + + + + + page/override + + + + + pageFileSourceOverrideBase + + + + + pageFileSourceOverrideBaseFiltered + + + + + + override/theme + + + + + pageFileSourceOverrideTheme + + + + + pageFileSourceOverrideThemeFiltered + + + + + + Magento\Framework\View\Layout\Reader\Container + Magento\Framework\View\Layout\Reader\Move + + + + + + pageLayoutFileCollectorAggregated + pageLayoutRenderPool + + + + + pageFileSourceBaseSorted + pageFileSourceThemeSorted + pageFileSourceOverrideBaseSorted + pageFileSourceOverrideThemeSorted + + + + + Magento\Framework\View\Design\Theme\Image\Uploader\Proxy + + + + + Magento\Framework\App\Config\Initial\Reader\Proxy + + + + + Magento\Framework\App\Config\Initial\Converter + + + + + Magento\Framework\App\Route\Config\Reader\Proxy + + + + + Magento\Store\Model\ScopeInterface::SCOPE_STORE + + Shockwave Flash + + + + + + + Magento\Framework\DataObject\Copy\Config\Data\Proxy + + + + + fieldset.xml + Magento\Framework\DataObject\Copy\Config\SchemaLocator + + + + + urn:magento:framework:DataObject/etc/fieldset.xsd + urn:magento:framework:DataObject/etc/fieldset_file.xsd + + + + + Magento\Framework\DataObject\Copy\Config\Reader\Proxy + fieldset_config + + + + + Magento\Framework\Image\Adapter\Gd2 + + + + + page_types.xml + Magento\Framework\View\Layout\PageType\Config\Converter + Magento\Framework\View\Layout\PageType\Config\SchemaLocator + frontend + + + + + Magento\Framework\View\Layout\PageType\Config\Reader + page_types_config + + + + + Magento\Framework\View\Layout\PageType\Config\Data + + + + + message + + + + + Magento\Framework\Message\Session\Storage + + + + + frontend + + + + + + Magento\Framework\Filesystem\Driver\File + + + + + + [a-z]+[_a-z\d]*?\/[a-z]+[_a-z\d]*?)::.*?$/sui]]> + [a-z]+[_a-z\d]*?\/[a-z]+[_a-z\d]*?)\".*?}}/sui]]> + [a-z]+[_a-z\d]*?\/[a-z]+[_a-z\d]*?)\".*?>/sui]]> + s:\d+:"(?P[a-z]+[_a-z\d]*?/[a-z]+[_a-z\d]*?)")#sui]]> + + + + + + + + application/json + Magento\Framework\Webapi\Rest\Request\Deserializer\Json + + + application/xml + Magento\Framework\Webapi\Rest\Request\Deserializer\Xml + + + application/xhtml+xml + Magento\Framework\Webapi\Rest\Request\Deserializer\Xml + + + text/xml + Magento\Framework\Webapi\Rest\Request\Deserializer\Xml + + + + + + + Magento\Framework\App\Cache\Type\Config + + + + + + Magento\Framework\Reflection\ExtensionAttributesProcessor\Proxy + Magento\Framework\Reflection\CustomAttributesProcessor\Proxy + + + + + Magento\Framework\UrlInterface + + + + + + + view.xml + Magento\Framework\Config\Converter + Magento\Framework\Config\SchemaLocator + Magento\Framework\Config\FileResolver + + + + + Magento\Framework\DB\Select\RendererProxy + + + + + Magento\Framework\Locale\ResolverInterface + + + + + + + Magento\Framework\DB\Select\DistinctRenderer + 100 + distinct + + + Magento\Framework\DB\Select\ColumnsRenderer + 200 + columns + + + Magento\Framework\DB\Select\UnionRenderer + 300 + union + + + Magento\Framework\DB\Select\FromRenderer + 400 + from + + + Magento\Framework\DB\Select\WhereRenderer + 500 + where + + + Magento\Framework\DB\Select\GroupRenderer + 600 + group + + + Magento\Framework\DB\Select\HavingRenderer + 700 + having + + + Magento\Framework\DB\Select\OrderRenderer + 800 + order + + + Magento\Framework\DB\Select\LimitRenderer + 900 + limitcount + + + Magento\Framework\DB\Select\ForUpdateRenderer + 1000 + forupdate + + + + + + + + + Magento\Framework\EntityManager\Operation\CheckIfExists + Magento\Framework\EntityManager\Operation\Read + Magento\Framework\EntityManager\Operation\Create + Magento\Framework\EntityManager\Operation\Update + Magento\Framework\EntityManager\Operation\Delete + + + + + + + + Magento\Framework\App\Cache\Type\Block::TYPE_IDENTIFIER + Magento\Framework\App\Cache\Type\Collection::TYPE_IDENTIFIER + + + + + + + Magento\Framework\EntityManager\Mapper + + + + + + + + Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor + Magento\Framework\Api\SearchCriteria\CollectionProcessor\SortingProcessor + Magento\Framework\Api\SearchCriteria\CollectionProcessor\PaginationProcessor + + + + + + + Magento\Framework\DB\Select\InQueryModifier + Magento\Framework\DB\Select\LikeQueryModifier + Magento\Framework\DB\Select\CompositeQueryModifier + + + + + + + HASH + BTREE + + + INNODB + MEMORY + MYISAM + + + + + + Magento\Framework\DB\FieldDataConverter::BATCH_SIZE_VARIABLE_NAME + + + + + + + true + + + + + + + Magento\Framework\View\Asset\PreProcessor\Passthrough + + + + + Magento\Framework\App\Filesystem\DirectoryList::STATIC_VIEW + deployed_version.txt + + + + + Magento\Directory\Helper\Data::XML_PATH_DEFAULT_LOCALE + Magento\Framework\App\ScopeInterface::SCOPE_DEFAULT + + + + + + Magento\Framework\View\Element\Message\Renderer\EscapeRenderer + Magento\Framework\View\Element\Message\Renderer\BlockRenderer + + + + + + + + \Magento\Framework\View\Element\Message\Renderer\EscapeRenderer::CODE + + + + + + + Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_OUTPUT + Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_LOG_EVERYTHING + Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_QUERY_TIME_THRESHOLD + Magento\Framework\Config\ConfigOptionsListConstants::CONFIG_PATH_DB_LOGGER_INCLUDE_STACKTRACE + + + + + Magento\Config\App\Config\Source\EnvironmentConfigSource + + + + + Magento\Framework\Message\ExceptionMessageFactory + + + + + + updated_at + + + + + + + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Table + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Real + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Real + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Real + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Integer + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Integer + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Integer + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Integer + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Date + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Timestamp + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Timestamp + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\LongText + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\MediumText + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Text + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\StringBinary + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\StringBinary + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Blob + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\MediumBlob + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\LongBlob + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Boolean + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Unique + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Primary + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Foreign + \Magento\Framework\Setup\Declaration\Schema\Dto\Factories\Index + + + + + + + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Boolean + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Integer + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Integer + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Integer + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Integer + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Real + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Real + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Real + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Blob + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Timestamp + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Date + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\Timestamp + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\StringBinary + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\StringBinary + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\StringBinary + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Columns\StringBinary + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Index + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Constraints\Internal + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Constraints\Internal + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Constraints\Internal + \Magento\Framework\Setup\Declaration\Schema\Db\MySQL\Definition\Constraints\ForeignKey + + + + + + + Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DDL\Triggers\MigrateDataFrom + + + + + + + Magento\Framework\Setup\Declaration\Schema\FileSystem\XmlReader + + + + + + urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd + + + + + Magento\Framework\Config\FileResolverByModule + Magento\Framework\Setup\Declaration\Schema\Config\Converter + Magento\Framework\Setup\Declaration\Schema\Config\SchemaLocator + db_schema.xml + + name + name + referenceId + referenceId + name + name + + + + + + + Magento\Framework\Setup\Declaration\Schema\Operations\ReCreateTable + Magento\Framework\Setup\Declaration\Schema\Operations\CreateTable + Magento\Framework\Setup\Declaration\Schema\Operations\DropTable + Magento\Framework\Setup\Declaration\Schema\Operations\DropReference + Magento\Framework\Setup\Declaration\Schema\Operations\ModifyColumn + Magento\Framework\Setup\Declaration\Schema\Operations\AddColumn + Magento\Framework\Setup\Declaration\Schema\Operations\DropElement + Magento\Framework\Setup\Declaration\Schema\Operations\AddComplexElement + Magento\Framework\Setup\Declaration\Schema\Operations\ModifyTable + + + Magento\Framework\Setup\Declaration\Schema\DataSavior\TableSavior + Magento\Framework\Setup\Declaration\Schema\DataSavior\ColumnSavior + + + + + + + default + + + + + + + Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\CheckReferenceColumnHasIndex + Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\RealTypes + Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\PrimaryKeyCanBeCreated + Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\IncosistentReferenceDefinition + Magento\Framework\Setup\Declaration\Schema\Declaration\ValidationRules\AutoIncrementColumnValidation + + + + + + + Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition + Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition + Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition + Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition + Magento\Framework\Setup\SchemaListenerDefinition\IntegerDefinition + Magento\Framework\Setup\SchemaListenerDefinition\RealDefinition + Magento\Framework\Setup\SchemaListenerDefinition\RealDefinition + Magento\Framework\Setup\SchemaListenerDefinition\RealDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TimestampDefinition + Magento\Framework\Setup\SchemaListenerDefinition\TimestampDefinition + Magento\Framework\Setup\SchemaListenerDefinition\DateDefinition + Magento\Framework\Setup\SchemaListenerDefinition\BooleanDefinition + + + + + + schema + + + + + data + + + + + \Magento\Framework\Setup\Patch\DataPatchReader + \Magento\Framework\Setup\Patch\SchemaPatchReader + + + + + \Magento\Framework\Setup\Patch\DataPatchReader + + + + + \Magento\Framework\Setup\Patch\SchemaPatchReader + + + + + + + Magento\Framework\MessageQueue\Config\Reader\Xml + 10 + + + Magento\Framework\MessageQueue\Config\Reader\Env + 20 + + + + + + + + + Magento\Framework\MessageQueue\Config\Reader\Xml\Converter\TopicConfig + 20 + + + + + + + Magento\Framework\MessageQueue\Consumer\Config\CompositeReader + + + + + + Magento\Framework\MessageQueue\Consumer\Config\Xml\Reader + Magento\Framework\MessageQueue\Consumer\Config\Env\Reader + + + + + + + Magento\Framework\MessageQueue\Consumer\Config\Validator\RequiredFields + Magento\Framework\MessageQueue\Consumer\Config\Validator\FieldsTypes + Magento\Framework\MessageQueue\Consumer\Config\Validator\Handlers + Magento\Framework\MessageQueue\Consumer\Config\Validator\ConsumerInstance + + + + + + + Magento\Framework\MessageQueue\Publisher\Config\Validator\Format + Magento\Framework\MessageQueue\Publisher\Config\Validator\EnabledConnection + + + + + + + Magento\WebapiAsync\Code\Generator\Config\RemoteServiceReader\Publisher + Magento\Framework\MessageQueue\Publisher\Config\RemoteService\Reader + Magento\Framework\MessageQueue\Publisher\Config\Xml\Reader + Magento\Framework\MessageQueue\Publisher\Config\Env\Reader + + + + + + + Magento\Framework\MessageQueue\Topology\Config\Validator\Format + Magento\Framework\MessageQueue\Topology\Config\Validator\FieldsTypes + Magento\Framework\MessageQueue\Topology\Config\Validator\DependentFields + + + + + + + Magento\Framework\MessageQueue\Topology\Config\RemoteService\Reader + Magento\Framework\MessageQueue\Topology\Config\Xml\Reader + + + + + + + Magento\Framework\Amqp\Topology\BindingInstallerType\Queue + Magento\Framework\Amqp\Topology\BindingInstallerType\Exchange + + + + + + + magento + magento-db + + + + + + + amqp + db + + + + + + + Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DDL\Triggers\MigrateDataFromAnotherTable + + + + + + + + Magento\Framework\App\Config\ScopeConfigInterface::SCOPE_TYPE_DEFAULT + + + + + + + CsrfRequestValidator + + Magento\Framework\App\Request\HttpMethodValidator + + + + + + + + + \Magento\Framework\App\Action\HttpOptionsActionInterface + \Magento\Framework\App\Action\HttpGetActionInterface + \Magento\Framework\App\Action\HttpGetActionInterface + \Magento\Framework\App\Action\HttpPostActionInterface + \Magento\Framework\App\Action\HttpPutActionInterface + \Magento\Framework\App\Action\HttpPatchActionInterface + \Magento\Framework\App\Action\HttpDeleteActionInterface + \Magento\Framework\App\Action\HttpConnectActionInterface + \Magento\Framework\App\Action\HttpPropfindActionInterface + \Magento\Framework\App\Action\HttpTraceActionInterface + + + + + + + Magento\Framework\App\ScopeResolver + + + + + + Magento\Framework\Lock\Backend\Cache + 10000 + 20 + + + + + + + + + + + diff --git a/app/etc/registration_globlist.php b/app/etc/registration_globlist.php new file mode 100644 index 0000000000000..23caae00cb303 --- /dev/null +++ b/app/etc/registration_globlist.php @@ -0,0 +1,19 @@ + + order allow,deny + deny from all + += 2.4> + Require all denied + + diff --git a/pub/media/.htaccess b/pub/media/.htaccess new file mode 100644 index 0000000000000..d68d163d7a6b5 --- /dev/null +++ b/pub/media/.htaccess @@ -0,0 +1,134 @@ +Options -Indexes + + +php_flag engine 0 + + + +php_flag engine 0 + + +AddHandler cgi-script .php .pl .py .jsp .asp .htm .shtml .sh .cgi +Options -ExecCGI + + +SetHandler default-handler + + + + +############################################ +## enable rewrites + + Options +FollowSymLinks + RewriteEngine on + + ## you can put here your pub/media folder path relative to web root + #RewriteBase /magento/pub/media/ + +############################################ +## never rewrite for existing files + RewriteCond %{REQUEST_FILENAME} !-f + +############################################ +## rewrite everything else to get.php + + RewriteRule .* ../get.php [L] + + +############################################ +## setting MIME types + +# JavaScript +AddType application/javascript js jsonp +AddType application/json json + +# CSS +AddType text/css css + +# Images and icons +AddType image/x-icon ico +AddType image/gif gif +AddType image/png png +AddType image/jpeg jpg +AddType image/jpeg jpeg + +# SVG +AddType image/svg+xml svg + +# Fonts +AddType application/vnd.ms-fontobject eot +AddType application/x-font-ttf ttf +AddType application/x-font-otf otf +AddType application/x-font-woff woff +AddType application/font-woff2 woff2 + +# Flash +AddType application/x-shockwave-flash swf + +# Archives and exports +AddType application/zip gzip +AddType application/x-gzip gz gzip +AddType application/x-bzip2 bz2 +AddType text/csv csv +AddType application/xml xml + + + + + Header append Cache-Control public + + + + Header append Cache-Control no-store + + + + + + +############################################ +## Add default Expires header +## http://developer.yahoo.com/performance/rules.html#expires + + ExpiresActive On + + # Data + + ExpiresDefault "access plus 0 seconds" + + ExpiresByType text/xml "access plus 0 seconds" + ExpiresByType text/csv "access plus 0 seconds" + ExpiresByType application/json "access plus 0 seconds" + ExpiresByType application/zip "access plus 0 seconds" + ExpiresByType application/x-gzip "access plus 0 seconds" + ExpiresByType application/x-bzip2 "access plus 0 seconds" + + # CSS, JavaScript + + ExpiresDefault "access plus 1 year" + + ExpiresByType text/css "access plus 1 year" + ExpiresByType application/javascript "access plus 1 year" + + # Favicon, images, flash + + ExpiresDefault "access plus 1 year" + + ExpiresByType image/gif "access plus 1 year" + ExpiresByType image/png "access plus 1 year" + ExpiresByType image/jpg "access plus 1 year" + ExpiresByType image/jpeg "access plus 1 year" + ExpiresByType image/svg+xml "access plus 1 year" + + # Fonts + + ExpiresDefault "access plus 1 year" + + ExpiresByType application/vnd.ms-fontobject "access plus 1 year" + ExpiresByType application/x-font-ttf "access plus 1 year" + ExpiresByType application/x-font-otf "access plus 1 year" + ExpiresByType application/x-font-woff "access plus 1 year" + ExpiresByType application/font-woff2 "access plus 1 year" + + diff --git a/pub/media/customer/.htaccess b/pub/media/customer/.htaccess new file mode 100644 index 0000000000000..707c26b075e16 --- /dev/null +++ b/pub/media/customer/.htaccess @@ -0,0 +1,8 @@ + + order allow,deny + deny from all + += 2.4> + Require all denied + + diff --git a/pub/media/downloadable/.htaccess b/pub/media/downloadable/.htaccess new file mode 100644 index 0000000000000..707c26b075e16 --- /dev/null +++ b/pub/media/downloadable/.htaccess @@ -0,0 +1,8 @@ + + order allow,deny + deny from all + += 2.4> + Require all denied + + diff --git a/pub/media/import/.htaccess b/pub/media/import/.htaccess new file mode 100644 index 0000000000000..707c26b075e16 --- /dev/null +++ b/pub/media/import/.htaccess @@ -0,0 +1,8 @@ + + order allow,deny + deny from all + += 2.4> + Require all denied + + diff --git a/pub/media/theme_customization/.htaccess b/pub/media/theme_customization/.htaccess new file mode 100644 index 0000000000000..2b93da6b4c079 --- /dev/null +++ b/pub/media/theme_customization/.htaccess @@ -0,0 +1,10 @@ +Options -Indexes + + + order allow,deny + deny from all + + = 2.4> + Require all denied + + diff --git a/pub/static/.htaccess b/pub/static/.htaccess new file mode 100644 index 0000000000000..a5aa6fb0d5cfd --- /dev/null +++ b/pub/static/.htaccess @@ -0,0 +1,133 @@ + +php_flag engine 0 + + + +php_flag engine 0 + + +# To avoid situation when web server automatically adds extension to path +Options -MultiViews + + + RewriteEngine On + + ## you can put here your pub/static folder path relative to web root + #RewriteBase /magento/pub/static/ + + # Remove signature of the static files that is used to overcome the browser cache + RewriteRule ^version.+?/(.+)$ $1 [L] + + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-l + + RewriteRule .* ../static.php?resource=$0 [L] + # Detects if moxieplayer request with uri params and redirects to uri without params + + RewriteCond %{QUERY_STRING} !^$ + RewriteRule ^(.*)$ %{REQUEST_URI}? [R=301,L] + + + +############################################ +## setting MIME types + +# JavaScript +AddType application/javascript js jsonp +AddType application/json json + +# HTML + +AddType text/html html + +# CSS +AddType text/css css + +# Images and icons +AddType image/x-icon ico +AddType image/gif gif +AddType image/png png +AddType image/jpeg jpg +AddType image/jpeg jpeg + +# SVG +AddType image/svg+xml svg + +# Fonts +AddType application/vnd.ms-fontobject eot +AddType application/x-font-ttf ttf +AddType application/x-font-otf otf +AddType application/x-font-woff woff +AddType application/font-woff2 woff2 + +# Flash +AddType application/x-shockwave-flash swf + +# Archives and exports +AddType application/zip gzip +AddType application/x-gzip gz gzip +AddType application/x-bzip2 bz2 +AddType text/csv csv +AddType application/xml xml + + + + + Header append Cache-Control public + + + + Header append Cache-Control no-store + + + + + + +############################################ +## Add default Expires header +## http://developer.yahoo.com/performance/rules.html#expires + + ExpiresActive On + + # Data + + ExpiresDefault "access plus 0 seconds" + + ExpiresByType text/xml "access plus 0 seconds" + ExpiresByType text/csv "access plus 0 seconds" + ExpiresByType application/json "access plus 0 seconds" + ExpiresByType application/zip "access plus 0 seconds" + ExpiresByType application/x-gzip "access plus 0 seconds" + ExpiresByType application/x-bzip2 "access plus 0 seconds" + + # CSS, JavaScript, html + + ExpiresDefault "access plus 1 year" + + ExpiresByType text/css "access plus 1 year" + ExpiresByType text/html "access plus 1 year" + ExpiresByType application/javascript "access plus 1 year" + ExpiresByType application/json "access plus 1 year" + + # Favicon, images, flash + + ExpiresDefault "access plus 1 year" + + ExpiresByType image/gif "access plus 1 year" + ExpiresByType image/png "access plus 1 year" + ExpiresByType image/jpg "access plus 1 year" + ExpiresByType image/jpeg "access plus 1 year" + ExpiresByType image/svg+xml "access plus 1 year" + + # Fonts + + ExpiresDefault "access plus 1 year" + + ExpiresByType application/vnd.ms-fontobject "access plus 1 year" + ExpiresByType application/x-font-ttf "access plus 1 year" + ExpiresByType application/x-font-otf "access plus 1 year" + ExpiresByType application/x-font-woff "access plus 1 year" + ExpiresByType application/font-woff2 "access plus 1 year" + + diff --git a/var/.htaccess b/var/.htaccess new file mode 100644 index 0000000000000..707c26b075e16 --- /dev/null +++ b/var/.htaccess @@ -0,0 +1,8 @@ + + order allow,deny + deny from all + += 2.4> + Require all denied + + From 7da3d539b7402f5561be8fd679d61a5334ec0067 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Wed, 25 Sep 2019 14:20:32 +0530 Subject: [PATCH 027/369] corrected di.xml and Account.php files --- .../Block/Adminhtml/Order/Create/Form/Account.php | 14 ++++++-------- app/etc/di.xml | 4 ++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index fc48e351b0e7e..aa5bd3702c6d1 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -165,16 +165,14 @@ public function getFormValues() { try { $customer = $this->customerRepository->getById($this->getCustomerId()); - } catch (\Exception $e) { - /** If customer does not exist do nothing. */ - } - $data = isset($customer) - ? $this->_extensibleDataObjectConverter->toFlatArray( + $data = $this->_extensibleDataObjectConverter->toFlatArray( $customer, [], - \Magento\Customer\Api\Data\CustomerInterface::class - ) - : []; + CustomerInterface::class + ); + } catch (\Exception $e) { + $data = []; + } foreach ($this->getQuote()->getData() as $key => $value) { if (strpos($key, 'customer_') === 0) { $data[substr($key, 9)] = $value; diff --git a/app/etc/di.xml b/app/etc/di.xml index 1a74fd9d7f840..882d1be623988 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -47,6 +47,7 @@ + system/currency/installed @@ -1777,4 +1778,7 @@ type="Magento\Framework\Mail\MimeMessage" /> + + + From 6950ead57dc9b17f8604401421fbf1339c81592d Mon Sep 17 00:00:00 2001 From: Dmitriy Danichenko Date: Fri, 27 Sep 2019 12:19:07 +0300 Subject: [PATCH 028/369] fix issue 24735 --- .../frontend/web/template/product/image_with_borders.html | 4 ++-- app/design/frontend/Magento/luma/etc/view.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/view/frontend/web/template/product/image_with_borders.html b/app/code/Magento/Catalog/view/frontend/web/template/product/image_with_borders.html index d59237c190f71..48c5feb190990 100644 --- a/app/code/Magento/Catalog/view/frontend/web/template/product/image_with_borders.html +++ b/app/code/Magento/Catalog/view/frontend/web/template/product/image_with_borders.html @@ -4,8 +4,8 @@ * See COPYING.txt for license details. */ --> - + - + diff --git a/app/design/frontend/Magento/luma/etc/view.xml b/app/design/frontend/Magento/luma/etc/view.xml index a2802b7e374f3..e8b8bc66d2e44 100644 --- a/app/design/frontend/Magento/luma/etc/view.xml +++ b/app/design/frontend/Magento/luma/etc/view.xml @@ -53,8 +53,8 @@ 100 - 75 - 75 + 150 + 150 240 From 58511261b6ed15192b4014adc96e8a418df41e16 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Tue, 1 Oct 2019 16:24:42 +0530 Subject: [PATCH 029/369] Added Function and Integration --- .../Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml | 3 +++ .../Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml | 1 + 2 files changed, 4 insertions(+) diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index 3f178ae02102a..e6e4f14854fc6 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -94,7 +94,10 @@ + + + diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml index 430211e52bfb5..f374b15ea2491 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml @@ -13,6 +13,7 @@ + From c7deb42a3b9b1875d4afe95ffea3c0f11cd1cb5d Mon Sep 17 00:00:00 2001 From: Dmitriy Danichenko Date: Wed, 2 Oct 2019 09:08:51 +0300 Subject: [PATCH 030/369] fix minicart image for Blank theme --- app/design/frontend/Magento/blank/etc/view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/design/frontend/Magento/blank/etc/view.xml b/app/design/frontend/Magento/blank/etc/view.xml index 5884699af15cd..aebdf8dce4a27 100644 --- a/app/design/frontend/Magento/blank/etc/view.xml +++ b/app/design/frontend/Magento/blank/etc/view.xml @@ -49,8 +49,8 @@ 100 - 78 - 78 + 156 + 156 240 From 7772d52ee9cbdd12346d8d2bc9583a4c05193884 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 4 Oct 2019 12:28:25 +0530 Subject: [PATCH 031/369] functional test changes --- .../Mftf/ActionGroup/AdminOrderActionGroup.xml | 15 ++++++--------- .../Mftf/Section/AdminOrderFormAccountSection.xml | 3 +-- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index e6e4f14854fc6..f0f50e757e38f 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -92,12 +92,9 @@ Clears the Email, First Name, Last Name, Street Line 1, City, Postal Code and Phone fields when adding an Order and then verifies that they are required after attempting to Save. - + - - - @@ -184,7 +181,7 @@ EXTENDS: addConfigurableProductToOrder. Selects the provided Option to the Configurable Product. - + @@ -198,7 +195,7 @@ - + @@ -216,7 +213,7 @@ - + @@ -238,7 +235,7 @@ - + {{price}} @@ -519,7 +516,7 @@ - + diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml index f374b15ea2491..65d86c2940bab 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormAccountSection.xml @@ -12,8 +12,7 @@ - - + From 6915dc8089a1403764e761c869a1e4130f636269 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 4 Oct 2019 16:12:42 +0530 Subject: [PATCH 032/369] Added Function and Integration email validation --- .../Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml | 1 - .../Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml | 2 -- 2 files changed, 3 deletions(-) diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index f0f50e757e38f..3a4bb0b2a045e 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -106,7 +106,6 @@ - diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml index 2d1a4d5a4cbae..7fe241c3a118d 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormBillingAddressSection.xml @@ -27,8 +27,6 @@ - - From 65ae5b064415165f2df56435af392d4fa44d3339 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 11 Oct 2019 15:21:38 +0530 Subject: [PATCH 033/369] Integration test --- .../Adminhtml/Order/Create/Form/AccountTest.php | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php index b75501911be6b..4174e3108c34e 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php @@ -102,18 +102,6 @@ public function testGetFormWithCustomer() sprintf('Unexpected field "%s" in form.', $element->getId()) ); } - - self::assertContains( - '', - $content, - 'The Customer Group specified for the chosen customer should be selected.' - ); - - self::assertContains( - 'value="'.$customer->getEmail().'"', - $content, - 'The Customer Email specified for the chosen customer should be input ' - ); } /** From 93fc0eeea9618ed1008a661ff691a08b81abd91e Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 11 Oct 2019 17:58:33 +0530 Subject: [PATCH 034/369] integration test fails --- .../Adminhtml/Order/Create/Form/AccountTest.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php index 4174e3108c34e..b75501911be6b 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php @@ -102,6 +102,18 @@ public function testGetFormWithCustomer() sprintf('Unexpected field "%s" in form.', $element->getId()) ); } + + self::assertContains( + '', + $content, + 'The Customer Group specified for the chosen customer should be selected.' + ); + + self::assertContains( + 'value="'.$customer->getEmail().'"', + $content, + 'The Customer Email specified for the chosen customer should be input ' + ); } /** From d5997ce775ab5ae8d39ebadbce91fbc7f83c13d9 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Wed, 16 Oct 2019 16:54:18 +0530 Subject: [PATCH 035/369] version checker test failed --- .../Block/Adminhtml/Order/Create/Form/Account.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 03915c0499367..07800cc4c10a0 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -163,16 +163,14 @@ public function getFormValues() { try { $customer = $this->customerRepository->getById($this->getCustomerId()); - } catch (\Exception $e) { - /** If customer does not exist do nothing. */ - } - $data = isset($customer) - ? $this->_extensibleDataObjectConverter->toFlatArray( + $data = $this->_extensibleDataObjectConverter->toFlatArray( $customer, [], - \Magento\Customer\Api\Data\CustomerInterface::class - ) - : []; + CustomerInterface::class + ); + } catch (\Exception $e) { + $data = []; + } foreach ($this->getQuote()->getData() as $key => $value) { if (strpos($key, 'customer_') === 0) { $data[substr($key, 9)] = $value; From 781684ab90d077ae838a5b3c833e1760c408c30a Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Tue, 22 Oct 2019 17:31:26 +0530 Subject: [PATCH 036/369] Integration test changes --- .../Block/Adminhtml/Order/Create/Form/Account.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index aa5bd3702c6d1..726a8e80c793b 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -165,14 +165,17 @@ public function getFormValues() { try { $customer = $this->customerRepository->getById($this->getCustomerId()); - $data = $this->_extensibleDataObjectConverter->toFlatArray( - $customer, - [], - CustomerInterface::class - ); } catch (\Exception $e) { - $data = []; + /** If customer does not exist do nothing. */ } + $data = isset($customer) + ? $this->_extensibleDataObjectConverter->toFlatArray( + $customer, + [], + \Magento\Customer\Api\Data\CustomerInterface::class + ) + : []; + foreach ($this->getQuote()->getData() as $key => $value) { if (strpos($key, 'customer_') === 0) { $data[substr($key, 9)] = $value; From c0e73a8b3c01473060bf55c9166421ca5b69056c Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Sat, 26 Oct 2019 13:32:23 +0100 Subject: [PATCH 037/369] Reduce severity of 'processing' state --- .../Indexer/Block/Backend/Grid/Column/Renderer/Status.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php index d8a017ef5a4b4..144d44fe42880 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php @@ -27,7 +27,7 @@ public function render(\Magento\Framework\DataObject $row) $text = __('Ready'); break; case \Magento\Framework\Indexer\StateInterface::STATUS_WORKING: - $class = 'grid-severity-major'; + $class = 'grid-severity-minor'; $text = __('Processing'); break; } From e39b01284294be28e760a5ca884ae90cca7f373f Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Sat, 26 Oct 2019 14:12:50 +0100 Subject: [PATCH 038/369] Add 'schedule status' column to admin indexer grid This is a copy of the 'schedule status' column available via command line. --- .../Grid/Column/Renderer/ScheduleStatus.php | 83 +++++++++++++++++++ .../layout/indexer_indexer_list_grid.xml | 9 ++ 2 files changed, 92 insertions(+) create mode 100644 app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php new file mode 100644 index 0000000000000..0a407527edb95 --- /dev/null +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -0,0 +1,83 @@ +viewModel = $viewModel; + } + + /** + * Render indexer status + * + * @param \Magento\Framework\DataObject $row + * @return string + */ + public function render(\Magento\Framework\DataObject $row) + { + try { + if (!$row->getIsScheduled()) { + return ''; + } + + try { + $view = $this->viewModel->load($row->getIndexerId()); + } catch (\InvalidArgumentException $exception) { + // No view for this index. + return ''; + } + + $state = $view->getState()->loadByView($view->getId()); + $changelog = $view->getChangelog()->setViewId($view->getId()); + $currentVersionId = $changelog->getVersion(); + $count = count($changelog->getList($state->getVersionId(), $currentVersionId)); + + if ($count > 1000) { + $class = 'grid-severity-critical'; + } elseif ($count > 100) { + $class = 'grid-severity-major'; + } elseif ($count > 10) { + $class = 'grid-severity-minor'; + } else { + $class = 'grid-severity-notice'; + } + + if ($state->getStatus() !== 'idle') { + $class = 'grid-severity-minor'; + } + + $text = new Phrase( + "%status (%count in backlog)", + [ + 'status' => $state->getStatus(), + 'count' => $count, + ] + ); + + return '' . $text . ''; + } catch (\Exception $exception) { + return '' . + htmlspecialchars( + get_class($exception) . ': ' . $exception->getMessage() + ) . ''; + } + } +} diff --git a/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml b/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml index f1099c4133bca..b6f9b0e53cba2 100644 --- a/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml +++ b/app/code/Magento/Indexer/view/adminhtml/layout/indexer_indexer_list_grid.xml @@ -76,6 +76,15 @@ indexer-status + + + Schedule Status + schedule_status + Magento\Indexer\Block\Backend\Grid\Column\Renderer\ScheduleStatus + 0 + indexer-schedule-status + + Updated From 1a1cba61332f3d51ff2840a766f1aecf29ce88a9 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Mon, 28 Oct 2019 16:25:23 +0000 Subject: [PATCH 039/369] Add comment with parameter annotations --- .../Block/Backend/Grid/Column/Renderer/ScheduleStatus.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php index 0a407527edb95..fb2679c8f4c24 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -16,6 +16,11 @@ class ScheduleStatus extends AbstractRenderer */ protected $viewModel; + /** + * @param \Magento\Backend\Block\Context $context + * @param \Magento\Framework\Mview\ViewInterface $viewModel + * @param array $data + */ public function __construct( \Magento\Backend\Block\Context $context, View $viewModel, From 7200593bb5fcebfe1d5fa8be91649c1c7e23698c Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Mon, 28 Oct 2019 16:28:15 +0000 Subject: [PATCH 040/369] Use Magento\Framework\Escaper not htmlspecialchars --- .../Backend/Grid/Column/Renderer/ScheduleStatus.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php index fb2679c8f4c24..1aff83cdf4bbf 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -6,11 +6,17 @@ namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; use Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer; +use Magento\Framework\Escaper; use Magento\Framework\Mview\View; use Magento\Framework\Phrase; class ScheduleStatus extends AbstractRenderer { + /** + * @var \Magento\Framework\Escaper + */ + protected $escaper; + /** * @var \Magento\Framework\Mview\ViewInterface */ @@ -19,14 +25,17 @@ class ScheduleStatus extends AbstractRenderer /** * @param \Magento\Backend\Block\Context $context * @param \Magento\Framework\Mview\ViewInterface $viewModel + * @param \Magento\Framework\Escaper $escaper * @param array $data */ public function __construct( \Magento\Backend\Block\Context $context, + Escaper $escaper, View $viewModel, array $data = [] ) { parent::__construct($context, $data); + $this->escaper = $escaper; $this->viewModel = $viewModel; } @@ -80,7 +89,7 @@ public function render(\Magento\Framework\DataObject $row) return '' . $text . ''; } catch (\Exception $exception) { return '' . - htmlspecialchars( + $this->escaper->escapeHtml( get_class($exception) . ': ' . $exception->getMessage() ) . ''; } From b74b7a4984e9623adc92499ab530ab4103ded607 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Mon, 28 Oct 2019 16:30:13 +0000 Subject: [PATCH 041/369] Update unit test to match class change --- .../Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Indexer/Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php b/app/code/Magento/Indexer/Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php index 705a9e3998ae8..7249f764a66a1 100644 --- a/app/code/Magento/Indexer/Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Block/Backend/Grid/Column/Renderer/StatusTest.php @@ -49,7 +49,7 @@ public function renderDataProvider() ], 'set3' => [ [\Magento\Framework\Indexer\StateInterface::STATUS_WORKING], - ['class' => 'grid-severity-major', 'text' => 'Processing'] + ['class' => 'grid-severity-minor', 'text' => 'Processing'] ] ]; } From c1f8adb53a2a33339f61852bb6e1e5d505f082c9 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Wed, 30 Oct 2019 16:51:05 +0000 Subject: [PATCH 042/369] Correct parameter order in comment --- .../Block/Backend/Grid/Column/Renderer/ScheduleStatus.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php index 1aff83cdf4bbf..712b17b91a0d7 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -24,8 +24,8 @@ class ScheduleStatus extends AbstractRenderer /** * @param \Magento\Backend\Block\Context $context - * @param \Magento\Framework\Mview\ViewInterface $viewModel * @param \Magento\Framework\Escaper $escaper + * @param \Magento\Framework\Mview\ViewInterface $viewModel * @param array $data */ public function __construct( From fbe87ab80084aa4f1803377c6c69c1fdb0779467 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 1 Nov 2019 12:13:46 +0530 Subject: [PATCH 043/369] static test failed exception fix --- .../Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 726a8e80c793b..01d7625b9e63c 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -166,7 +166,7 @@ public function getFormValues() try { $customer = $this->customerRepository->getById($this->getCustomerId()); } catch (\Exception $e) { - /** If customer does not exist do nothing. */ + $data = []; } $data = isset($customer) ? $this->_extensibleDataObjectConverter->toFlatArray( From 93d698a4de0ba46094043cf895d87d271d67178e Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 1 Nov 2019 12:51:31 +0530 Subject: [PATCH 044/369] static test failed exception space fix --- .../Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 01d7625b9e63c..bffc51a11c5cb 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -166,7 +166,7 @@ public function getFormValues() try { $customer = $this->customerRepository->getById($this->getCustomerId()); } catch (\Exception $e) { - $data = []; + $data = []; } $data = isset($customer) ? $this->_extensibleDataObjectConverter->toFlatArray( From f4692b2274d986489c8c096096957c28de65040a Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 1 Nov 2019 15:22:14 +0530 Subject: [PATCH 045/369] Semantic Version Checker --- app/code/Magento/Sales/composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/composer.json b/app/code/Magento/Sales/composer.json index 8d4ef50a2ebc5..3b20f6e43e721 100644 --- a/app/code/Magento/Sales/composer.json +++ b/app/code/Magento/Sales/composer.json @@ -47,5 +47,6 @@ "psr-4": { "Magento\\Sales\\": "" } - } + }, + "version": "102.0.4" } From 3e8369ece31b9241c7b1ad6c3bf47f48ac7e5cfc Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Fri, 1 Nov 2019 16:29:30 +0530 Subject: [PATCH 046/369] revert for composer json change --- app/code/Magento/Sales/composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/composer.json b/app/code/Magento/Sales/composer.json index 3b20f6e43e721..8d4ef50a2ebc5 100644 --- a/app/code/Magento/Sales/composer.json +++ b/app/code/Magento/Sales/composer.json @@ -47,6 +47,5 @@ "psr-4": { "Magento\\Sales\\": "" } - }, - "version": "102.0.4" + } } From aff78240d43a648953131db03a9e18c588a8711d Mon Sep 17 00:00:00 2001 From: Hirokazu Nishi Date: Mon, 4 Nov 2019 12:00:58 +0900 Subject: [PATCH 047/369] fix for #24637 . --- lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 4dafc845309cb..3a2055259ec76 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -607,7 +607,7 @@ define([ } this.addContentEditableAttributeBackToNonEditableNodes(); - this.fixRangeSelection(editor); + //this.fixRangeSelection(editor); content = editor.getContent(); content = this.decodeContent(content); From 6fe2ab2dbf043fad74c218cbd2a4d699843f573f Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Sat, 9 Nov 2019 11:59:42 +0530 Subject: [PATCH 048/369] git merge with 2.3-develop fix --- .../Sales/Block/Adminhtml/Order/Create/Form/Account.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 3b4fbac981f2e..6a62ba7fbf0fd 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -38,9 +38,7 @@ class Account extends AbstractForm * @var \Magento\Framework\Api\ExtensibleDataObjectConverter */ protected $_extensibleDataObjectConverter; - private const XML_PATH_EMAIL_REQUIRED_CREATE_ORDER = 'customer/create_account/email_required_create_order'; - /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Backend\Model\Session\Quote $sessionQuote @@ -168,20 +166,19 @@ public function getFormValues() } catch (\Exception $e) { $data = []; } - - $data = isset($customer) + $data = isset($customer) ? $this->_extensibleDataObjectConverter->toFlatArray( $customer, [], \Magento\Customer\Api\Data\CustomerInterface::class ) : []; - - foreach ($this->getQuote()->getData() as $key => $value) { + foreach ($this->getQuote()->getData() as $key => $value) { if (strpos($key, 'customer_') === 0) { $data[substr($key, 9)] = $value; } } + if ($this->getQuote()->getCustomerEmail()) { $data['email'] = $this->getQuote()->getCustomerEmail(); } From 74cfd180853f41d9b6634267c82b7e51cc99ff56 Mon Sep 17 00:00:00 2001 From: solwininfotech Date: Wed, 20 Nov 2019 15:09:51 +0530 Subject: [PATCH 049/369] merge with 2.3 --- app/code/Magento/Sales/Model/AdminOrder/Create.php | 9 +++++---- app/code/Magento/Sales/etc/config.xml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index bc4c7a1ab47cf..d1b3e67815098 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -2038,11 +2038,11 @@ protected function _validate() protected function _getNewCustomerEmail() { $email = $this->getData('account/email'); - + if ($email || $this->isEmailRequired()) { return $email; } - + return $this->generateEmail(); } @@ -2055,7 +2055,8 @@ private function isEmailRequired(): bool { return (bool)$this->_scopeConfig->getValue( self::XML_PATH_EMAIL_REQUIRED_CREATE_ORDER, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $this->_session->getStore()->getId() ); } @@ -2075,7 +2076,7 @@ private function generateEmail(): string $account = $this->getData('account'); $account['email'] = $email; $this->setData('account', $account); - + return $email; } diff --git a/app/code/Magento/Sales/etc/config.xml b/app/code/Magento/Sales/etc/config.xml index cb921b8b0a1bc..1a881fbea2449 100644 --- a/app/code/Magento/Sales/etc/config.xml +++ b/app/code/Magento/Sales/etc/config.xml @@ -113,7 +113,7 @@ - 0 + 1 From f96d993184b3aea5512a2c4976f459e712cc14f4 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky Date: Wed, 20 Nov 2019 12:20:33 +0200 Subject: [PATCH 050/369] magento/magento2#25540: Products are not displaying infront end after updating product via importing CSV. --- .../Model/Import/Product/Type/Bundle.php | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php index 81a47d72602b7..33a7d2efaa273 100644 --- a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php +++ b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php @@ -6,14 +6,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\BundleImportExport\Model\Import\Product\Type; -use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory as AttributeCollectionFactory; -use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory; -use Magento\Framework\App\ObjectManager; use Magento\Bundle\Model\Product\Price as BundlePrice; use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory as AttributeCollectionFactory; use Magento\CatalogImportExport\Model\Import\Product; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\StoreManagerInterface; @@ -26,6 +27,9 @@ */ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType { + /** + * phpcs:disable Magento2.Commenting.ConstantsPHPDocFormatting + */ /** * Delimiter before product option value. @@ -62,6 +66,10 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst */ const SELECTION_PRICE_TYPE_PERCENT = 1; + /** + * phpcs:enable Magento2.Commenting.ConstantsPHPDocFormatting + */ + /** * Array of cached options. * @@ -133,7 +141,7 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst protected $_optionTypeMapping = [ 'dropdown' => 'select', 'radiobutton' => 'radio', - 'checkbox' => 'checkbox', + 'checkbox' => 'checkbox', 'multiselect' => 'multi', ]; @@ -543,7 +551,7 @@ protected function populateExistingSelections($existingOptions) ? $this->_bundleFieldMapping[$origKey] : $origKey; if ( - !isset($this->_cachedOptions[$existingSelection['parent_product_id']][$optionTitle]['selections'][$selectIndex][$key]) + !isset($this->_cachedOptions[$existingSelection['parent_product_id']][$optionTitle]['selections'][$selectIndex][$key]) ) { $this->_cachedOptions[$existingSelection['parent_product_id']][$optionTitle]['selections'][$selectIndex][$key] = $existingSelection[$origKey]; @@ -616,6 +624,7 @@ protected function populateInsertOptionValues(array $optionIds): array if ($assoc['position'] == $this->_cachedOptions[$entityId][$key]['index'] && $assoc['parent_id'] == $entityId) { $option['parent_id'] = $entityId; + //phpcs:ignore Magento2.Performance.ForeachArrayMerge $optionValues = array_merge( $optionValues, $this->populateOptionValueTemplate($option, $optionId) @@ -675,10 +684,7 @@ private function insertParentChildRelations() $childIds = []; foreach ($options as $option) { foreach ($option['selections'] as $selection) { - if (!isset($selection['parent_product_id'])) { - if (!isset($this->_cachedSkuToProducts[$selection['sku']])) { - continue; - } + if (isset($this->_cachedSkuToProducts[$selection['sku']])) { $childIds[] = $this->_cachedSkuToProducts[$selection['sku']]; } } @@ -735,17 +741,19 @@ protected function deleteOptionsAndSelections($productIds) $optionTable = $this->_resource->getTableName('catalog_product_bundle_option'); $optionValueTable = $this->_resource->getTableName('catalog_product_bundle_option_value'); $selectionTable = $this->_resource->getTableName('catalog_product_bundle_selection'); - $valuesIds = $this->connection->fetchAssoc($this->connection->select()->from( - ['bov' => $optionValueTable], - ['value_id'] - )->joinLeft( - ['bo' => $optionTable], - 'bo.option_id = bov.option_id', - ['option_id'] - )->where( - 'parent_id IN (?)', - $productIds - )); + $valuesIds = $this->connection->fetchAssoc( + $this->connection->select()->from( + ['bov' => $optionValueTable], + ['value_id'] + )->joinLeft( + ['bo' => $optionTable], + 'bo.option_id = bov.option_id', + ['option_id'] + )->where( + 'parent_id IN (?)', + $productIds + ) + ); $this->connection->delete( $optionValueTable, $this->connection->quoteInto('value_id IN (?)', array_keys($valuesIds)) From 5c121e20f909d666079654c6aa7ff325cc094278 Mon Sep 17 00:00:00 2001 From: Andrii Beziazychnyi Date: Sun, 24 Nov 2019 20:43:57 +0200 Subject: [PATCH 051/369] Magento#25669: health_check.php fails if any database cache engine configured - fixed wrong "@return description" of the remove method in class Magento\Framework\Cache\Backend\Database - fixed wrong returned type of the method "unlock" in class Magento\Framework\Lock\Backend\Cache - fixed health_check to create instances into block "check cache storage availability" --- lib/internal/Magento/Framework/Cache/Backend/Database.php | 2 +- lib/internal/Magento/Framework/Lock/Backend/Cache.php | 2 +- pub/health_check.php | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/Cache/Backend/Database.php b/lib/internal/Magento/Framework/Cache/Backend/Database.php index 231a8584cc8a5..c562957b9bf51 100644 --- a/lib/internal/Magento/Framework/Cache/Backend/Database.php +++ b/lib/internal/Magento/Framework/Cache/Backend/Database.php @@ -240,7 +240,7 @@ public function save($data, $id, $tags = [], $specificLifetime = false) * Remove a cache record * * @param string $id Cache id - * @return boolean True if no problem + * @return int|boolean Number of affected rows or false on failure */ public function remove($id) { diff --git a/lib/internal/Magento/Framework/Lock/Backend/Cache.php b/lib/internal/Magento/Framework/Lock/Backend/Cache.php index dfe6bbb828352..b6faca8380667 100644 --- a/lib/internal/Magento/Framework/Lock/Backend/Cache.php +++ b/lib/internal/Magento/Framework/Lock/Backend/Cache.php @@ -45,7 +45,7 @@ public function lock(string $name, int $timeout = -1): bool */ public function unlock(string $name): bool { - return $this->cache->remove($this->getIdentifier($name)); + return (bool)$this->cache->remove($this->getIdentifier($name)); } /** diff --git a/pub/health_check.php b/pub/health_check.php index 4bdda68851bde..8642877323bbc 100644 --- a/pub/health_check.php +++ b/pub/health_check.php @@ -63,8 +63,10 @@ } $cacheBackendClass = $cacheConfig[ConfigOptionsListConstants::CONFIG_PATH_BACKEND]; try { + /** @var \Magento\Framework\App\Cache\Frontend\Factory $cacheFrontendFactory */ + $cacheFrontendFactory = $objectManager->get(Magento\Framework\App\Cache\Frontend\Factory::class); /** @var \Zend_Cache_Backend_Interface $backend */ - $backend = new $cacheBackendClass($cacheConfig[ConfigOptionsListConstants::CONFIG_PATH_BACKEND_OPTIONS]); + $backend = $cacheFrontendFactory->create($cacheConfig); $backend->test('test_cache_id'); } catch (\Exception $e) { http_response_code(500); From 3905038c09ee3c5f7a9dac63f5f28070c1197434 Mon Sep 17 00:00:00 2001 From: Andrii Beziazychnyi Date: Mon, 25 Nov 2019 08:51:40 +0200 Subject: [PATCH 052/369] Magento#25669: health_check.php fails if any database cache engine configured - fixed PHPDocs according to requirements static tests for CE --- .../Framework/Cache/Backend/Database.php | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/lib/internal/Magento/Framework/Cache/Backend/Database.php b/lib/internal/Magento/Framework/Cache/Backend/Database.php index c562957b9bf51..a33c8f1a0de0e 100644 --- a/lib/internal/Magento/Framework/Cache/Backend/Database.php +++ b/lib/internal/Magento/Framework/Cache/Backend/Database.php @@ -59,6 +59,7 @@ class Database extends \Zend_Cache_Backend implements \Zend_Cache_Backend_Extend * Constructor * * @param array $options associative array of options + * @throws \Zend_Cache_Exception */ public function __construct($options = []) { @@ -82,6 +83,7 @@ public function __construct($options = []) * Get DB adapter * * @return \Magento\Framework\DB\Adapter\AdapterInterface + * @throws \Zend_Cache_Exception */ protected function _getConnection() { @@ -106,6 +108,7 @@ protected function _getConnection() * Get table name where data is stored * * @return string + * @throws \Zend_Cache_Exception */ protected function _getDataTable() { @@ -122,6 +125,7 @@ protected function _getDataTable() * Get table name where tags are stored * * @return string + * @throws \Zend_Cache_Exception */ protected function _getTagsTable() { @@ -139,9 +143,10 @@ protected function _getTagsTable() * * Note : return value is always "string" (unserialization is done by the core not by the backend) * - * @param string $id Cache id - * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested + * @param string $id Cache id + * @param boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested * @return string|false cached datas + * @throws \Zend_Cache_Exception */ public function load($id, $doNotTestCacheValidity = false) { @@ -166,8 +171,9 @@ public function load($id, $doNotTestCacheValidity = false) /** * Test if a cache is available or not (for the given id) * - * @param string $id cache id + * @param string $id cache id * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record + * @throws \Zend_Cache_Exception */ public function test($id) { @@ -196,11 +202,13 @@ public function test($id) * Note : $data is always "string" (serialization is done by the * core not by the backend) * - * @param string $data Datas to cache - * @param string $id Cache id - * @param string[] $tags Array of strings, the cache record will be tagged by each string entry - * @param int|bool $specificLifetime Integer to set a specific lifetime or null for infinite lifetime + * @param string $data Datas to cache + * @param string $id Cache id + * @param string[] $tags Array of strings, the cache record will be tagged by each string entry + * @param int|bool $specificLifetime Integer to set a specific lifetime or null for infinite lifetime * @return bool true if no problem + * @throws \Zend_Db_Statement_Exception + * @throws \Zend_Cache_Exception */ public function save($data, $id, $tags = [], $specificLifetime = false) { @@ -239,8 +247,9 @@ public function save($data, $id, $tags = [], $specificLifetime = false) /** * Remove a cache record * - * @param string $id Cache id + * @param string $id Cache id * @return int|boolean Number of affected rows or false on failure + * @throws \Zend_Cache_Exception */ public function remove($id) { @@ -266,9 +275,10 @@ public function remove($id) * \Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags * ($tags can be an array of strings or a single string) * - * @param string $mode Clean mode - * @param string[] $tags Array of tags + * @param string $mode Clean mode + * @param string[] $tags Array of tags * @return boolean true if no problem + * @throws \Zend_Cache_Exception */ public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = []) { @@ -301,6 +311,7 @@ public function clean($mode = \Zend_Cache::CLEANING_MODE_ALL, $tags = []) * Return an array of stored cache ids * * @return string[] array of stored cache ids (string) + * @throws \Zend_Cache_Exception */ public function getIds() { @@ -316,6 +327,7 @@ public function getIds() * Return an array of stored tags * * @return string[] array of stored tags (string) + * @throws \Zend_Cache_Exception */ public function getTags() { @@ -330,6 +342,7 @@ public function getTags() * * @param string[] $tags array of tags * @return string[] array of matching cache ids (string) + * @throws \Zend_Cache_Exception */ public function getIdsMatchingTags($tags = []) { @@ -356,6 +369,7 @@ public function getIdsMatchingTags($tags = []) * * @param string[] $tags array of tags * @return string[] array of not matching cache ids (string) + * @throws \Zend_Cache_Exception */ public function getIdsNotMatchingTags($tags = []) { @@ -369,6 +383,7 @@ public function getIdsNotMatchingTags($tags = []) * * @param string[] $tags array of tags * @return string[] array of any matching cache ids (string) + * @throws \Zend_Cache_Exception */ public function getIdsMatchingAnyTags($tags = []) { @@ -404,6 +419,7 @@ public function getFillingPercentage() * * @param string $id cache id * @return array|false array of metadatas (false if the cache id is not found) + * @throws \Zend_Cache_Exception */ public function getMetadatas($id) { @@ -425,6 +441,7 @@ public function getMetadatas($id) * @param string $id cache id * @param int $extraLifetime * @return boolean true if ok + * @throws \Zend_Cache_Exception */ public function touch($id, $extraLifetime) { @@ -471,6 +488,7 @@ public function getCapabilities() * @param string $id * @param string[] $tags * @return bool + * @throws \Zend_Cache_Exception */ protected function _saveTags($id, $tags) { @@ -509,6 +527,8 @@ protected function _saveTags($id, $tags) * @param string $mode * @param string[] $tags * @return bool + * @throws \Zend_Cache_Exception + * @throws \Zend_Db_Statement_Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _cleanByTags($mode, $tags) @@ -558,6 +578,7 @@ protected function _cleanByTags($mode, $tags) * * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @return bool + * @throws \Zend_Cache_Exception */ private function cleanAll(\Magento\Framework\DB\Adapter\AdapterInterface $connection) { @@ -575,6 +596,7 @@ private function cleanAll(\Magento\Framework\DB\Adapter\AdapterInterface $connec * * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @return bool + * @throws \Zend_Cache_Exception */ private function cleanOld(\Magento\Framework\DB\Adapter\AdapterInterface $connection) { From 52624d2f3f8749edf5c863dec4095f622720c236 Mon Sep 17 00:00:00 2001 From: Eden Date: Mon, 25 Nov 2019 21:52:09 +0700 Subject: [PATCH 053/369] Resolve Mass Delete Widget should have "Confirmation Modal" --- app/code/Magento/Widget/i18n/en_US.csv | 1 + .../view/adminhtml/layout/adminhtml_widget_instance_block.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/code/Magento/Widget/i18n/en_US.csv b/app/code/Magento/Widget/i18n/en_US.csv index 4156b94a1f988..3592cbe9a2042 100644 --- a/app/code/Magento/Widget/i18n/en_US.csv +++ b/app/code/Magento/Widget/i18n/en_US.csv @@ -71,3 +71,4 @@ Product,Product Conditions,Conditions "Widget ID","Widget ID" "Inserting a widget does not create a widget instance.","Inserting a widget does not create a widget instance." +"Are you sure you want to delete the selected widget(s)?","Are you sure you want to delete the selected widget(s)?" diff --git a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml index c78f9ec225be4..1604ac5ba1122 100644 --- a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml +++ b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml @@ -25,6 +25,7 @@ Delete */*/massDelete 0 + Are you sure you want to delete the selected widget(s)? From 2e9d1bb5c421264d62d7fbdf00c3cc266b298285 Mon Sep 17 00:00:00 2001 From: Lewis Voncken Date: Mon, 25 Nov 2019 16:57:59 +0100 Subject: [PATCH 054/369] [BUGFIX] Make sure disabled products are removed from the Product Flat Table --- .../Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php index 7b2a6272c7052..8cafc82bf77d6 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php @@ -67,6 +67,7 @@ public function execute($ids) foreach ($idsBatches as $changedIds) { if ($tableExists) { $this->flatItemEraser->removeDeletedProducts($changedIds, $store->getId()); + $this->flatItemEraser->removeDisabledProducts($changedIds, $store->getId()); } if (!empty($changedIds)) { $this->_reindex($store->getId(), $changedIds); From c6d3c8e5161356efada9cf8278e355280efc3981 Mon Sep 17 00:00:00 2001 From: Lewis Voncken Date: Mon, 25 Nov 2019 17:20:37 +0100 Subject: [PATCH 055/369] [BUGFIX] Solved invalid link field for Enterprise implementation --- .../Catalog/Model/Indexer/Product/Flat/Action/Eraser.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php index c4d807667bfbc..ba209884fbb53 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php @@ -99,7 +99,7 @@ public function removeDisabledProducts(array &$ids, $storeId) ['status_global_attr' => $statusAttribute->getBackendTable()], ' status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID - . ' AND status_global_attr.' . $statusAttribute->getEntityIdField() . '=' + . ' AND status_global_attr.' . $metadata->getLinkField() . '=' . 'product_table.' . $metadata->getLinkField(), [] ); @@ -107,7 +107,7 @@ public function removeDisabledProducts(array &$ids, $storeId) ['status_attr' => $statusAttribute->getBackendTable()], ' status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId() . ' AND status_attr.store_id = ' . $storeId - . ' AND status_attr.' . $statusAttribute->getEntityIdField() . '=' + . ' AND status_attr.' . $metadata->getLinkField() . '=' . 'product_table.' . $metadata->getLinkField(), [] ); From b906d2b40fc3401667878408d8cb97615cf7350f Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Sat, 30 Nov 2019 14:43:55 +0000 Subject: [PATCH 056/369] Use interface constant not hard-coded string --- .../Block/Backend/Grid/Column/Renderer/ScheduleStatus.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php index 712b17b91a0d7..0c8083d57bcd8 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -74,7 +74,7 @@ public function render(\Magento\Framework\DataObject $row) $class = 'grid-severity-notice'; } - if ($state->getStatus() !== 'idle') { + if ($state->getStatus() !== $state::STATUS_IDLE) { $class = 'grid-severity-minor'; } From e6e4bb80e44266502f1236e299bacc9824679421 Mon Sep 17 00:00:00 2001 From: Dan Wallis Date: Tue, 10 Dec 2019 18:54:58 +0000 Subject: [PATCH 057/369] Add class comments to appease linter --- .../Block/Backend/Grid/Column/Renderer/ScheduleStatus.php | 3 +++ .../Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php | 3 +++ .../Indexer/Block/Backend/Grid/Column/Renderer/Status.php | 3 +++ .../Indexer/Block/Backend/Grid/Column/Renderer/Updated.php | 3 +++ 4 files changed, 12 insertions(+) diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php index 0c8083d57bcd8..7737077609b51 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -10,6 +10,9 @@ use Magento\Framework\Mview\View; use Magento\Framework\Phrase; +/** + * Renderer for 'Schedule Status' column in indexer grid + */ class ScheduleStatus extends AbstractRenderer { /** diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php index d5a749270a66f..adfe3dd5b346b 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Scheduled.php @@ -5,6 +5,9 @@ */ namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; +/** + * Renderer for 'Scheduled' column in indexer grid + */ class Scheduled extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { /** diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php index 144d44fe42880..e8d6004b2727d 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Status.php @@ -5,6 +5,9 @@ */ namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; +/** + * Renderer for 'Status' column in indexer grid + */ class Status extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { /** diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Updated.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Updated.php index 92ded9c1f0dc1..6c84ef0e628f7 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Updated.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/Updated.php @@ -5,6 +5,9 @@ */ namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; +/** + * Renderer for 'Updated' column in indexer grid + */ class Updated extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\Datetime { /** From ed0cdb1293e0c98eb48ba11bdf39657641218b3f Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Thu, 12 Dec 2019 16:09:25 +0100 Subject: [PATCH 058/369] [WIP] Remove media gallery assets metadata when a directory removed --- .../Asset/Command/DeleteByDirectoryPath.php | 93 +++++++++++++++++++ .../Plugin/Wysiwyg/Images/Storage.php | 43 ++++++++- app/code/Magento/MediaGallery/etc/di.xml | 1 + .../DeleteByDirectoryPathInterface.php | 25 +++++ 4 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php create mode 100644 app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByDirectoryPathInterface.php diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php new file mode 100644 index 0000000000000..e9b22588928d5 --- /dev/null +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php @@ -0,0 +1,93 @@ +resourceConnection = $resourceConnection; + $this->logger = $logger; + } + + /** + * Delete media asset by path + * + * @param string $directoryPath + * + * @return void + * @throws CouldNotDeleteException + */ + public function execute(string $directoryPath): void + { + try { + $this->validateDirectoryPath($directoryPath); + + if (substr($directoryPath, -1) !== '/') { + $directoryPath .= '/'; + } + + /** @var AdapterInterface $connection */ + $connection = $this->resourceConnection->getConnection(); + $tableName = $this->resourceConnection->getTableName(self::TABLE_MEDIA_GALLERY_ASSET); + $connection->delete($tableName, [self::MEDIA_GALLERY_ASSET_PATH . ' LIKE ?' => $directoryPath . '%']); + } catch (\Exception $exception) { + $this->logger->critical($exception); + $message = __( + 'Could not delete media assets by path %path: %error', + ['path' => $directoryPath, 'error' => $exception->getMessage()] + ); + throw new CouldNotDeleteException($message, $exception); + } + } + + /** + * Validate the directory path + * + * @param string $directoryPath + * @throws LocalizedException + */ + private function validateDirectoryPath(string $directoryPath): void + { + if (trim($directoryPath) === '') { + throw new LocalizedException(__('The directory path cannot be empty')); + } + } +} diff --git a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php index ff0e1528e0597..1fcad5765e703 100644 --- a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php @@ -8,12 +8,12 @@ namespace Magento\MediaGallery\Plugin\Wysiwyg\Images; +use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByDirectoryPathInterface; use Magento\MediaGalleryApi\Model\Asset\Command\GetByPathInterface; use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByPathInterface; use Magento\Cms\Model\Wysiwyg\Images\Storage as StorageSubject; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; -use Magento\Framework\Exception\ValidatorException; use Psr\Log\LoggerInterface; /** @@ -31,6 +31,11 @@ class Storage */ private $deleteMediaAssetByPath; + /** + * @var DeleteByDirectoryPathInterface + */ + private $deleteMediAssetByDirectoryPath; + /** * @var Filesystem */ @@ -46,17 +51,20 @@ class Storage * * @param GetByPathInterface $getMediaAssetByPath * @param DeleteByPathInterface $deleteMediaAssetByPath + * @param DeleteByDirectoryPathInterface $deleteByDirectoryPath * @param Filesystem $filesystem * @param LoggerInterface $logger */ public function __construct( GetByPathInterface $getMediaAssetByPath, DeleteByPathInterface $deleteMediaAssetByPath, + DeleteByDirectoryPathInterface $deleteByDirectoryPath, Filesystem $filesystem, LoggerInterface $logger ) { $this->getMediaAssetByPath = $getMediaAssetByPath; $this->deleteMediaAssetByPath = $deleteMediaAssetByPath; + $this->deleteMediAssetByDirectoryPath = $deleteByDirectoryPath; $this->filesystem = $filesystem; $this->logger = $logger; } @@ -69,7 +77,6 @@ public function __construct( * @param string $target * * @return StorageSubject - * @throws ValidatorException * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -92,4 +99,36 @@ public function afterDeleteFile(StorageSubject $subject, StorageSubject $result, return $result; } + + /** + * Delete media data after the folder delete action from Wysiwyg + * + * @param StorageSubject $subject + * @param null $result + * @param string $path + * + * @return null + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDeleteDirectory(StorageSubject $subject, $result, $path) + { + if (!is_string($path)) { + return $result; + } + + $relativePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getRelativePath($path); + + if (!$relativePath) { + return $result; + } + + try { + $this->deleteMediAssetByDirectoryPath->execute($relativePath); + } catch (\Exception $exception) { + $this->logger->critical($exception); + } + + return $result; + } } diff --git a/app/code/Magento/MediaGallery/etc/di.xml b/app/code/Magento/MediaGallery/etc/di.xml index 8c4a856852e1a..24ed42b2b06dd 100644 --- a/app/code/Magento/MediaGallery/etc/di.xml +++ b/app/code/Magento/MediaGallery/etc/di.xml @@ -13,6 +13,7 @@ + diff --git a/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByDirectoryPathInterface.php b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByDirectoryPathInterface.php new file mode 100644 index 0000000000000..e55f7cb714f77 --- /dev/null +++ b/app/code/Magento/MediaGalleryApi/Model/Asset/Command/DeleteByDirectoryPathInterface.php @@ -0,0 +1,25 @@ + Date: Thu, 26 Dec 2019 07:18:57 +0100 Subject: [PATCH 059/369] Refactoring and unit test coverage --- .../Asset/Command/DeleteByDirectoryPath.php | 9 +- .../Plugin/Wysiwyg/Images/Storage.php | 11 +- .../Command/DeleteByDirectoryPathTest.php | 105 ++++++++++++++++++ 3 files changed, 110 insertions(+), 15 deletions(-) create mode 100644 app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/DeleteByDirectoryPathTest.php diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php index e9b22588928d5..c83ac23ddb0d7 100644 --- a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php @@ -60,9 +60,8 @@ public function execute(string $directoryPath): void try { $this->validateDirectoryPath($directoryPath); - if (substr($directoryPath, -1) !== '/') { - $directoryPath .= '/'; - } + // Make sure that the path has a trailing slash + $directoryPath = rtrim($directoryPath, '/') . '/'; /** @var AdapterInterface $connection */ $connection = $this->resourceConnection->getConnection(); @@ -86,8 +85,8 @@ public function execute(string $directoryPath): void */ private function validateDirectoryPath(string $directoryPath): void { - if (trim($directoryPath) === '') { - throw new LocalizedException(__('The directory path cannot be empty')); + if (!$directoryPath || trim($directoryPath) === '') { + throw new LocalizedException(__('Cannot remove assets, the directory path does not exist')); } } } diff --git a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php index 1fcad5765e703..5fbe370e36a3c 100644 --- a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php @@ -118,16 +118,7 @@ public function afterDeleteDirectory(StorageSubject $subject, $result, $path) } $relativePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getRelativePath($path); - - if (!$relativePath) { - return $result; - } - - try { - $this->deleteMediAssetByDirectoryPath->execute($relativePath); - } catch (\Exception $exception) { - $this->logger->critical($exception); - } + $this->deleteMediAssetByDirectoryPath->execute($relativePath); return $result; } diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/DeleteByDirectoryPathTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/DeleteByDirectoryPathTest.php new file mode 100644 index 0000000000000..ebc90cdc0470f --- /dev/null +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/DeleteByDirectoryPathTest.php @@ -0,0 +1,105 @@ +logger = $this->createMock(LoggerInterface::class); + $this->resourceConnection = $this->createMock(ResourceConnection::class); + + $this->deleteMediaAssetByDirectoryPath = (new ObjectManager($this))->getObject( + DeleteByDirectoryPath::class, + [ + 'resourceConnection' => $this->resourceConnection, + 'logger' => $this->logger, + ] + ); + + $this->adapter = $this->createMock(AdapterInterface::class); + } + + /** + * Test delete media asset by path command + * + * @param string $directoryPath + * @throws CouldNotDeleteException + * @dataProvider directoryPathDataProvider + */ + public function testDeleteByDirectoryPath(string $directoryPath): void + { + if (!empty($directoryPath)) { + $this->resourceConnection->expects($this->once()) + ->method('getConnection') + ->willReturn($this->adapter); + $this->resourceConnection->expects($this->once()) + ->method('getTableName') + ->with(self::TABLE_NAME) + ->willReturn('prefix_' . self::TABLE_NAME); + $this->adapter->expects($this->once()) + ->method('delete') + ->with('prefix_' . self::TABLE_NAME, ['path LIKE ?' => self::DIRECTORY_PATH . '%']); + } else { + self::expectException('\Magento\Framework\Exception\CouldNotDeleteException'); + } + + $this->deleteMediaAssetByDirectoryPath->execute($directoryPath); + } + + /** + * Data provider for directory path + * + * @return array + */ + public function directoryPathDataProvider(): array + { + return [ + 'Existing path' => [self::DIRECTORY_PATH], + 'Empty path' => [''] + ]; + } +} From 145cb44e195c9d66c64c2b5f5d3e3f7371cdec15 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Mon, 30 Dec 2019 15:22:09 +0100 Subject: [PATCH 060/369] Added an extended class purpose description --- .../Model/Asset/Command/DeleteByDirectoryPath.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php index c83ac23ddb0d7..c985907673818 100644 --- a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php @@ -16,6 +16,8 @@ /** * Class DeleteByDirectoryPath + * + * Remove asset(s) that correspond the provided directory path */ class DeleteByDirectoryPath implements DeleteByDirectoryPathInterface { @@ -48,7 +50,7 @@ public function __construct( } /** - * Delete media asset by path + * Delete media asset(s) by path * * @param string $directoryPath * From 8679c9a0cad176f556fd6d4893dffd25fa24a88a Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Mon, 30 Dec 2019 16:37:04 +0100 Subject: [PATCH 061/369] Changed DocBlock description of the parameter type --- app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php index 5fbe370e36a3c..d741067228b09 100644 --- a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php @@ -104,7 +104,7 @@ public function afterDeleteFile(StorageSubject $subject, StorageSubject $result, * Delete media data after the folder delete action from Wysiwyg * * @param StorageSubject $subject - * @param null $result + * @param mixed $result * @param string $path * * @return null From 847e5bcc38c9175c5dba0a0eaa4aaf377c34ca59 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza Date: Tue, 7 Jan 2020 14:43:26 +0100 Subject: [PATCH 062/369] Errors handling refactoring and additional test coverage --- .../Asset/Command/DeleteByDirectoryPath.php | 10 +- .../Plugin/Wysiwyg/Images/Storage.php | 19 +- .../Test/Unit/Plugin/Images/StorageTest.php | 162 ++++++++++++++++++ 3 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php diff --git a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php index c985907673818..a50095fb3d8e7 100644 --- a/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php +++ b/app/code/Magento/MediaGallery/Model/Asset/Command/DeleteByDirectoryPath.php @@ -10,7 +10,6 @@ use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\Exception\CouldNotDeleteException; -use Magento\Framework\Exception\LocalizedException; use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByDirectoryPathInterface; use Psr\Log\LoggerInterface; @@ -55,13 +54,13 @@ public function __construct( * @param string $directoryPath * * @return void + * * @throws CouldNotDeleteException */ public function execute(string $directoryPath): void { + $this->validateDirectoryPath($directoryPath); try { - $this->validateDirectoryPath($directoryPath); - // Make sure that the path has a trailing slash $directoryPath = rtrim($directoryPath, '/') . '/'; @@ -83,12 +82,13 @@ public function execute(string $directoryPath): void * Validate the directory path * * @param string $directoryPath - * @throws LocalizedException + * + * @throws CouldNotDeleteException */ private function validateDirectoryPath(string $directoryPath): void { if (!$directoryPath || trim($directoryPath) === '') { - throw new LocalizedException(__('Cannot remove assets, the directory path does not exist')); + throw new CouldNotDeleteException(__('Cannot remove assets, the directory path does not exist')); } } } diff --git a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php index d741067228b09..7a4aa219cdf29 100644 --- a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php @@ -8,6 +8,7 @@ namespace Magento\MediaGallery\Plugin\Wysiwyg\Images; +use Magento\Framework\Exception\CouldNotDeleteException; use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByDirectoryPathInterface; use Magento\MediaGalleryApi\Model\Asset\Command\GetByPathInterface; use Magento\MediaGalleryApi\Model\Asset\Command\DeleteByPathInterface; @@ -15,6 +16,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; use Psr\Log\LoggerInterface; +use Magento\Framework\Exception\ValidatorException; /** * Ensures that metadata is removed from the database when a file is deleted and it is an image @@ -34,7 +36,7 @@ class Storage /** * @var DeleteByDirectoryPathInterface */ - private $deleteMediAssetByDirectoryPath; + private $deleteMediaAssetByDirectoryPath; /** * @var Filesystem @@ -64,7 +66,7 @@ public function __construct( ) { $this->getMediaAssetByPath = $getMediaAssetByPath; $this->deleteMediaAssetByPath = $deleteMediaAssetByPath; - $this->deleteMediAssetByDirectoryPath = $deleteByDirectoryPath; + $this->deleteMediaAssetByDirectoryPath = $deleteByDirectoryPath; $this->filesystem = $filesystem; $this->logger = $logger; } @@ -109,6 +111,7 @@ public function afterDeleteFile(StorageSubject $subject, StorageSubject $result, * * @return null * + * @throws CouldNotDeleteException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterDeleteDirectory(StorageSubject $subject, $result, $path) @@ -117,8 +120,16 @@ public function afterDeleteDirectory(StorageSubject $subject, $result, $path) return $result; } - $relativePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getRelativePath($path); - $this->deleteMediAssetByDirectoryPath->execute($relativePath); + try { + $mediaDirectoryRead = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); + $relativePath = $mediaDirectoryRead->getRelativePath($path); + if ($mediaDirectoryRead->isExist($relativePath) === false) { + throw new CouldNotDeleteException(__('Cannot remove assets, the provided path does not exist')); + } + $this->deleteMediaAssetByDirectoryPath->execute($relativePath); + } catch (ValidatorException $exception) { + $this->logger->critical($exception); + } return $result; } diff --git a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php b/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php new file mode 100644 index 0000000000000..f57d6e9c7635b --- /dev/null +++ b/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php @@ -0,0 +1,162 @@ +logger = $this->createMock(LoggerInterface::class); + $this->getMediaAssetByPath = $this->createMock(GetByPathInterface::class); + $this->deleteMediaAssetByPath = $this->createMock(DeleteByPathInterface::class); + $this->deleteMediaAssetByDirectoryPath = $this->createMock(DeleteByDirectoryPath::class); + $this->filesystem = $this->createMock(Filesystem::class); + + $this->storagePlugin = (new ObjectManager($this))->getObject( + StoragePlugin::class, + [ + 'getMediaAssetByPath' => $this->getMediaAssetByPath, + 'deleteMediaAssetByPath' => $this->deleteMediaAssetByPath, + 'deleteByDirectoryPath' => $this->deleteMediaAssetByDirectoryPath, + 'filesystem' => $this->filesystem, + 'logger' => $this->logger + ] + ); + } + + /** + * @param string $path + * + * @dataProvider pathPathDataProvider + */ + public function testAfterDeleteDirectory(string $path): void + { + /** @var StorageSubject|MockObject $storageSubject */ + $storageSubject = $this->getMockBuilder(StorageSubject::class) + ->disableOriginalConstructor() + ->getMock(); + $directoryRead = $this->createMock(ReadInterface::class); + $this->filesystem->expects($this->any()) + ->method('getDirectoryRead') + ->willReturn($directoryRead); + + switch ($path) { + case self::NON_STRING_PATH: + $result = $this->storagePlugin->afterDeleteDirectory($storageSubject, null, (int)$path); + self::assertNull($result); + break; + case self::NON_EXISTENT_PATH: + $directoryRead->expects($this->once()) + ->method('getRelativePath') + ->with($path) + ->willReturn($path); + $directoryRead->expects($this->once()) + ->method('isExist') + ->with($path) + ->willReturn(false); + self::expectException('Magento\Framework\Exception\CouldNotDeleteException'); + $this->storagePlugin->afterDeleteDirectory($storageSubject, null, $path); + break; + case self::INVALID_PATH: + $exception = new ValidatorException(__('Path cannot be used with directory')); + $directoryRead->expects($this->once()) + ->method('getRelativePath') + ->with($path) + ->willThrowException($exception); + $this->logger->expects($this->once()) + ->method('critical') + ->with($exception); + $this->storagePlugin->afterDeleteDirectory($storageSubject, null, $path); + break; + case self::VALID_PATH: + $directoryRead->expects($this->once()) + ->method('getRelativePath') + ->with($path) + ->willReturn($path); + $directoryRead->expects($this->once()) + ->method('isExist') + ->with($path) + ->willReturn(true); + $this->deleteMediaAssetByDirectoryPath->expects($this->once()) + ->method('execute') + ->with($path); + $this->storagePlugin->afterDeleteDirectory($storageSubject, null, $path); + break; + } + } + + /** + * Data provider for path + * + * @return array + */ + public function pathPathDataProvider(): array + { + return [ + 'Non string path' => [2020], + 'Non-existent path' => [self::NON_EXISTENT_PATH], + 'Invalid path' => [self::INVALID_PATH], + 'Existent path' => [self::VALID_PATH] + ]; + } +} From 16079d95e30cc6be2aa6a35b79f80db9e90ac155 Mon Sep 17 00:00:00 2001 From: Mudit Shukla Date: Sat, 11 Jan 2020 03:27:50 +0530 Subject: [PATCH 063/369] Update Reorder.php Update Reorder.php so that it can handle the Localized Exception and added try catch block --- .../Adminhtml/Order/Create/Reorder.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php index 995a6c216d3c2..04013df790a1d 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php @@ -90,10 +90,18 @@ public function execute() } $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]); } else { - $order->setReordered(true); - $this->_getSession()->setUseOldShippingMethod(true); - $this->_getOrderCreateModel()->initFromOrder($order); - $resultRedirect->setPath('sales/*'); + try { + $order->setReordered(true); + $this->_getSession()->setUseOldShippingMethod(true); + $this->_getOrderCreateModel()->initFromOrder($order); + $resultRedirect->setPath('sales/*'); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + return $resultRedirect->setPath('sales/*'); + } catch (\Exception $e) { + $this->messageManager->addException($e, __('Error while processing order.')); + return $resultRedirect->setPath('sales/*'); + } } return $resultRedirect; From ec609e33ba13a384c1f108da5ae95d894534b443 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev Date: Fri, 17 Jan 2020 16:27:17 +0200 Subject: [PATCH 064/369] Migrate ZF2 components to Laminas components --- .../Backend/App/Area/FrontNameResolver.php | 2 +- .../Unit/App/Area/FrontNameResolverTest.php | 4 +- .../CacheInvalidate/Model/PurgeCache.php | 4 +- .../CacheInvalidate/Model/SocketFactory.php | 4 +- .../Test/Unit/Model/PurgeCacheTest.php | 8 +- .../Test/Unit/Model/SocketFactoryTest.php | 2 +- .../Magento/Captcha/Model/DefaultModel.php | 10 +- .../Captcha/Model/ResourceModel/Log.php | 4 +- app/code/Magento/Captcha/composer.json | 6 +- .../Product/Frontend/Action/Synchronize.php | 4 +- .../Frontend/Action/SynchronizeTest.php | 4 +- .../Model/Cart/RequestQuantityProcessor.php | 2 +- .../Customer/Controller/Section/Load.php | 4 +- .../Test/Unit/Controller/Section/LoadTest.php | 4 +- .../Model/Url/DomainValidator.php | 2 +- .../Patch/Data/AddDownloadableHostsConfig.php | 2 +- app/code/Magento/Email/Model/Transport.php | 6 +- .../Test/Unit/Model/Oauth/ConsumerTest.php | 2 +- ...tionDuringMediaAssetInitializationTest.php | 2 +- .../GetByIdExceptionNoSuchEntityTest.php | 2 +- .../Command/GetByIdExceptionOnGetDataTest.php | 2 +- .../Asset/Command/GetByIdSuccessfulTest.php | 2 +- .../Unit/Model/File/Storage/ResponseTest.php | 2 +- .../App/FrontController/BuiltinPlugin.php | 2 +- .../Magento/PageCache/Model/Cache/Server.php | 4 +- .../Model/Controller/Result/BuiltinPlugin.php | 2 +- .../App/FrontController/BuiltinPluginTest.php | 2 +- .../Test/Unit/Model/Cache/ServerTest.php | 2 +- .../Controller/Result/BuiltinPluginTest.php | 2 +- .../Quote/Model/Quote/Item/Updater.php | 2 +- .../Product/Downloads/Collection.php | 2 +- .../Controller/Adminhtml/Feed/IndexTest.php | 2 +- .../Test/Unit/Controller/Feed/IndexTest.php | 2 +- .../Magento/Rss/Test/Unit/Model/RssTest.php | 2 +- .../Magento/Store/App/Response/Redirect.php | 8 +- app/code/Magento/Store/Model/Store.php | 2 +- .../Adminhtml/Design/Config/SaveTest.php | 4 +- .../Ui/Controller/Adminhtml/Index/Render.php | 12 +- .../Magento/Ui/Controller/Index/Render.php | 12 +- .../Controller/Adminhtml/Index/RenderTest.php | 4 +- .../Test/Unit/Controller/Index/RenderTest.php | 4 +- .../Test/Unit/Controller/RouterTest.php | 2 +- .../Webapi/Model/Config/ClassReflector.php | 18 +- app/code/Magento/Webapi/Model/Soap/Wsdl.php | 4 +- .../Model/Soap/Wsdl/ComplexTypeStrategy.php | 4 +- .../Webapi/Test/Unit/Controller/SoapTest.php | 2 +- .../Unit/Model/Config/ClassReflectorTest.php | 4 +- .../Soap/Wsdl/ComplexTypeStrategyTest.php | 2 +- composer.json | 64 +- composer.lock | 4751 +++++++++-------- .../Authentication/OauthHelper.php | 4 +- .../TestCase/Webapi/Adapter/Soap.php | 10 +- .../Magento/TestFramework/Request.php | 2 +- .../testsuite/Magento/Test/RequestTest.php | 2 +- .../App/Request/BackendValidatorTest.php | 2 +- .../Controller/Cards/DeleteActionTest.php | 2 +- .../Controller/Advanced/ResultTest.php | 2 +- .../Customer/Controller/AccountTest.php | 6 +- .../Controller/Adminhtml/IndexTest.php | 4 +- .../Magento/Developer/Helper/DataTest.php | 2 +- .../Magento/Framework/App/AreaTest.php | 2 +- .../App/Filesystem/CreatePdfFileTest.php | 2 +- .../App/Request/CsrfValidatorTest.php | 2 +- .../HeaderProvider/AbstractHeaderTestCase.php | 2 +- .../ParentClassWithNamespace.php | 8 +- .../SourceClassWithNamespace.php | 10 +- ...ceClassWithNamespaceInterceptor.php.sample | 6 +- .../SourceClassWithNamespaceProxy.php.sample | 6 +- .../_files/testFromClone/composer.json | 42 +- .../_files/testFromClone/composer.lock | 1040 ++-- .../testFromCreateProject/composer.lock | 1180 ++-- .../_files/testSkeleton/composer.lock | 1180 ++-- .../GraphQl/Config/GraphQlReaderTest.php | 2 +- .../Magento/Framework/HTTP/HeaderTest.php | 2 +- .../Stdlib/Cookie/CookieScopeTest.php | 2 +- .../testsuite/Magento/Framework/UrlTest.php | 2 +- .../Controller/GraphQlControllerTest.php | 4 +- .../Adminhtml/NewsletterTemplateTest.php | 4 +- .../Magento/Phpserver/PhpserverTest.php | 4 +- .../Adminhtml/Product/MassUpdateTest.php | 2 +- .../Magento/Setup/Controller/UrlCheckTest.php | 4 +- .../Model/ConfigOptionsListCollectorTest.php | 2 +- .../Setup/Model/ObjectManagerProviderTest.php | 2 +- .../Model/_files/testSkeleton/composer.lock | 680 +-- .../Magento/Sitemap/Model/SitemapTest.php | 2 +- .../Plugin/RequestPreprocessorTest.php | 2 +- .../Magento/Store/Model/StoreTest.php | 2 +- .../Ui/Controller/Index/RenderTest.php | 2 +- .../Integrity/Library/Injectable.php | 8 +- .../Test/Integrity/Library/InjectableTest.php | 14 +- .../Magento/Test/Integrity/ClassesTest.php | 2 +- .../Test/Integrity/Library/DependencyTest.php | 2 +- .../Api/ExtensibleInterfacesTest.php | 2 +- .../Magento/Test/Integrity/PublicCodeTest.php | 2 +- .../Test/Legacy/_files/obsolete_classes.php | 4 +- .../pre_composer_update_2.3.php | 4 +- lib/internal/Magento/Framework/App/Feed.php | 2 +- .../Magento/Framework/App/Request/Http.php | 2 +- .../Framework/App/Request/HttpMethodMap.php | 2 +- .../Framework/App/Response/HttpInterface.php | 2 +- .../App/Test/Unit/PageCache/KernelTest.php | 6 +- .../Code/Generator/ClassGenerator.php | 14 +- .../Code/Generator/CodeGeneratorInterface.php | 2 +- .../Code/Generator/EntityAbstract.php | 2 +- .../Generator/InterfaceMethodGenerator.php | 2 +- .../Framework/Code/Reader/ArgumentsReader.php | 6 +- .../Unit/Generator/ClassGeneratorTest.php | 24 +- .../Unit/Generator/TestAsset/ParentClass.php | 8 +- .../Unit/Generator/TestAsset/SourceClass.php | 12 +- .../Console/GenerationDirectoryAccess.php | 2 +- .../Framework/DB/Adapter/Pdo/Mysql.php | 2 +- .../Data/Test/Unit/Form/FormKeyTest.php | 2 +- .../Framework/Encryption/Helper/Security.php | 4 +- .../Test/Unit/Transfer/Adapter/HttpTest.php | 2 +- .../Framework/File/Transfer/Adapter/Http.php | 2 +- .../Magento/Framework/Filesystem/Glob.php | 10 +- .../Framework/HTTP/PhpEnvironment/Request.php | 12 +- .../HTTP/PhpEnvironment/Response.php | 4 +- .../Test/Unit/PhpEnvironment/RequestTest.php | 2 +- .../Test/Unit/PhpEnvironment/ResponseTest.php | 8 +- .../Magento/Framework/Mail/EmailMessage.php | 6 +- .../Magento/Framework/Mail/Message.php | 12 +- .../Magento/Framework/Mail/MimeInterface.php | 2 +- .../Magento/Framework/Mail/MimeMessage.php | 2 +- .../Magento/Framework/Mail/MimePart.php | 2 +- .../Magento/Framework/Mail/Transport.php | 4 +- .../Code/Generator/RemoteServiceGenerator.php | 4 +- .../Framework/Oauth/Helper/Request.php | 2 +- .../Code/Generator/Repository.php | 4 +- .../ExtensionAttributesProcessor.php | 2 +- .../Framework/Reflection/MethodsMap.php | 6 +- .../Framework/Reflection/NameFinder.php | 2 +- .../Reflection/Test/Unit/NameFinderTest.php | 4 +- .../Test/Unit/TypeProcessorTest.php | 2 +- .../Framework/Reflection/TypeProcessor.php | 14 +- .../Validator/CookieDomainValidator.php | 2 +- .../Magento/Framework/Stdlib/Parameters.php | 4 +- .../Framework/Url/Test/Unit/ValidatorTest.php | 4 +- .../Magento/Framework/Url/Validator.php | 4 +- .../Framework/Validator/AllowedProtocols.php | 2 +- .../Webapi/ServiceInputProcessor.php | 2 +- .../Webapi/ServiceOutputProcessor.php | 2 +- .../Magento/Framework/ZendEscaper.php | 2 +- lib/internal/Magento/Framework/composer.json | 18 +- setup/config/application.config.php | 4 +- setup/config/di.config.php | 4 +- setup/config/module.config.php | 2 +- setup/src/Magento/Setup/Application.php | 16 +- .../src/Magento/Setup/Console/CommandList.php | 2 +- .../Setup/Console/CompilerPreparation.php | 2 +- .../Magento/Setup/Controller/AddDatabase.php | 4 +- .../Setup/Controller/BackupActionItems.php | 16 +- .../Setup/Controller/CompleteBackup.php | 8 +- .../Setup/Controller/CreateAdminAccount.php | 4 +- .../Magento/Setup/Controller/CreateBackup.php | 4 +- .../Setup/Controller/CustomizeYourStore.php | 6 +- .../Magento/Setup/Controller/DataOption.php | 10 +- .../Setup/Controller/DatabaseCheck.php | 6 +- .../Setup/Controller/DependencyCheck.php | 6 +- .../Magento/Setup/Controller/Environment.php | 32 +- .../Setup/Controller/ExtensionGrid.php | 8 +- setup/src/Magento/Setup/Controller/Home.php | 6 +- setup/src/Magento/Setup/Controller/Index.php | 6 +- .../src/Magento/Setup/Controller/Install.php | 8 +- .../Setup/Controller/InstallExtensionGrid.php | 6 +- .../Setup/Controller/LandingInstaller.php | 4 +- .../Setup/Controller/LandingUpdater.php | 4 +- .../src/Magento/Setup/Controller/License.php | 4 +- .../Magento/Setup/Controller/Maintenance.php | 6 +- .../Magento/Setup/Controller/Marketplace.php | 10 +- .../Controller/MarketplaceCredentials.php | 4 +- .../Magento/Setup/Controller/ModuleGrid.php | 10 +- .../src/Magento/Setup/Controller/Modules.php | 6 +- .../Magento/Setup/Controller/Navigation.php | 6 +- .../Setup/Controller/OtherComponentsGrid.php | 10 +- .../Controller/ReadinessCheckInstaller.php | 4 +- .../Controller/ReadinessCheckUpdater.php | 4 +- .../Setup/Controller/SelectVersion.php | 8 +- .../src/Magento/Setup/Controller/Session.php | 24 +- .../Magento/Setup/Controller/StartUpdater.php | 8 +- .../src/Magento/Setup/Controller/Success.php | 4 +- .../Magento/Setup/Controller/SystemConfig.php | 4 +- .../Setup/Controller/UpdateExtensionGrid.php | 6 +- .../Setup/Controller/UpdaterSuccess.php | 4 +- .../src/Magento/Setup/Controller/UrlCheck.php | 6 +- .../Controller/ValidateAdminCredentials.php | 6 +- .../Setup/Controller/WebConfiguration.php | 4 +- .../Setup/Model/AdminAccountFactory.php | 2 +- .../Model/ConfigOptionsListCollector.php | 2 +- .../Magento/Setup/Model/Cron/JobFactory.php | 2 +- .../Magento/Setup/Model/InstallerFactory.php | 2 +- setup/src/Magento/Setup/Model/Navigation.php | 2 +- .../Setup/Model/ObjectManagerProvider.php | 2 +- .../src/Magento/Setup/Model/PackagesAuth.php | 10 +- .../Setup/Model/UpdaterTaskCreator.php | 2 +- setup/src/Magento/Setup/Module.php | 26 +- .../Setup/Module/ConnectionFactory.php | 2 +- .../Module/Di/Code/Reader/FileScanner.php | 4 +- .../Module/Di/Code/Scanner/PhpScanner.php | 2 +- .../Magento/Setup/Module/ResourceFactory.php | 2 +- .../Setup/Mvc/Bootstrap/InitParamListener.php | 28 +- .../Mvc/View/Http/InjectTemplateListener.php | 4 +- .../Test/Unit/Console/CommandListTest.php | 4 +- .../Unit/Console/CompilerPreparationTest.php | 2 +- .../Test/Unit/Controller/AddDatabaseTest.php | 2 +- .../Unit/Controller/BackupActionItemsTest.php | 16 +- .../Unit/Controller/CompleteBackupTest.php | 6 +- .../Controller/CreateAdminAccountTest.php | 2 +- .../Test/Unit/Controller/CreateBackupTest.php | 2 +- .../Controller/CustomizeYourStoreTest.php | 4 +- .../Test/Unit/Controller/DataOptionTest.php | 16 +- .../Test/Unit/Controller/EnvironmentTest.php | 48 +- .../Unit/Controller/ExtensionGridTest.php | 6 +- .../Setup/Test/Unit/Controller/IndexTest.php | 2 +- .../Controller/InstallExtensionGridTest.php | 4 +- .../Test/Unit/Controller/InstallTest.php | 24 +- .../Unit/Controller/LandingInstallerTest.php | 2 +- .../Unit/Controller/LandingUpdaterTest.php | 2 +- .../Test/Unit/Controller/LicenseTest.php | 4 +- .../Test/Unit/Controller/MaintenanceTest.php | 12 +- .../Test/Unit/Controller/MarketplaceTest.php | 16 +- .../Test/Unit/Controller/ModuleGridTest.php | 4 +- .../Test/Unit/Controller/ModulesTest.php | 4 +- .../Test/Unit/Controller/NavigationTest.php | 10 +- .../Controller/OtherComponentsGridTest.php | 6 +- .../ReadinessCheckInstallerTest.php | 4 +- .../Controller/ReadinessCheckUpdaterTest.php | 4 +- .../Unit/Controller/SelectVersionTest.php | 8 +- .../Test/Unit/Controller/SessionTest.php | 8 +- .../Test/Unit/Controller/StartUpdaterTest.php | 16 +- .../Test/Unit/Controller/SuccessTest.php | 2 +- .../Test/Unit/Controller/SystemConfigTest.php | 2 +- .../Controller/UpdateExtensionGridTest.php | 4 +- .../Unit/Controller/UpdaterSuccessTest.php | 2 +- .../Test/Unit/Controller/UrlCheckTest.php | 4 +- .../Unit/Controller/WebConfigurationTest.php | 2 +- .../Unit/Model/AdminAccountFactoryTest.php | 2 +- .../Test/Unit/Model/Cron/JobFactoryTest.php | 2 +- .../Test/Unit/Model/InstallerFactoryTest.php | 2 +- .../Setup/Test/Unit/Model/NavigationTest.php | 4 +- .../Unit/Model/ObjectManagerProviderTest.php | 2 +- .../Test/Unit/Model/PackagesAuthTest.php | 2 +- .../Unit/Module/ConnectionFactoryTest.php | 2 +- .../Test/Unit/Module/ResourceFactoryTest.php | 2 +- .../Mvc/Bootstrap/InitParamListenerTest.php | 52 +- .../LazyControllerAbstractFactory.php | 34 +- 246 files changed, 5263 insertions(+), 5012 deletions(-) diff --git a/app/code/Magento/Backend/App/Area/FrontNameResolver.php b/app/code/Magento/Backend/App/Area/FrontNameResolver.php index f03e97e32d2ab..6c586781f2d81 100644 --- a/app/code/Magento/Backend/App/Area/FrontNameResolver.php +++ b/app/code/Magento/Backend/App/Area/FrontNameResolver.php @@ -14,7 +14,7 @@ use Magento\Framework\App\RequestInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; -use Zend\Uri\Uri; +use Laminas\Uri\Uri; /** * Class to get area front name. diff --git a/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php b/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php index ba0b01d4055de..9483de741f169 100644 --- a/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php +++ b/app/code/Magento/Backend/Test/Unit/App/Area/FrontNameResolverTest.php @@ -29,7 +29,7 @@ class FrontNameResolverTest extends \PHPUnit\Framework\TestCase protected $scopeConfigMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Zend\Uri\Uri + * @var \PHPUnit_Framework_MockObject_MockObject|\Laminas\Uri\Uri */ protected $uri; @@ -51,7 +51,7 @@ protected function setUp() ->method('get') ->with(ConfigOptionsList::CONFIG_PATH_BACKEND_FRONTNAME) ->will($this->returnValue($this->_defaultFrontName)); - $this->uri = $this->createMock(\Zend\Uri\Uri::class); + $this->uri = $this->createMock(\Laminas\Uri\Uri::class); $this->request = $this->createMock(\Magento\Framework\App\Request\Http::class); diff --git a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php index b2aa0d000e9cf..a111414fd3074 100644 --- a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php +++ b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php @@ -110,8 +110,8 @@ private function splitTags($tagsPattern) /** * Send curl purge request to servers to invalidate cache by tags pattern * - * @param \Zend\Http\Client\Adapter\Socket $socketAdapter - * @param \Zend\Uri\Uri[] $servers + * @param \Laminas\Http\Client\Adapter\Socket $socketAdapter + * @param \Laminas\Uri\Uri[] $servers * @param string $formattedTagsChunk * @return bool Return true if successful; otherwise return false */ diff --git a/app/code/Magento/CacheInvalidate/Model/SocketFactory.php b/app/code/Magento/CacheInvalidate/Model/SocketFactory.php index b69788bf829de..25b4228d9de5e 100644 --- a/app/code/Magento/CacheInvalidate/Model/SocketFactory.php +++ b/app/code/Magento/CacheInvalidate/Model/SocketFactory.php @@ -8,10 +8,10 @@ class SocketFactory { /** - * @return \Zend\Http\Client\Adapter\Socket + * @return \Laminas\Http\Client\Adapter\Socket */ public function create() { - return new \Zend\Http\Client\Adapter\Socket(); + return new \Laminas\Http\Client\Adapter\Socket(); } } diff --git a/app/code/Magento/CacheInvalidate/Test/Unit/Model/PurgeCacheTest.php b/app/code/Magento/CacheInvalidate/Test/Unit/Model/PurgeCacheTest.php index c66e27ea41025..956c8802f597f 100644 --- a/app/code/Magento/CacheInvalidate/Test/Unit/Model/PurgeCacheTest.php +++ b/app/code/Magento/CacheInvalidate/Test/Unit/Model/PurgeCacheTest.php @@ -5,14 +5,14 @@ */ namespace Magento\CacheInvalidate\Test\Unit\Model; -use Zend\Uri\UriFactory; +use Laminas\Uri\UriFactory; class PurgeCacheTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\CacheInvalidate\Model\PurgeCache */ protected $model; - /** @var \PHPUnit_Framework_MockObject_MockObject | \Zend\Http\Client\Adapter\Socket */ + /** @var \PHPUnit_Framework_MockObject_MockObject | \Laminas\Http\Client\Adapter\Socket */ protected $socketAdapterMock; /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Cache\InvalidateLogger */ @@ -24,7 +24,7 @@ class PurgeCacheTest extends \PHPUnit\Framework\TestCase protected function setUp() { $socketFactoryMock = $this->createMock(\Magento\CacheInvalidate\Model\SocketFactory::class); - $this->socketAdapterMock = $this->createMock(\Zend\Http\Client\Adapter\Socket::class); + $this->socketAdapterMock = $this->createMock(\Laminas\Http\Client\Adapter\Socket::class); $this->socketAdapterMock->expects($this->once()) ->method('setOptions') ->with(['timeout' => 10]); @@ -113,7 +113,7 @@ public function testSendPurgeRequestWithException() ->method('getUris') ->willReturn($uris); $this->socketAdapterMock->method('connect') - ->willThrowException(new \Zend\Http\Client\Adapter\Exception\RuntimeException()); + ->willThrowException(new \Laminas\Http\Client\Adapter\Exception\RuntimeException()); $this->loggerMock->expects($this->never()) ->method('execute'); $this->loggerMock->expects($this->once()) diff --git a/app/code/Magento/CacheInvalidate/Test/Unit/Model/SocketFactoryTest.php b/app/code/Magento/CacheInvalidate/Test/Unit/Model/SocketFactoryTest.php index c69c7f4d8543e..f72abc2760794 100644 --- a/app/code/Magento/CacheInvalidate/Test/Unit/Model/SocketFactoryTest.php +++ b/app/code/Magento/CacheInvalidate/Test/Unit/Model/SocketFactoryTest.php @@ -10,6 +10,6 @@ class SocketFactoryTest extends \PHPUnit\Framework\TestCase public function testCreate() { $factory = new \Magento\CacheInvalidate\Model\SocketFactory(); - $this->assertInstanceOf(\Zend\Http\Client\Adapter\Socket::class, $factory->create()); + $this->assertInstanceOf(\Laminas\Http\Client\Adapter\Socket::class, $factory->create()); } } diff --git a/app/code/Magento/Captcha/Model/DefaultModel.php b/app/code/Magento/Captcha/Model/DefaultModel.php index bbbbfb0a36e08..32614be560893 100644 --- a/app/code/Magento/Captcha/Model/DefaultModel.php +++ b/app/code/Magento/Captcha/Model/DefaultModel.php @@ -11,14 +11,14 @@ use Magento\Framework\Math\Random; /** - * Implementation of \Zend\Captcha\Image + * Implementation of \Laminas\Captcha\Image * * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * * @api * @since 100.0.2 */ -class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model\CaptchaInterface +class DefaultModel extends \Laminas\Captcha\Image implements \Magento\Captcha\Model\CaptchaInterface { /** * Key in session for captcha code @@ -51,7 +51,7 @@ class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model /** * Override default value to prevent a captcha cut off * @var int - * @see \Zend\Captcha\Image::$fsize + * @see \Laminas\Captcha\Image::$fsize * @since 100.2.0 */ protected $fsize = 22; @@ -99,7 +99,7 @@ class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model * @param ResourceModel\LogFactory $resLogFactory * @param string $formId * @param Random $randomMath - * @throws \Zend\Captcha\Exception\ExtensionNotLoadedException + * @throws \Laminas\Captcha\Exception\ExtensionNotLoadedException */ public function __construct( \Magento\Framework\Session\SessionManagerInterface $session, @@ -537,7 +537,7 @@ private function clearWord() /** * Override function to generate less curly captcha that will not cut off * - * @see \Zend\Captcha\Image::_randomSize() + * @see \Laminas\Captcha\Image::_randomSize() * @return int * @throws \Magento\Framework\Exception\LocalizedException * @since 100.2.0 diff --git a/app/code/Magento/Captcha/Model/ResourceModel/Log.php b/app/code/Magento/Captcha/Model/ResourceModel/Log.php index 95b7d3a5a0a03..30c20fdeb3956 100644 --- a/app/code/Magento/Captcha/Model/ResourceModel/Log.php +++ b/app/code/Magento/Captcha/Model/ResourceModel/Log.php @@ -79,7 +79,7 @@ public function logAttempt($login) 'count' => 1, 'updated_at' => $this->_coreDate->gmtDate() ], - ['count' => new \Zend\Db\Sql\Expression('count+1'), 'updated_at'] + ['count' => new \Laminas\Db\Sql\Expression('count+1'), 'updated_at'] ); } $ip = $this->_remoteAddress->getRemoteAddress(); @@ -92,7 +92,7 @@ public function logAttempt($login) 'count' => 1, 'updated_at' => $this->_coreDate->gmtDate() ], - ['count' => new \Zend\Db\Sql\Expression('count+1'), 'updated_at'] + ['count' => new \Laminas\Db\Sql\Expression('count+1'), 'updated_at'] ); } return $this; diff --git a/app/code/Magento/Captcha/composer.json b/app/code/Magento/Captcha/composer.json index 294961118e93a..c6bbcc8e91c3e 100644 --- a/app/code/Magento/Captcha/composer.json +++ b/app/code/Magento/Captcha/composer.json @@ -11,9 +11,9 @@ "magento/module-checkout": "*", "magento/module-customer": "*", "magento/module-store": "*", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-session": "^2.7.3" + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-session": "^2.7.3" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Catalog/Controller/Product/Frontend/Action/Synchronize.php b/app/code/Magento/Catalog/Controller/Product/Frontend/Action/Synchronize.php index 58f4b9a4bb51e..f6896fe6a7a99 100644 --- a/app/code/Magento/Catalog/Controller/Product/Frontend/Action/Synchronize.php +++ b/app/code/Magento/Catalog/Controller/Product/Frontend/Action/Synchronize.php @@ -71,8 +71,8 @@ public function execute() $this->synchronizer->syncActions($productsData, $typeId); } catch (\Exception $e) { $resultJson->setStatusHeader( - \Zend\Http\Response::STATUS_CODE_400, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_400, + \Laminas\Http\AbstractMessage::VERSION_11, 'Bad Request' ); } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Product/Frontend/Action/SynchronizeTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Product/Frontend/Action/SynchronizeTest.php index bae370c7dd79f..4b7053765516d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Product/Frontend/Action/SynchronizeTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Product/Frontend/Action/SynchronizeTest.php @@ -133,8 +133,8 @@ public function testExecuteActionException() $jsonObject->expects($this->once()) ->method('setStatusHeader') ->with( - \Zend\Http\Response::STATUS_CODE_400, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_400, + \Laminas\Http\AbstractMessage::VERSION_11, 'Bad Request' ); $jsonObject->expects($this->once()) diff --git a/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php b/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php index 971b35c8f3e3d..d8597eb3640b3 100644 --- a/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php +++ b/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php @@ -34,7 +34,7 @@ public function __construct( */ public function process(array $cartData): array { - $filter = new \Zend\I18n\Filter\NumberParse($this->localeResolver->getLocale()); + $filter = new \Laminas\I18n\Filter\NumberParse($this->localeResolver->getLocale()); foreach ($cartData as $index => $data) { if (isset($data['qty'])) { diff --git a/app/code/Magento/Customer/Controller/Section/Load.php b/app/code/Magento/Customer/Controller/Section/Load.php index 37cd071b13623..6c3aa06b9f022 100644 --- a/app/code/Magento/Customer/Controller/Section/Load.php +++ b/app/code/Magento/Customer/Controller/Section/Load.php @@ -78,8 +78,8 @@ public function execute() $response = $this->sectionPool->getSectionsData($sectionNames, (bool)$forceNewSectionTimestamp); } catch (\Exception $e) { $resultJson->setStatusHeader( - \Zend\Http\Response::STATUS_CODE_400, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_400, + \Laminas\Http\AbstractMessage::VERSION_11, 'Bad Request' ); $response = ['message' => $this->escaper->escapeHtml($e->getMessage())]; diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php index 5a7cf42be2c7e..8eca499f849ea 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php @@ -166,8 +166,8 @@ public function testExecuteWithThrowException() $this->resultJsonMock->expects($this->once()) ->method('setStatusHeader') ->with( - \Zend\Http\Response::STATUS_CODE_400, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_400, + \Laminas\Http\AbstractMessage::VERSION_11, 'Bad Request' ); diff --git a/app/code/Magento/Downloadable/Model/Url/DomainValidator.php b/app/code/Magento/Downloadable/Model/Url/DomainValidator.php index cab7fb134ea33..68ab34c65a8d1 100644 --- a/app/code/Magento/Downloadable/Model/Url/DomainValidator.php +++ b/app/code/Magento/Downloadable/Model/Url/DomainValidator.php @@ -9,7 +9,7 @@ use Magento\Downloadable\Api\DomainManagerInterface as DomainManager; use Magento\Framework\Validator\Ip as IpValidator; -use Zend\Uri\Uri as UriHandler; +use Laminas\Uri\Uri as UriHandler; /** * Class is responsible for checking if downloadable product link domain is allowed. diff --git a/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php b/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php index 0e88bd166b604..642b1734310ea 100644 --- a/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php +++ b/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php @@ -14,7 +14,7 @@ use Magento\Framework\UrlInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; -use Zend\Uri\Uri as UriHandler; +use Laminas\Uri\Uri as UriHandler; use Magento\Framework\Url\ScopeResolverInterface; use Magento\Downloadable\Api\DomainManagerInterface as DomainManager; use Magento\Framework\Setup\ModuleDataSetupInterface; diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php index 79ceb56a8834d..a1e25c41f3bb5 100644 --- a/app/code/Magento/Email/Model/Transport.php +++ b/app/code/Magento/Email/Model/Transport.php @@ -13,12 +13,12 @@ use Magento\Framework\Mail\TransportInterface; use Magento\Framework\Phrase; use Magento\Store\Model\ScopeInterface; -use Zend\Mail\Message; -use Zend\Mail\Transport\Sendmail; +use Laminas\Mail\Message; +use Laminas\Mail\Transport\Sendmail; /** * Class that responsible for filling some message data before transporting it. - * @see \Zend\Mail\Transport\Sendmail is used for transport + * @see \Laminas\Mail\Transport\Sendmail is used for transport */ class Transport implements TransportInterface { diff --git a/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php b/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php index c6b7ce22fc39c..55c494e1ed2dd 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php @@ -6,7 +6,7 @@ namespace Magento\Integration\Test\Unit\Model\Oauth; use Magento\Framework\Url\Validator as UrlValidator; -use Zend\Validator\Uri as ZendUriValidator; +use Laminas\Validator\Uri as ZendUriValidator; use Magento\Integration\Model\Oauth\Consumer\Validator\KeyLength; /** diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php index 0d0bfc510b518..49a5421e623a5 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionDuringMediaAssetInitializationTest.php @@ -16,7 +16,7 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; -use Zend\Db\Adapter\Driver\Pdo\Statement; +use Laminas\Db\Adapter\Driver\Pdo\Statement; /** * Test the GetById command with exception during media asset initialization diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionNoSuchEntityTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionNoSuchEntityTest.php index 0ca9b3a3ffc8a..75b69c441c6d7 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionNoSuchEntityTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionNoSuchEntityTest.php @@ -16,7 +16,7 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; -use Zend\Db\Adapter\Driver\Pdo\Statement; +use Laminas\Db\Adapter\Driver\Pdo\Statement; /** * Test the GetById command with exception thrown in case when there is no such entity diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php index a709c2d214bda..f76552487e0f7 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdExceptionOnGetDataTest.php @@ -16,7 +16,7 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; -use Zend\Db\Adapter\Driver\Pdo\Statement; +use Laminas\Db\Adapter\Driver\Pdo\Statement; /** * Test the GetById command with exception during get media data diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php index c300d4f121bd2..c9e8416c53156 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/Asset/Command/GetByIdSuccessfulTest.php @@ -16,7 +16,7 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; -use Zend\Db\Adapter\Driver\Pdo\Statement; +use Laminas\Db\Adapter\Driver\Pdo\Statement; /** * Test the GetById command successful scenario diff --git a/app/code/Magento/MediaStorage/Test/Unit/Model/File/Storage/ResponseTest.php b/app/code/Magento/MediaStorage/Test/Unit/Model/File/Storage/ResponseTest.php index cdeb47d2b8490..7c0453fbf3da6 100644 --- a/app/code/Magento/MediaStorage/Test/Unit/Model/File/Storage/ResponseTest.php +++ b/app/code/Magento/MediaStorage/Test/Unit/Model/File/Storage/ResponseTest.php @@ -47,7 +47,7 @@ protected function setUp() public function testSendResponse(): void { $filePath = 'file_path'; - $headers = $this->getMockBuilder(\Zend\Http\Headers::class)->getMock(); + $headers = $this->getMockBuilder(\Laminas\Http\Headers::class)->getMock(); $this->response->setFilePath($filePath); $this->response->setHeaders($headers); $this->transferAdapter diff --git a/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php b/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php index 37dcd1302b3a3..8fcecea2e23d2 100644 --- a/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php +++ b/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php @@ -90,7 +90,7 @@ public function aroundDispatch( protected function addDebugHeaders(ResponseHttp $result) { $cacheControlHeader = $result->getHeader('Cache-Control'); - if ($cacheControlHeader instanceof \Zend\Http\Header\HeaderInterface) { + if ($cacheControlHeader instanceof \Laminas\Http\Header\HeaderInterface) { $this->addDebugHeader($result, 'X-Magento-Cache-Control', $cacheControlHeader->getFieldValue()); } $this->addDebugHeader($result, 'X-Magento-Cache-Debug', 'MISS', true); diff --git a/app/code/Magento/PageCache/Model/Cache/Server.php b/app/code/Magento/PageCache/Model/Cache/Server.php index 7f3a4af969d7e..3be54863f2dcd 100644 --- a/app/code/Magento/PageCache/Model/Cache/Server.php +++ b/app/code/Magento/PageCache/Model/Cache/Server.php @@ -9,8 +9,8 @@ use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Framework\App\RequestInterface; -use Zend\Uri\Uri; -use Zend\Uri\UriFactory; +use Laminas\Uri\Uri; +use Laminas\Uri\UriFactory; /** * Cache server model. diff --git a/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php b/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php index aadae97009cac..20e0155aaf113 100644 --- a/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php +++ b/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php @@ -11,7 +11,7 @@ use Magento\Framework\Registry; use Magento\Framework\Controller\ResultInterface; use Magento\Framework\App\Response\Http as ResponseHttp; -use Zend\Http\Header\HeaderInterface as HttpHeaderInterface; +use Laminas\Http\Header\HeaderInterface as HttpHeaderInterface; use Magento\PageCache\Model\Cache\Type as CacheType; /** diff --git a/app/code/Magento/PageCache/Test/Unit/Model/App/FrontController/BuiltinPluginTest.php b/app/code/Magento/PageCache/Test/Unit/Model/App/FrontController/BuiltinPluginTest.php index db0edfa6bd779..c9a887595b5a1 100644 --- a/app/code/Magento/PageCache/Test/Unit/Model/App/FrontController/BuiltinPluginTest.php +++ b/app/code/Magento/PageCache/Test/Unit/Model/App/FrontController/BuiltinPluginTest.php @@ -84,7 +84,7 @@ protected function setUp() */ public function testAroundDispatchProcessIfCacheMissed($state) { - $header = \Zend\Http\Header\GenericHeader::fromString('Cache-Control: no-cache'); + $header = \Laminas\Http\Header\GenericHeader::fromString('Cache-Control: no-cache'); $this->configMock ->expects($this->once()) ->method('getType') diff --git a/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php b/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php index a57effe1f31ad..57c4bb7107b13 100644 --- a/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php +++ b/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php @@ -7,7 +7,7 @@ use \Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use \Magento\PageCache\Model\Cache\Server; -use \Zend\Uri\UriFactory; +use \Laminas\Uri\UriFactory; class ServerTest extends \PHPUnit\Framework\TestCase { diff --git a/app/code/Magento/PageCache/Test/Unit/Model/Controller/Result/BuiltinPluginTest.php b/app/code/Magento/PageCache/Test/Unit/Model/Controller/Result/BuiltinPluginTest.php index fc4e056734939..cc431b94eb5bf 100644 --- a/app/code/Magento/PageCache/Test/Unit/Model/Controller/Result/BuiltinPluginTest.php +++ b/app/code/Magento/PageCache/Test/Unit/Model/Controller/Result/BuiltinPluginTest.php @@ -13,7 +13,7 @@ use Magento\Framework\Registry; use Magento\Framework\Controller\ResultInterface; use Magento\Framework\App\Response\Http as ResponseHttp; -use Zend\Http\Header\HeaderInterface as HttpHeaderInterface; +use Laminas\Http\Header\HeaderInterface as HttpHeaderInterface; use Magento\PageCache\Model\Cache\Type as CacheType; /** diff --git a/app/code/Magento/Quote/Model/Quote/Item/Updater.php b/app/code/Magento/Quote/Model/Quote/Item/Updater.php index 9865ae82ac3d6..410eb69e96ff5 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Updater.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Updater.php @@ -10,7 +10,7 @@ use Magento\Framework\DataObject\Factory as ObjectFactory; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Item; -use Zend\Code\Exception\InvalidArgumentException; +use Laminas\Code\Exception\InvalidArgumentException; /** * Class Updater diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Downloads/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Downloads/Collection.php index 2009cd3ff9d92..d194526858cde 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Product/Downloads/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Downloads/Collection.php @@ -104,7 +104,7 @@ public function addFieldToFilter($field, $condition = null) public function getSelectCountSql() { $countSelect = parent::getSelectCountSql(); - $countSelect->reset(\Zend\Db\Sql\Select::GROUP); + $countSelect->reset(\Laminas\Db\Sql\Select::GROUP); return $countSelect; } } diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php index a601f8fb2d1d7..3964cec18da8d 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php @@ -6,7 +6,7 @@ namespace Magento\Rss\Test\Unit\Controller\Adminhtml\Feed; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Zend\Feed\Writer\Exception\InvalidArgumentException; +use Laminas\Feed\Writer\Exception\InvalidArgumentException; /** * Class IndexTest diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php index 30415155d5f6e..3afd9f7833bba 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php @@ -6,7 +6,7 @@ namespace Magento\Rss\Test\Unit\Controller\Feed; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Zend\Feed\Writer\Exception\InvalidArgumentException; +use Laminas\Feed\Writer\Exception\InvalidArgumentException; /** * Class IndexTest diff --git a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php index f2888e4296b40..75652a1f75221 100644 --- a/app/code/Magento/Rss/Test/Unit/Model/RssTest.php +++ b/app/code/Magento/Rss/Test/Unit/Model/RssTest.php @@ -43,7 +43,7 @@ class RssTest extends \PHPUnit\Framework\TestCase http://magento.com/rss/link Sat, 22 Apr 2017 13:21:12 +0200 - Zend\Feed + Laminas\Feed http://blogs.law.harvard.edu/tech/rss <![CDATA[Feed 1 Title]]> diff --git a/app/code/Magento/Store/App/Response/Redirect.php b/app/code/Magento/Store/App/Response/Redirect.php index 178395ff6eb6a..da0c49aa1bc11 100644 --- a/app/code/Magento/Store/App/Response/Redirect.php +++ b/app/code/Magento/Store/App/Response/Redirect.php @@ -51,7 +51,7 @@ class Redirect implements \Magento\Framework\App\Response\RedirectInterface protected $_urlBuilder; /** - * @var \Zend\Uri\Uri|null + * @var \Laminas\Uri\Uri|null */ private $uri; @@ -64,7 +64,7 @@ class Redirect implements \Magento\Framework\App\Response\RedirectInterface * @param \Magento\Framework\Session\SessionManagerInterface $session * @param \Magento\Framework\Session\SidResolverInterface $sidResolver * @param \Magento\Framework\UrlInterface $urlBuilder - * @param \Zend\Uri\Uri|null $uri + * @param \Laminas\Uri\Uri|null $uri * @param bool $canUseSessionIdInParam */ public function __construct( @@ -74,7 +74,7 @@ public function __construct( \Magento\Framework\Session\SessionManagerInterface $session, \Magento\Framework\Session\SidResolverInterface $sidResolver, \Magento\Framework\UrlInterface $urlBuilder, - \Zend\Uri\Uri $uri = null, + \Laminas\Uri\Uri $uri = null, $canUseSessionIdInParam = true ) { $this->_canUseSessionIdInParam = $canUseSessionIdInParam; @@ -84,7 +84,7 @@ public function __construct( $this->_session = $session; $this->_sidResolver = $sidResolver; $this->_urlBuilder = $urlBuilder; - $this->uri = $uri ?: ObjectManager::getInstance()->get(\Zend\Uri\Uri::class); + $this->uri = $uri ?: ObjectManager::getInstance()->get(\Laminas\Uri\Uri::class); } /** diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index 5eda6f4a9b57d..9d7add39394e3 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -18,7 +18,7 @@ use Magento\Framework\Url\ScopeInterface as UrlScopeInterface; use Magento\Framework\UrlInterface; use Magento\Store\Api\Data\StoreInterface; -use Zend\Uri\UriFactory; +use Laminas\Uri\UriFactory; /** * Store model diff --git a/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/Design/Config/SaveTest.php b/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/Design/Config/SaveTest.php index a193604a0d6da..21989a0e6c58e 100644 --- a/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/Design/Config/SaveTest.php +++ b/app/code/Magento/Theme/Test/Unit/Controller/Adminhtml/Design/Config/SaveTest.php @@ -37,7 +37,7 @@ class SaveTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Backend\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject */ protected $context; - /** @var \Zend\Stdlib\Parameters|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Laminas\Stdlib\Parameters|\PHPUnit_Framework_MockObject_MockObject */ protected $fileParams; /** @var \Magento\Theme\Api\Data\DesignConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -83,7 +83,7 @@ public function setUp() '', false ); - $this->fileParams = $this->createMock(\Zend\Stdlib\Parameters::class); + $this->fileParams = $this->createMock(\Laminas\Stdlib\Parameters::class); $this->dataPersistor = $this->getMockBuilder(\Magento\Framework\App\Request\DataPersistorInterface::class) ->getMockForAbstractClass(); $this->controller = new Save( diff --git a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php index b06c655939b1c..bbdbcc637a5a0 100644 --- a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php +++ b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php @@ -90,8 +90,8 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); $resultJson->setStatusHeader( - \Zend\Http\Response::STATUS_CODE_403, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_403, + \Laminas\Http\AbstractMessage::VERSION_11, 'Forbidden' ); return $resultJson->setData([ @@ -108,8 +108,8 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); $resultJson->setStatusHeader( - \Zend\Http\Response::STATUS_CODE_400, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_400, + \Laminas\Http\AbstractMessage::VERSION_11, 'Bad Request' ); @@ -123,8 +123,8 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); $resultJson->setStatusHeader( - \Zend\Http\Response::STATUS_CODE_400, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_400, + \Laminas\Http\AbstractMessage::VERSION_11, 'Bad Request' ); diff --git a/app/code/Magento/Ui/Controller/Index/Render.php b/app/code/Magento/Ui/Controller/Index/Render.php index faab203547064..f74123955ce23 100644 --- a/app/code/Magento/Ui/Controller/Index/Render.php +++ b/app/code/Magento/Ui/Controller/Index/Render.php @@ -109,8 +109,8 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); $resultJson->setStatusHeader( - \Zend\Http\Response::STATUS_CODE_403, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_403, + \Laminas\Http\AbstractMessage::VERSION_11, 'Forbidden' ); return $resultJson->setData( @@ -129,8 +129,8 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); $resultJson->setStatusHeader( - \Zend\Http\Response::STATUS_CODE_400, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_400, + \Laminas\Http\AbstractMessage::VERSION_11, 'Bad Request' ); @@ -144,8 +144,8 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); $resultJson->setStatusHeader( - \Zend\Http\Response::STATUS_CODE_400, - \Zend\Http\AbstractMessage::VERSION_11, + \Laminas\Http\Response::STATUS_CODE_400, + \Laminas\Http\AbstractMessage::VERSION_11, 'Bad Request' ); diff --git a/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php b/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php index 2bba8686490b6..7c4c373abad3b 100644 --- a/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php +++ b/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php @@ -12,8 +12,8 @@ use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Ui\Controller\Adminhtml\Index\Render; use Magento\Ui\Model\UiComponentTypeResolver; -use Zend\Http\AbstractMessage; -use Zend\Http\Response; +use Laminas\Http\AbstractMessage; +use Laminas\Http\Response; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) diff --git a/app/code/Magento/Ui/Test/Unit/Controller/Index/RenderTest.php b/app/code/Magento/Ui/Test/Unit/Controller/Index/RenderTest.php index 646cea81212f9..894ff354a96fe 100644 --- a/app/code/Magento/Ui/Test/Unit/Controller/Index/RenderTest.php +++ b/app/code/Magento/Ui/Test/Unit/Controller/Index/RenderTest.php @@ -12,8 +12,8 @@ use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Ui\Controller\Index\Render; use Magento\Ui\Model\UiComponentTypeResolver; -use Zend\Http\AbstractMessage; -use Zend\Http\Response; +use Laminas\Http\AbstractMessage; +use Laminas\Http\Response; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php index c67f3f400b007..cd2725f218aae 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php @@ -11,7 +11,7 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\Store\Model\Store; use PHPUnit\Framework\MockObject\MockObject; -use Zend\Stdlib\ParametersInterface; +use Laminas\Stdlib\ParametersInterface; /** * Test class for UrlRewrite Controller Router diff --git a/app/code/Magento/Webapi/Model/Config/ClassReflector.php b/app/code/Magento/Webapi/Model/Config/ClassReflector.php index b73e4e0afb585..af34a3852caab 100644 --- a/app/code/Magento/Webapi/Model/Config/ClassReflector.php +++ b/app/code/Magento/Webapi/Model/Config/ClassReflector.php @@ -5,7 +5,7 @@ */ namespace Magento\Webapi\Model\Config; -use Zend\Code\Reflection\MethodReflection; +use Laminas\Code\Reflection\MethodReflection; /** * Class reflector. @@ -64,8 +64,8 @@ public function __construct(\Magento\Framework\Reflection\TypeProcessor $typePro public function reflectClassMethods($className, $methods) { $data = []; - $classReflection = new \Zend\Code\Reflection\ClassReflection($className); - /** @var \Zend\Code\Reflection\MethodReflection $methodReflection */ + $classReflection = new \Laminas\Code\Reflection\ClassReflection($className); + /** @var \Laminas\Code\Reflection\MethodReflection $methodReflection */ foreach ($classReflection->getMethods() as $methodReflection) { $methodName = $methodReflection->getName(); if (in_array($methodName, $methods) || array_key_exists($methodName, $methods)) { @@ -78,14 +78,14 @@ public function reflectClassMethods($className, $methods) /** * Retrieve method interface and documentation description. * - * @param \Zend\Code\Reflection\MethodReflection $method + * @param \Laminas\Code\Reflection\MethodReflection $method * @return array * @throws \InvalidArgumentException */ - public function extractMethodData(\Zend\Code\Reflection\MethodReflection $method) + public function extractMethodData(\Laminas\Code\Reflection\MethodReflection $method) { $methodData = ['documentation' => $this->extractMethodDescription($method), 'interface' => []]; - /** @var \Zend\Code\Reflection\ParameterReflection $parameter */ + /** @var \Laminas\Code\Reflection\ParameterReflection $parameter */ foreach ($method->getParameters() as $parameter) { $parameterData = [ 'type' => $this->_typeProcessor->register($this->_typeProcessor->getParamType($parameter)), @@ -116,10 +116,10 @@ public function extractMethodData(\Zend\Code\Reflection\MethodReflection $method /** * Retrieve method full documentation description. * - * @param \Zend\Code\Reflection\MethodReflection $method + * @param \Laminas\Code\Reflection\MethodReflection $method * @return string */ - protected function extractMethodDescription(\Zend\Code\Reflection\MethodReflection $method) + protected function extractMethodDescription(\Laminas\Code\Reflection\MethodReflection $method) { $methodReflection = new MethodReflection( $method->getDeclaringClass()->getName(), @@ -144,7 +144,7 @@ protected function extractMethodDescription(\Zend\Code\Reflection\MethodReflecti */ public function extractClassDescription($className) { - $classReflection = new \Zend\Code\Reflection\ClassReflection($className); + $classReflection = new \Laminas\Code\Reflection\ClassReflection($className); $docBlock = $classReflection->getDocBlock(); if (!$docBlock) { return ''; diff --git a/app/code/Magento/Webapi/Model/Soap/Wsdl.php b/app/code/Magento/Webapi/Model/Soap/Wsdl.php index 2d0b310995215..870dfec1fc072 100644 --- a/app/code/Magento/Webapi/Model/Soap/Wsdl.php +++ b/app/code/Magento/Webapi/Model/Soap/Wsdl.php @@ -11,14 +11,14 @@ /** * Magento-specific WSDL builder. */ -class Wsdl extends \Zend\Soap\Wsdl +class Wsdl extends \Laminas\Soap\Wsdl { /** * Constructor. * Save URI for targetNamespace generation. * * @param string $name - * @param string|\Zend\Uri\Uri $uri + * @param string|\Laminas\Uri\Uri $uri * @param ComplexTypeStrategy $strategy */ public function __construct($name, $uri, ComplexTypeStrategy $strategy) diff --git a/app/code/Magento/Webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php b/app/code/Magento/Webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php index 3884a0ef026e1..2087f9e5e3d6e 100644 --- a/app/code/Magento/Webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php +++ b/app/code/Magento/Webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php @@ -5,8 +5,8 @@ */ namespace Magento\Webapi\Model\Soap\Wsdl; -use Zend\Soap\Wsdl; -use Zend\Soap\Wsdl\ComplexTypeStrategy\AbstractComplexTypeStrategy; +use Laminas\Soap\Wsdl; +use Laminas\Soap\Wsdl\ComplexTypeStrategy\AbstractComplexTypeStrategy; /** * Magento-specific Complex type strategy for WSDL auto discovery. diff --git a/app/code/Magento/Webapi/Test/Unit/Controller/SoapTest.php b/app/code/Magento/Webapi/Test/Unit/Controller/SoapTest.php index 9b42d3c9c3e3a..576353f3636a4 100644 --- a/app/code/Magento/Webapi/Test/Unit/Controller/SoapTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Controller/SoapTest.php @@ -100,7 +100,7 @@ protected function setUp() $this->_responseMock ->expects($this->any()) ->method('getHeaders') - ->will($this->returnValue(new \Zend\Http\Headers())); + ->will($this->returnValue(new \Laminas\Http\Headers())); $appconfig = $this->createMock(\Magento\Framework\App\Config::class); $objectManagerHelper->setBackwardCompatibleProperty( diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Config/ClassReflectorTest.php b/app/code/Magento/Webapi/Test/Unit/Model/Config/ClassReflectorTest.php index b597b838a3512..8c77c65135a61 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/Config/ClassReflectorTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Model/Config/ClassReflectorTest.php @@ -46,10 +46,10 @@ public function testReflectClassMethods() public function testExtractMethodData() { - $classReflection = new \Zend\Code\Reflection\ClassReflection( + $classReflection = new \Laminas\Code\Reflection\ClassReflection( \Magento\Webapi\Test\Unit\Model\Config\TestServiceForClassReflector::class ); - /** @var $methodReflection \Zend\Code\Reflection\MethodReflection */ + /** @var $methodReflection \Laminas\Code\Reflection\MethodReflection */ $methodReflection = $classReflection->getMethods()[0]; $methodData = $this->_classReflector->extractMethodData($methodReflection); $expectedResponse = $this->_getSampleReflectionData(); diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Soap/Wsdl/ComplexTypeStrategyTest.php b/app/code/Magento/Webapi/Test/Unit/Model/Soap/Wsdl/ComplexTypeStrategyTest.php index 93d65d545408d..4ff37b3c15b0d 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/Soap/Wsdl/ComplexTypeStrategyTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Model/Soap/Wsdl/ComplexTypeStrategyTest.php @@ -7,7 +7,7 @@ use \Magento\Webapi\Model\Soap\Wsdl\ComplexTypeStrategy; -use Zend\Soap\Wsdl; +use Laminas\Soap\Wsdl; /** * Complex type strategy tests. diff --git a/composer.json b/composer.json index ab767fdac286d..0f70af3613dff 100644 --- a/composer.json +++ b/composer.json @@ -35,11 +35,41 @@ "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.6", "elasticsearch/elasticsearch": "~2.0||~5.1||~6.1", + "guzzlehttp/guzzle": "^6.3.3", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-config": "^2.6.0", + "laminas/laminas-console": "^2.6.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-dependency-plugin": "^1.0", + "laminas/laminas-di": "^2.6.1", + "laminas/laminas-eventmanager": "^3.0.0", + "laminas/laminas-feed": "^2.9.0", + "laminas/laminas-form": "^2.10.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-i18n": "^2.7.3", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.1", + "laminas/laminas-mail": "^2.9.0", + "laminas/laminas-mime": "^2.5.0", + "laminas/laminas-modulemanager": "^2.7", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-serializer": "^2.7.2", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.8", + "laminas/laminas-session": "^2.7.3", + "laminas/laminas-soap": "^2.7.0", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-text": "^2.6.0", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0", + "laminas/laminas-view": "~2.11.2", + "laminas/laminas-zendframework-bridge": "^1.0", "magento/composer": "~1.5.0", "magento/magento-composer-installer": ">=0.1.11", "magento/zendframework1": "~1.14.2", "monolog/monolog": "^1.17", - "wikimedia/less.php": "~1.8.0", "paragonie/sodium_compat": "^1.6", "pelago/emogrifier": "^2.0.0", "php-amqplib/php-amqplib": "~2.7.0||~2.10.0", @@ -52,35 +82,7 @@ "tedivm/jshrink": "~1.3.0", "tubalmartin/cssmin": "4.1.1", "webonyx/graphql-php": "^0.13.8", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-config": "^2.6.0", - "zendframework/zend-console": "^2.6.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-di": "^2.6.1", - "zendframework/zend-eventmanager": "^3.0.0", - "zendframework/zend-feed": "^2.9.0", - "zendframework/zend-form": "^2.10.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-i18n": "^2.7.3", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.1", - "zendframework/zend-mail": "^2.9.0", - "zendframework/zend-mime": "^2.5.0", - "zendframework/zend-modulemanager": "^2.7", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-serializer": "^2.7.2", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.8", - "zendframework/zend-session": "^2.7.3", - "zendframework/zend-soap": "^2.7.0", - "zendframework/zend-stdlib": "^3.2.1", - "zendframework/zend-text": "^2.6.0", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-view": "~2.11.2", - "guzzlehttp/guzzle": "^6.3.3" + "wikimedia/less.php": "~1.8.0" }, "require-dev": { "allure-framework/allure-phpunit": "~1.2.0", @@ -319,7 +321,7 @@ "Magento\\Framework\\": "lib/internal/Magento/Framework/", "Magento\\Setup\\": "setup/src/Magento/Setup/", "Magento\\": "app/code/Magento/", - "Zend\\Mvc\\Controller\\": "setup/src/Zend/Mvc/Controller/" + "Laminas\\Mvc\\Controller\\": "setup/src/Zend/Mvc/Controller/" }, "psr-0": { "": [ diff --git a/composer.lock b/composer.lock index b6d834610059a..5202b8e6c43d8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8d8e6b87c1f6ac98b3b7331eba9473f3", + "content-hash": "a418300ac80a4c8ddea59451a7e563c1", "packages": [ { "name": "braintree/braintree_php", @@ -949,3935 +949,4183 @@ "time": "2019-09-25T14:49:45+00:00" }, { - "name": "magento/composer", - "version": "1.5.0", + "name": "laminas/laminas-captcha", + "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/magento/composer.git", - "reference": "ea12b95be5c0833b3d9497aaefa08816c19e5dcd" + "url": "https://github.com/laminas/laminas-captcha.git", + "reference": "b88f650f3adf2d902ef56f6377cceb5cd87b9876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/composer/zipball/ea12b95be5c0833b3d9497aaefa08816c19e5dcd", - "reference": "ea12b95be5c0833b3d9497aaefa08816c19e5dcd", + "url": "https://api.github.com/repos/laminas/laminas-captcha/zipball/b88f650f3adf2d902ef56f6377cceb5cd87b9876", + "reference": "b88f650f3adf2d902ef56f6377cceb5cd87b9876", "shasum": "" }, "require": { - "composer/composer": "^1.6", - "php": "~7.1.3||~7.2.0||~7.3.0", - "symfony/console": "~4.0.0 || ~4.1.0" + "laminas/laminas-math": "^2.7 || ^3.0", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-captcha": "self.version" }, "require-dev": { - "phpunit/phpunit": "~7.0.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-recaptcha": "^3.0", + "laminas/laminas-session": "^2.8", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.10.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" + }, + "suggest": { + "laminas/laminas-i18n-resources": "Translations of captcha messages", + "laminas/laminas-recaptcha": "Laminas\\ReCaptcha component", + "laminas/laminas-session": "Laminas\\Session component", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-validator": "Laminas\\Validator component" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" + } + }, "autoload": { "psr-4": { - "Magento\\Composer\\": "src" + "Laminas\\Captcha\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "OSL-3.0", - "AFL-3.0" + "BSD-3-Clause" ], - "description": "Magento composer library helps to instantiate Composer application and run composer commands.", - "time": "2019-07-29T19:52:05+00:00" + "description": "Generate and validate CAPTCHAs using Figlets, images, ReCaptcha, and more", + "homepage": "https://laminas.dev", + "keywords": [ + "captcha", + "laminas" + ], + "time": "2019-12-31T16:24:14+00:00" }, { - "name": "magento/magento-composer-installer", - "version": "0.1.13", + "name": "laminas/laminas-code", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/magento/magento-composer-installer.git", - "reference": "8b6c32f53b4944a5d6656e86344cd0f9784709a1" + "url": "https://github.com/laminas/laminas-code.git", + "reference": "128784abc7a0d9e1fcc30c446533aa6f1db1f999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/8b6c32f53b4944a5d6656e86344cd0f9784709a1", - "reference": "8b6c32f53b4944a5d6656e86344cd0f9784709a1", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/128784abc7a0d9e1fcc30c446533aa6f1db1f999", + "reference": "128784abc7a0d9e1fcc30c446533aa6f1db1f999", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0" + "laminas/laminas-eventmanager": "^2.6 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.1" }, "replace": { - "magento-hackathon/magento-composer-installer": "*" + "zendframework/zend-code": "self.version" }, "require-dev": { - "composer/composer": "*@dev", - "firegento/phpcs": "dev-patch-1", - "mikey179/vfsstream": "*", - "phpunit/phpunit": "*", - "phpunit/phpunit-mock-objects": "dev-master", - "squizlabs/php_codesniffer": "1.4.7", - "symfony/process": "*" + "doctrine/annotations": "^1.0", + "ext-phar": "*", + "laminas/laminas-coding-standard": "^1.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "phpunit/phpunit": "^7.5.15" }, - "type": "composer-plugin", + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "laminas/laminas-stdlib": "Laminas\\Stdlib component" + }, + "type": "library", "extra": { - "composer-command-registry": [ - "MagentoHackathon\\Composer\\Magento\\Command\\DeployCommand" - ], - "class": "MagentoHackathon\\Composer\\Magento\\Plugin" + "branch-alias": { + "dev-master": "3.3.x-dev", + "dev-develop": "3.4.x-dev" + } }, "autoload": { - "psr-0": { - "MagentoHackathon\\Composer\\Magento": "src/" + "psr-4": { + "Laminas\\Code\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Vinai Kopp", - "email": "vinai@netzarbeiter.com" - }, - { - "name": "Daniel Fahlke aka Flyingmana", - "email": "flyingmana@googlemail.com" - }, - { - "name": "Jörg Weller", - "email": "weller@flagbit.de" - }, - { - "name": "Karl Spies", - "email": "karl.spies@gmx.net" - }, - { - "name": "Tobias Vogt", - "email": "tobi@webguys.de" - }, - { - "name": "David Fuhr", - "email": "fuhr@flagbit.de" - } + "BSD-3-Clause" ], - "description": "Composer installer for Magento modules", - "homepage": "https://github.com/magento/magento-composer-installer", + "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", + "homepage": "https://laminas.dev", "keywords": [ - "composer-installer", - "magento" + "code", + "laminas" ], - "time": "2017-12-29T16:45:24+00:00" + "time": "2019-12-31T16:28:14+00:00" }, { - "name": "magento/zendframework1", - "version": "1.14.2", + "name": "laminas/laminas-config", + "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/magento/zf1.git", - "reference": "8221062d42a198e431d183bbe672e5e1a2f98c5f" + "url": "https://github.com/laminas/laminas-config.git", + "reference": "71ba6d5dd703196ce66b25abc4d772edb094dae1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/zf1/zipball/8221062d42a198e431d183bbe672e5e1a2f98c5f", - "reference": "8221062d42a198e431d183bbe672e5e1a2f98c5f", + "url": "https://api.github.com/repos/laminas/laminas-config/zipball/71ba6d5dd703196ce66b25abc4d772edb094dae1", + "reference": "71ba6d5dd703196ce66b25abc4d772edb094dae1", "shasum": "" }, "require": { - "php": ">=5.2.11" + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-config": "self.version" }, "require-dev": { - "phpunit/dbunit": "1.3.*", - "phpunit/phpunit": "3.7.*" + "fabpot/php-cs-fixer": "1.7.*", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.5", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-json": "Laminas\\Json to use the Json reader or writer classes", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12.x-dev" + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" } }, "autoload": { - "psr-0": { - "Zend_": "library/" + "psr-4": { + "Laminas\\Config\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "library/" - ], "license": [ "BSD-3-Clause" ], - "description": "Magento Zend Framework 1", - "homepage": "http://framework.zend.com/", + "description": "provides a nested object property based user interface for accessing this configuration data within application code", + "homepage": "https://laminas.dev", "keywords": [ - "ZF1", - "framework" + "config", + "laminas" ], - "time": "2019-07-26T16:43:11+00:00" + "time": "2019-12-31T16:30:04+00:00" }, { - "name": "monolog/monolog", - "version": "1.25.2", + "name": "laminas/laminas-console", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287" + "url": "https://github.com/laminas/laminas-console.git", + "reference": "478a6ceac3e31fb38d6314088abda8b239ee23a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d5e2fb341cb44f7e2ab639d12a1e5901091ec287", - "reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287", + "url": "https://api.github.com/repos/laminas/laminas-console/zipball/478a6ceac3e31fb38d6314088abda8b239ee23a5", + "reference": "478a6ceac3e31fb38d6314088abda8b239ee23a5", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "provide": { - "psr/log-implementation": "1.0.0" + "replace": { + "zendframework/zend-console": "self.version" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-filter": "^2.7.2", + "laminas/laminas-json": "^2.6 || ^3.0", + "laminas/laminas-validator": "^2.10.1", + "phpunit/phpunit": "^5.7.23 || ^6.4.3" }, "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "laminas/laminas-filter": "To support DefaultRouteMatcher usage", + "laminas/laminas-validator": "To support DefaultRouteMatcher usage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-master": "2.8.x-dev", + "dev-develop": "2.9.x-dev" } }, "autoload": { "psr-4": { - "Monolog\\": "src/Monolog" + "Laminas\\Console\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } + "BSD-3-Clause" ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "description": "Build console applications using getopt syntax or routing, complete with prompts", + "homepage": "https://laminas.dev", "keywords": [ - "log", - "logging", - "psr-3" + "console", + "laminas" ], - "time": "2019-11-13T10:00:05+00:00" + "time": "2019-12-31T16:31:45+00:00" }, { - "name": "paragonie/random_compat", - "version": "v9.99.99", + "name": "laminas/laminas-crypt", + "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "url": "https://github.com/laminas/laminas-crypt.git", + "reference": "6f291fe90c84c74d737c9dc9b8f0ad2b55dc0567" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/laminas/laminas-crypt/zipball/6f291fe90c84c74d737c9dc9b8f0ad2b55dc0567", + "reference": "6f291fe90c84c74d737c9dc9b8f0ad2b55dc0567", "shasum": "" }, "require": { - "php": "^7" + "container-interop/container-interop": "~1.0", + "laminas/laminas-math": "^2.6", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-crypt": "self.version" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" }, "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "ext-mcrypt": "Required for most features of Laminas\\Crypt" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Crypt\\": "src/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } + "BSD-3-Clause" ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "homepage": "https://laminas.dev", "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" + "crypt", + "laminas" ], - "time": "2018-07-02T15:55:56+00:00" + "time": "2019-12-31T16:33:11+00:00" }, { - "name": "paragonie/sodium_compat", - "version": "v1.12.1", + "name": "laminas/laminas-db", + "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "063cae9b3a7323579063e7037720f5b52b56c178" + "url": "https://github.com/laminas/laminas-db.git", + "reference": "dae817b9e0c724ef10cb7906ef8ef3fe68debeb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/063cae9b3a7323579063e7037720f5b52b56c178", - "reference": "063cae9b3a7323579063e7037720f5b52b56c178", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/dae817b9e0c724ef10cb7906ef8ef3fe68debeb7", + "reference": "dae817b9e0c724ef10cb7906ef8ef3fe68debeb7", "shasum": "" }, "require": { - "paragonie/random_compat": ">=1", - "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-db": "self.version" }, "require-dev": { - "phpunit/phpunit": "^3|^4|^5|^6|^7" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "^5.7.25 || ^6.4.4" }, "suggest": { - "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", - "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-hydrator": "Laminas\\Hydrator component for using HydratingResultSets", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.9-dev", + "dev-develop": "2.10-dev" + }, + "laminas": { + "component": "Laminas\\Db", + "config-provider": "Laminas\\Db\\ConfigProvider" + } + }, "autoload": { - "files": [ - "autoload.php" - ] + "psr-4": { + "Laminas\\Db\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "ISC" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com" + "description": "Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "db", + "laminas" + ], + "time": "2019-12-31T19:55:57+00:00" + }, + { + "name": "laminas/laminas-dependency-plugin", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-dependency-plugin.git", + "reference": "f269716dc584cd7b69e7f6e8ac1092d645ab56d5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-dependency-plugin/zipball/f269716dc584cd7b69e7f6e8ac1092d645ab56d5", + "reference": "f269716dc584cd7b69e7f6e8ac1092d645ab56d5", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.1", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "composer/composer": "^1.9", + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^8.4", + "roave/security-advisories": "dev-master", + "webimpress/coding-standard": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev", + "dev-develop": "1.1.x-dev" }, - { - "name": "Frank Denis", - "email": "jedisct1@pureftpd.org" + "class": "Laminas\\DependencyPlugin\\DependencyRewriterPlugin" + }, + "autoload": { + "psr-4": { + "Laminas\\DependencyPlugin\\": "src/" } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" ], - "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", - "keywords": [ - "Authentication", - "BLAKE2b", - "ChaCha20", - "ChaCha20-Poly1305", - "Chapoly", - "Curve25519", - "Ed25519", - "EdDSA", - "Edwards-curve Digital Signature Algorithm", - "Elliptic Curve Diffie-Hellman", - "Poly1305", - "Pure-PHP cryptography", - "RFC 7748", - "RFC 8032", - "Salpoly", - "Salsa20", - "X25519", - "XChaCha20-Poly1305", - "XSalsa20-Poly1305", - "Xchacha20", - "Xsalsa20", - "aead", - "cryptography", - "ecdh", - "elliptic curve", - "elliptic curve cryptography", - "encryption", - "libsodium", - "php", - "public-key cryptography", - "secret-key cryptography", - "side-channel resistant" - ], - "time": "2019-11-07T17:07:24+00:00" + "description": "Replace zendframework and zfcampus packages with their Laminas Project equivalents.", + "time": "2020-01-14T19:36:52+00:00" }, { - "name": "pelago/emogrifier", - "version": "v2.2.0", + "name": "laminas/laminas-di", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/MyIntervals/emogrifier.git", - "reference": "2472bc1c3a2dee8915ecc2256139c6100024332f" + "url": "https://github.com/laminas/laminas-di.git", + "reference": "239b22408a1f8eacda6fc2b838b5065c4cf1d88e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/2472bc1c3a2dee8915ecc2256139c6100024332f", - "reference": "2472bc1c3a2dee8915ecc2256139c6100024332f", + "url": "https://api.github.com/repos/laminas/laminas-di/zipball/239b22408a1f8eacda6fc2b838b5065c4cf1d88e", + "reference": "239b22408a1f8eacda6fc2b838b5065c4cf1d88e", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0", - "symfony/css-selector": "^3.4.0 || ^4.0.0" + "container-interop/container-interop": "^1.1", + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^0.4.5 || ^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-di": "self.version" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.2.0", - "phpmd/phpmd": "^2.6.0", - "phpunit/phpunit": "^4.8.0", - "squizlabs/php_codesniffer": "^3.3.2" + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" } }, "autoload": { "psr-4": { - "Pelago\\": "src/" + "Laminas\\Di\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oliver Klee", - "email": "github@oliverklee.de" - }, - { - "name": "Zoli Szabó", - "email": "zoli.szabo+github@gmail.com" - }, - { - "name": "John Reeve", - "email": "jreeve@pelagodesign.com" - }, - { - "name": "Jake Hotson", - "email": "jake@qzdesign.co.uk" - }, - { - "name": "Cameron Brooks" - }, - { - "name": "Jaime Prado" - } + "BSD-3-Clause" ], - "description": "Converts CSS styles into inline style attributes in your HTML code", - "homepage": "https://www.myintervals.com/emogrifier.php", + "homepage": "https://laminas.dev", "keywords": [ - "css", - "email", - "pre-processing" + "di", + "laminas" ], - "time": "2019-09-04T16:07:59+00:00" + "time": "2019-12-31T15:17:33+00:00" }, { - "name": "php-amqplib/php-amqplib", - "version": "v2.10.1", + "name": "laminas/laminas-diactoros", + "version": "1.8.7", "source": { "type": "git", - "url": "https://github.com/php-amqplib/php-amqplib.git", - "reference": "6e2b2501e021e994fb64429e5a78118f83b5c200" + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "1c2dce9d2822030d5dcfd50b709708830429c89a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/6e2b2501e021e994fb64429e5a78118f83b5c200", - "reference": "6e2b2501e021e994fb64429e5a78118f83b5c200", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/1c2dce9d2822030d5dcfd50b709708830429c89a", + "reference": "1c2dce9d2822030d5dcfd50b709708830429c89a", "shasum": "" }, "require": { - "ext-bcmath": "*", - "ext-sockets": "*", - "php": ">=5.6" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" }, "replace": { - "videlalvaro/php-amqplib": "self.version" + "zendframework/zend-diactoros": "self.version" }, "require-dev": { - "ext-curl": "*", - "nategood/httpful": "^0.2.20", - "phpunit/phpunit": "^5.7|^6.5|^7.0", - "squizlabs/php_codesniffer": "^2.5" + "ext-dom": "*", + "ext-libxml": "*", + "laminas/laminas-coding-standard": "~1.0", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10-dev" + "dev-release-1.8": "1.8.x-dev" } }, "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], "psr-4": { - "PhpAmqpLib\\": "PhpAmqpLib/" + "Laminas\\Diactoros\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-2.1-or-later" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Alvaro Videla", - "role": "Original Maintainer" - }, - { - "name": "John Kelly", - "email": "johnmkelly86@gmail.com", - "role": "Maintainer" - }, - { - "name": "Raúl Araya", - "email": "nubeiro@gmail.com", - "role": "Maintainer" - }, - { - "name": "Luke Bakken", - "email": "luke@bakken.io", - "role": "Maintainer" + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-7" + ], + "time": "2019-12-31T16:41:30+00:00" + }, + { + "name": "laminas/laminas-escaper", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-escaper.git", + "reference": "25f2a053eadfa92ddacb609dcbbc39362610da70" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/25f2a053eadfa92ddacb609dcbbc39362610da70", + "reference": "25f2a053eadfa92ddacb609dcbbc39362610da70", + "shasum": "" + }, + "require": { + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-escaper": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Escaper\\": "src/" } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" ], - "description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.", - "homepage": "https://github.com/php-amqplib/php-amqplib/", + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "homepage": "https://laminas.dev", "keywords": [ - "message", - "queue", - "rabbitmq" + "escaper", + "laminas" ], - "time": "2019-10-10T13:23:40+00:00" + "time": "2019-12-31T16:43:30+00:00" }, { - "name": "phpseclib/mcrypt_compat", - "version": "1.0.8", + "name": "laminas/laminas-eventmanager", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/phpseclib/mcrypt_compat.git", - "reference": "f74c7b1897b62f08f268184b8bb98d9d9ab723b0" + "url": "https://github.com/laminas/laminas-eventmanager.git", + "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/mcrypt_compat/zipball/f74c7b1897b62f08f268184b8bb98d9d9ab723b0", - "reference": "f74c7b1897b62f08f268184b8bb98d9d9ab723b0", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", + "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", "shasum": "" }, "require": { - "php": ">=5.3.3", - "phpseclib/phpseclib": ">=2.0.11 <3.0.0" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-eventmanager": "self.version" }, "require-dev": { - "phpunit/phpunit": "^4.8.35|^5.7|^6.0" + "athletic/athletic": "^0.1", + "container-interop/container-interop": "^1.1.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-stdlib": "^2.7.3 || ^3.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" }, "suggest": { - "ext-openssl": "Will enable faster cryptographic operations" + "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", + "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev", + "dev-develop": "3.3-dev" + } + }, "autoload": { - "files": [ - "lib/mcrypt.php" - ] + "psr-4": { + "Laminas\\EventManager\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "homepage": "http://phpseclib.sourceforge.net" - } + "BSD-3-Clause" ], - "description": "PHP 7.1 polyfill for the mcrypt extension from PHP <= 7.0", + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://laminas.dev", "keywords": [ - "cryptograpy", - "encryption", - "mcrypt" + "event", + "eventmanager", + "events", + "laminas" ], - "time": "2018-08-22T03:11:43+00:00" + "time": "2019-12-31T16:44:52+00:00" }, { - "name": "phpseclib/phpseclib", - "version": "2.0.23", + "name": "laminas/laminas-feed", + "version": "2.12.0", "source": { "type": "git", - "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099" + "url": "https://github.com/laminas/laminas-feed.git", + "reference": "64d25e18a6ea3db90c27fe2d6b95630daa1bf602" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c78eb5058d5bb1a183133c36d4ba5b6675dfa099", - "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/64d25e18a6ea3db90c27fe2d6b95630daa1bf602", + "reference": "64d25e18a6ea3db90c27fe2d6b95630daa1bf602", "shasum": "" }, "require": { - "php": ">=5.3.3" + "ext-dom": "*", + "ext-libxml": "*", + "laminas/laminas-escaper": "^2.5.2", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-feed": "self.version" }, "require-dev": { - "phing/phing": "~2.7", - "phpunit/phpunit": "^4.8.35|^5.7|^6.0", - "sami/sami": "~2.0", - "squizlabs/php_codesniffer": "~2.0" + "laminas/laminas-cache": "^2.7.2", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-http": "^2.7", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-validator": "^2.10.1", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-message": "^1.0.1" }, "suggest": { - "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", - "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", - "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", - "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + "laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests", + "laminas/laminas-db": "Laminas\\Db component, for use with PubSubHubbub", + "laminas/laminas-http": "Laminas\\Http for PubSubHubbub, and optionally for use with Laminas\\Feed\\Reader", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for easily extending ExtensionManager implementations", + "laminas/laminas-validator": "Laminas\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent", + "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Laminas\\Feed\\Reader\\Http\\Psr7ResponseDecorator" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" + } + }, "autoload": { - "files": [ - "phpseclib/bootstrap.php" - ], "psr-4": { - "phpseclib\\": "phpseclib/" + "Laminas\\Feed\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" - }, - { - "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" - }, - { - "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" - }, - { - "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" - }, - { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" - } + "BSD-3-Clause" ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", + "description": "provides functionality for consuming RSS and Atom feeds", + "homepage": "https://laminas.dev", "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" + "feed", + "laminas" ], - "time": "2019-09-17T03:41:22+00:00" + "time": "2019-12-31T16:46:54+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "laminas/laminas-filter", + "version": "2.9.2", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/laminas/laminas-filter.git", + "reference": "4d8c0c25e40836bd617335e744009c2c950c4ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/4d8c0c25e40836bd617335e744009c2c950c4ad5", + "reference": "4d8c0c25e40836bd617335e744009c2c950c4ad5", "shasum": "" }, "require": { - "php": ">=5.3.0" + "laminas/laminas-stdlib": "^2.7.7 || ^3.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "conflict": { + "laminas/laminas-validator": "<2.10.1" + }, + "replace": { + "zendframework/zend-filter": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^3.2.1", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-uri": "^2.6", + "pear/archive_tar": "^1.4.3", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-factory": "^1.0" + }, + "suggest": { + "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", + "laminas/laminas-i18n": "Laminas\\I18n component for filters depending on i18n functionality", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for using the filter chain functionality", + "laminas/laminas-uri": "Laminas\\Uri component, for the UriNormalize filter", + "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" + }, + "laminas": { + "component": "Laminas\\Filter", + "config-provider": "Laminas\\Filter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Laminas\\Filter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } + "BSD-3-Clause" ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Programmatically filter and normalize data and files", + "homepage": "https://laminas.dev", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "filter", + "laminas" ], - "time": "2017-02-14T16:28:37+00:00" + "time": "2019-12-31T16:54:29+00:00" }, { - "name": "psr/http-message", - "version": "1.0.1", + "name": "laminas/laminas-form", + "version": "2.14.3", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "url": "https://github.com/laminas/laminas-form.git", + "reference": "012aae01366cb8c8fb64e39a887363ef82f388dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/012aae01366cb8c8fb64e39a887363ef82f388dd", + "reference": "012aae01366cb8c8fb64e39a887363ef82f388dd", "shasum": "" }, "require": { - "php": ">=5.3.0" + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-form": "self.version" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-recaptcha": "^3.0.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.2", + "phpunit/phpunit": "^5.7.23 || ^6.5.3" + }, + "suggest": { + "laminas/laminas-captcha": "^2.7.1, required for using CAPTCHA form elements", + "laminas/laminas-code": "^2.6 || ^3.0, required to use laminas-form annotations support", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, reuired for laminas-form annotations support", + "laminas/laminas-i18n": "^2.6, required when using laminas-form view helpers", + "laminas/laminas-recaptcha": "in order to use the ReCaptcha form element", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", + "laminas/laminas-view": "^2.6.2, required for using the laminas-form view helpers" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.14.x-dev", + "dev-develop": "2.15.x-dev" + }, + "laminas": { + "component": "Laminas\\Form", + "config-provider": "Laminas\\Form\\ConfigProvider" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" - } + "Laminas\\Form\\": "src/" + }, + "files": [ + "autoload/formElementManagerPolyfill.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } + "BSD-3-Clause" ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", + "description": "Validate and display simple and complex forms, casting forms to business objects and vice versa", + "homepage": "https://laminas.dev", "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" + "form", + "laminas" ], - "time": "2016-08-06T14:39:51+00:00" + "time": "2019-12-31T16:56:34+00:00" }, { - "name": "psr/log", - "version": "1.1.2", + "name": "laminas/laminas-http", + "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + "url": "https://github.com/laminas/laminas-http.git", + "reference": "d5b71b5b84329a13825e268e8bda1a225de0404b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/d5b71b5b84329a13825e268e8bda1a225de0404b", + "reference": "d5b71b5b84329a13825e268e8bda1a225de0404b", "shasum": "" }, "require": { - "php": ">=5.3.0" + "laminas/laminas-loader": "^2.5.1", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-uri": "^2.5.2", + "laminas/laminas-validator": "^2.10.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-http": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^3.1 || ^2.6", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3" + }, + "suggest": { + "paragonie/certainty": "For automated management of cacert.pem" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Laminas\\Http\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } + "BSD-3-Clause" ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "homepage": "https://laminas.dev", "keywords": [ - "log", - "psr", - "psr-3" + "http", + "http client", + "laminas" ], - "time": "2019-11-01T11:05:21+00:00" + "time": "2019-12-31T17:02:20+00:00" }, { - "name": "ralouphie/getallheaders", - "version": "3.0.3", + "name": "laminas/laminas-hydrator", + "version": "2.4.2", "source": { "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" + "url": "https://github.com/laminas/laminas-hydrator.git", + "reference": "4a0e81cf05f32edcace817f1f48cb4055f689d85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", + "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/4a0e81cf05f32edcace817f1f48cb4055f689d85", + "reference": "4a0e81cf05f32edcace817f1f48cb4055f689d85", "shasum": "" }, "require": { - "php": ">=5.6" + "laminas/laminas-stdlib": "^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-hydrator": "self.version" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-inputfilter": "^2.6", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" + }, + "suggest": { + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", + "laminas/laminas-filter": "^2.6, to support naming strategy hydrator usage", + "laminas/laminas-serializer": "^2.6.1, to use the SerializableStrategy", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" }, "type": "library", + "extra": { + "branch-alias": { + "dev-release-2.4": "2.4.x-dev" + }, + "laminas": { + "component": "Laminas\\Hydrator", + "config-provider": "Laminas\\Hydrator\\ConfigProvider" + } + }, "autoload": { - "files": [ - "src/getallheaders.php" - ] + "psr-4": { + "Laminas\\Hydrator\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } + "description": "Serialize objects to arrays, and vice versa", + "homepage": "https://laminas.dev", + "keywords": [ + "hydrator", + "laminas" ], - "description": "A polyfill for getallheaders.", - "time": "2019-03-08T08:55:37+00:00" + "time": "2019-12-31T17:06:38+00:00" }, { - "name": "ramsey/uuid", - "version": "3.8.0", + "name": "laminas/laminas-i18n", + "version": "2.9.2", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + "url": "https://github.com/laminas/laminas-i18n.git", + "reference": "48883436c6fa1f9ef001af295e3a40003c5cfbf2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/48883436c6fa1f9ef001af295e3a40003c5cfbf2", + "reference": "48883436c6fa1f9ef001af295e3a40003c5cfbf2", "shasum": "" }, "require": { - "paragonie/random_compat": "^1.0|^2.0|9.99.99", - "php": "^5.4 || ^7.0", - "symfony/polyfill-ctype": "^1.8" + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, "replace": { - "rhumsaa/uuid": "self.version" + "zendframework/zend-i18n": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^1.0 | ~2.0.0", - "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", - "ircmaxell/random-lib": "^1.1", - "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.9", - "moontoast/math": "^1.1", - "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|^5.0|^6.5", - "squizlabs/php_codesniffer": "^2.3" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16" }, "suggest": { - "ext-ctype": "Provides support for PHP Ctype functions", - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "ext-intl": "Required for most features of Laminas\\I18n; included in default builds of PHP", + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-eventmanager": "You should install this package to use the events in the translator", + "laminas/laminas-filter": "You should install this package to use the provided filters", + "laminas/laminas-i18n-resources": "Translation resources", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "You should install this package to use the provided validators", + "laminas/laminas-view": "You should install this package to use the provided view helpers" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" + }, + "laminas": { + "component": "Laminas\\I18n", + "config-provider": "Laminas\\I18n\\ConfigProvider" } }, "autoload": { "psr-4": { - "Ramsey\\Uuid\\": "src/" + "Laminas\\I18n\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - }, - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } + "BSD-3-Clause" ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", - "homepage": "https://github.com/ramsey/uuid", + "description": "Provide translations for your application, and filter and validate internationalized values", + "homepage": "https://laminas.dev", "keywords": [ - "guid", - "identifier", - "uuid" + "i18n", + "laminas" ], - "time": "2018-07-19T23:38:55+00:00" + "time": "2019-12-31T17:09:58+00:00" }, { - "name": "react/promise", - "version": "v2.7.1", + "name": "laminas/laminas-inputfilter", + "version": "2.10.1", "source": { "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d" + "url": "git@github.com:laminas/laminas-inputfilter.git", + "reference": "b29ce8f512c966468eee37ea4873ae5fb545d00a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d", - "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/b29ce8f512c966468eee37ea4873ae5fb545d00a", + "reference": "b29ce8f512c966468eee37ea4873ae5fb545d00a", "shasum": "" }, "require": { - "php": ">=5.4.0" + "laminas/laminas-filter": "^2.9.1", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.11", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-inputfilter": "self.version" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.15", + "psr/http-message": "^1.0" + }, + "suggest": { + "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" + }, + "laminas": { + "component": "Laminas\\InputFilter", + "config-provider": "Laminas\\InputFilter\\ConfigProvider" + } + }, "autoload": { "psr-4": { - "React\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + "Laminas\\InputFilter\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com" - } + "BSD-3-Clause" ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "description": "Normalize and validate input sets from the web, APIs, the CLI, and more, including files", + "homepage": "https://laminas.dev", "keywords": [ - "promise", - "promises" + "inputfilter", + "laminas" ], - "time": "2019-01-07T21:25:54+00:00" + "time": "2019-12-31T17:11:54+00:00" }, { - "name": "seld/jsonlint", - "version": "1.7.2", + "name": "laminas/laminas-json", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" + "url": "https://github.com/laminas/laminas-json.git", + "reference": "db58425b7f0eba44a7539450cc926af80915951a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", - "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "url": "https://api.github.com/repos/laminas/laminas-json/zipball/db58425b7f0eba44a7539450cc926af80915951a", + "reference": "db58425b7f0eba44a7539450cc926af80915951a", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-json": "self.version" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "fabpot/php-cs-fixer": "1.7.*", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.5 || ^3.0", + "laminas/laminas-xml": "^1.0.2", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "laminas/laminas-http": "Laminas\\Http component, required to use Laminas\\Json\\Server", + "laminas/laminas-server": "Laminas\\Server component, required to use Laminas\\Json\\Server", + "laminas/laminas-stdlib": "Laminas\\Stdlib component, for use with caching Laminas\\Json\\Server responses", + "laminas/laminas-xml": "To support Laminas\\Json\\Json::fromXml() usage" }, - "bin": [ - "bin/jsonlint" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } + }, "autoload": { "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" + "Laminas\\Json\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } + "BSD-3-Clause" ], - "description": "JSON Linter", + "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", + "homepage": "https://laminas.dev", "keywords": [ "json", - "linter", - "parser", - "validator" + "laminas" ], - "time": "2019-10-24T14:27:39+00:00" + "time": "2019-12-31T17:15:00+00:00" }, { - "name": "seld/phar-utils", - "version": "1.0.1", + "name": "laminas/laminas-loader", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "5d01c2c237ae9e68bec262f339947e2ea18979bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", - "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/5d01c2c237ae9e68bec262f339947e2ea18979bc", + "reference": "5d01c2c237ae9e68bec262f339947e2ea18979bc", "shasum": "" }, "require": { - "php": ">=5.3" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-loader": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" } }, "autoload": { "psr-4": { - "Seld\\PharUtils\\": "src/" + "Laminas\\Loader\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } + "BSD-3-Clause" ], - "description": "PHAR file format utilities, for when PHP phars you up", + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", "keywords": [ - "phra" + "laminas", + "loader" ], - "time": "2015-10-13T18:44:15+00:00" + "time": "2019-12-31T17:18:27+00:00" }, { - "name": "symfony/console", - "version": "v4.1.12", + "name": "laminas/laminas-log", + "version": "2.11.0", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "9e87c798f67dc9fceeb4f3d57847b52d945d1a02" + "url": "https://github.com/laminas/laminas-log.git", + "reference": "a702300d8639709b4f8f5f9617a62d1b973e5289" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9e87c798f67dc9fceeb4f3d57847b52d945d1a02", - "reference": "9e87c798f67dc9fceeb4f3d57847b52d945d1a02", + "url": "https://api.github.com/repos/laminas/laminas-log/zipball/a702300d8639709b4f8f5f9617a62d1b973e5289", + "reference": "a702300d8639709b4f8f5f9617a62d1b973e5289", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0" - }, - "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/process": "<3.3" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "psr/log": "^1.0" }, "provide": { - "psr/log-implementation": "1.0" + "psr/log-implementation": "1.0.0" + }, + "replace": { + "zendframework/zend-log": "self.version" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", - "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-filter": "^2.5", + "laminas/laminas-mail": "^2.6.1", + "laminas/laminas-validator": "^2.10.1", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.15" }, "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "ext-mongo": "mongo extension to use Mongo writer", + "ext-mongodb": "mongodb extension to use MongoDB writer", + "laminas/laminas-db": "Laminas\\Db component to use the database log writer", + "laminas/laminas-escaper": "Laminas\\Escaper component, for use in the XML log formatter", + "laminas/laminas-mail": "Laminas\\Mail component to use the email log writer", + "laminas/laminas-validator": "Laminas\\Validator component to block invalid log messages" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" + }, + "laminas": { + "component": "Laminas\\Log", + "config-provider": "Laminas\\Log\\ConfigProvider" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Log\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "Robust, composite logger with filtering, formatting, and PSR-3 support", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "log", + "logging" ], - "description": "Symfony Console Component", - "homepage": "https://symfony.com", - "time": "2019-01-25T14:34:37+00:00" + "time": "2019-12-31T17:18:57+00:00" }, { - "name": "symfony/css-selector", - "version": "v4.3.8", + "name": "laminas/laminas-mail", + "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9" + "url": "https://github.com/laminas/laminas-mail.git", + "reference": "019fb670c1dff6be7fc91d3b88942bd0a5f68792" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9", - "reference": "f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/019fb670c1dff6be7fc91d3b88942bd0a5f68792", + "reference": "019fb670c1dff6be7fc91d3b88942bd0a5f68792", "shasum": "" }, "require": { - "php": "^7.1.3" + "ext-iconv": "*", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "true/punycode": "^2.1" + }, + "replace": { + "zendframework/zend-mail": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" + }, + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" } }, "autoload": { "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Mail\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mail" ], - "description": "Symfony CssSelector Component", - "homepage": "https://symfony.com", - "time": "2019-10-02T08:36:26+00:00" + "time": "2019-12-31T17:21:22+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v4.3.8", + "name": "laminas/laminas-math", + "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "0df002fd4f500392eabd243c2947061a50937287" + "url": "https://github.com/laminas/laminas-math.git", + "reference": "8027b37e00accc43f28605c7d8fd081baed1f475" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0df002fd4f500392eabd243c2947061a50937287", - "reference": "0df002fd4f500392eabd243c2947061a50937287", + "url": "https://api.github.com/repos/laminas/laminas-math/zipball/8027b37e00accc43f28605c7d8fd081baed1f475", + "reference": "8027b37e00accc43f28605c7d8fd081baed1f475", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/event-dispatcher-contracts": "^1.1" - }, - "conflict": { - "symfony/dependency-injection": "<3.4" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" + "replace": { + "zendframework/zend-math": "self.version" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "~3.4|~4.0", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "^3.4|^4.0", - "symfony/service-contracts": "^1.1", - "symfony/stopwatch": "~3.4|~4.0" + "fabpot/php-cs-fixer": "1.7.*", + "ircmaxell/random-lib": "~1.1", + "phpunit/phpunit": "~4.0" }, "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "ext-bcmath": "If using the bcmath functionality", + "ext-gmp": "If using the gmp functionality", + "ircmaxell/random-lib": "Fallback random byte generator for Laminas\\Math\\Rand if Mcrypt extensions is unavailable" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "2.7-dev", + "dev-develop": "2.8-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Math\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "math" ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2019-11-03T09:04:05+00:00" + "time": "2019-12-31T17:24:15+00:00" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.7", + "name": "laminas/laminas-mime", + "version": "2.7.2", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" + "url": "https://github.com/laminas/laminas-mime.git", + "reference": "2dbace2c69542e5a251af3becb6d7209ac9fb42b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", - "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/2dbace2c69542e5a251af3becb6d7209ac9fb42b", + "reference": "2dbace2c69542e5a251af3becb6d7209ac9fb42b", "shasum": "" }, "require": { - "php": "^7.1.3" + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-mime": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-mail": "^2.6", + "phpunit/phpunit": "^5.7.21 || ^6.3" }, "suggest": { - "psr/event-dispatcher": "", - "symfony/event-dispatcher-implementation": "" + "laminas/laminas-mail": "Laminas\\Mail component" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "branch-alias": { + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\Mime\\": "src/" } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", + "description": "Create and parse MIME messages and parts", + "homepage": "https://laminas.dev", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "laminas", + "mime" ], - "time": "2019-09-17T09:54:03+00:00" + "time": "2019-12-31T17:25:27+00:00" }, { - "name": "symfony/filesystem", - "version": "v4.3.8", + "name": "laminas/laminas-modulemanager", + "version": "2.8.4", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" + "url": "https://github.com/laminas/laminas-modulemanager.git", + "reference": "92b1cde1aab5aef687b863face6dd5d9c6751c78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", - "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/92b1cde1aab5aef687b863face6dd5d9c6751c78", + "reference": "92b1cde1aab5aef687b863face6dd5d9c6751c78", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-ctype": "~1.8" + "laminas/laminas-config": "^3.1 || ^2.6", + "laminas/laminas-eventmanager": "^3.2 || ^2.6.3", + "laminas/laminas-stdlib": "^3.1 || ^2.7", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-modulemanager": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-console": "^2.6", + "laminas/laminas-di": "^2.6", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mvc": "^3.0 || ^2.7", + "laminas/laminas-servicemanager": "^3.0.3 || ^2.7.5", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16" + }, + "suggest": { + "laminas/laminas-console": "Laminas\\Console component", + "laminas/laminas-loader": "Laminas\\Loader component if you are not using Composer autoloading for your modules", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "2.8.x-dev", + "dev-develop": "2.9.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\ModuleManager\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "Modular application system for laminas-mvc applications", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "modulemanager" ], - "description": "Symfony Filesystem Component", - "homepage": "https://symfony.com", - "time": "2019-08-20T14:07:54+00:00" + "time": "2019-12-31T17:26:56+00:00" }, { - "name": "symfony/finder", - "version": "v4.3.8", + "name": "laminas/laminas-mvc", + "version": "2.7.15", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f" + "url": "https://github.com/laminas/laminas-mvc.git", + "reference": "7e7198b03556a57fb5fd3ed919d9e1cf71500642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/72a068f77e317ae77c0a0495236ad292cfb5ce6f", - "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f", + "url": "https://api.github.com/repos/laminas/laminas-mvc/zipball/7e7198b03556a57fb5fd3ed919d9e1cf71500642", + "reference": "7e7198b03556a57fb5fd3ed919d9e1cf71500642", "shasum": "" }, "require": { - "php": "^7.1.3" + "container-interop/container-interop": "^1.1", + "laminas/laminas-console": "^2.7", + "laminas/laminas-eventmanager": "^2.6.4 || ^3.0", + "laminas/laminas-form": "^2.11", + "laminas/laminas-hydrator": "^1.1 || ^2.4", + "laminas/laminas-psr7bridge": "^0.2", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7.5 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-mvc": "self.version" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "1.7.*", + "laminas/laminas-authentication": "^2.6", + "laminas/laminas-cache": "^2.8", + "laminas/laminas-di": "^2.6", + "laminas/laminas-filter": "^2.8", + "laminas/laminas-http": "^2.8", + "laminas/laminas-i18n": "^2.8", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.3", + "laminas/laminas-modulemanager": "^2.8", + "laminas/laminas-serializer": "^2.8", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.7", + "laminas/laminas-uri": "^2.6", + "laminas/laminas-validator": "^2.10", + "laminas/laminas-view": "^2.9", + "phpunit/phpunit": "^4.8.36", + "sebastian/comparator": "^1.2.4", + "sebastian/version": "^1.0.4" + }, + "suggest": { + "laminas/laminas-authentication": "Laminas\\Authentication component for Identity plugin", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-di": "Laminas\\Di component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component for translatable segments", + "laminas/laminas-inputfilter": "Laminas\\Inputfilter component", + "laminas/laminas-json": "Laminas\\Json component", + "laminas/laminas-log": "Laminas\\Log component", + "laminas/laminas-modulemanager": "Laminas\\ModuleManager component", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager-di": "^1.0.1, if using laminas-servicemanager v3 and requiring the laminas-di integration", + "laminas/laminas-session": "Laminas\\Session component for FlashMessenger, PRG, and FPRG plugins", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-uri": "Laminas\\Uri component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-view": "Laminas\\View component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "2.7-dev", + "dev-develop": "3.0-dev" } }, "autoload": { + "files": [ + "src/autoload.php" + ], "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Mvc\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mvc" ], - "description": "Symfony Finder Component", - "homepage": "https://symfony.com", - "time": "2019-10-30T12:53:54+00:00" + "time": "2019-12-31T17:32:15+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "name": "laminas/laminas-psr7bridge", + "version": "0.2.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "url": "https://github.com/laminas/laminas-psr7bridge.git", + "reference": "14780ef1d40effd59d77ab29c6d439b2af42cdfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/laminas/laminas-psr7bridge/zipball/14780ef1d40effd59d77ab29c6d439b2af42cdfa", + "reference": "14780ef1d40effd59d77ab29c6d439b2af42cdfa", "shasum": "" }, "require": { - "php": ">=5.3.3" + "laminas/laminas-diactoros": "^1.1", + "laminas/laminas-http": "^2.5", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": ">=5.5", + "psr/http-message": "^1.0" }, - "suggest": { - "ext-ctype": "For best performance" + "replace": { + "zendframework/zend-psr7bridge": "self.version" + }, + "require-dev": { + "phpunit/phpunit": "^4.7", + "squizlabs/php_codesniffer": "^2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.0-dev", + "dev-develop": "1.1-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Laminas\\Psr7Bridge\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "BSD-3-Clause" ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", + "description": "PSR-7 <-> Laminas\\Http bridge", + "homepage": "https://laminas.dev", "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "http", + "laminas", + "psr", + "psr-7" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-12-31T17:38:47+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.12.0", + "name": "laminas/laminas-serializer", + "version": "2.9.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + "url": "https://github.com/laminas/laminas-serializer.git", + "reference": "c1c9361f114271b0736db74e0083a919081af5e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "url": "https://api.github.com/repos/laminas/laminas-serializer/zipball/c1c9361f114271b0736db74e0083a919081af5e0", + "reference": "c1c9361f114271b0736db74e0083a919081af5e0", "shasum": "" }, "require": { - "php": ">=5.3.3" + "laminas/laminas-json": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-serializer": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-math": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16" }, "suggest": { - "ext-mbstring": "For best performance" + "laminas/laminas-math": "(^2.6 || ^3.0) To support Python Pickle serialization", + "laminas/laminas-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" + }, + "laminas": { + "component": "Laminas\\Serializer", + "config-provider": "Laminas\\Serializer\\ConfigProvider" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Laminas\\Serializer\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "BSD-3-Clause" ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", + "description": "Serialize and deserialize PHP structures to a variety of representations", + "homepage": "https://laminas.dev", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "laminas", + "serializer" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-12-31T17:42:11+00:00" }, { - "name": "symfony/process", - "version": "v4.3.8", + "name": "laminas/laminas-server", + "version": "2.8.1", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0" + "url": "https://github.com/laminas/laminas-server.git", + "reference": "4aaca9174c40a2fab2e2aa77999da99f71bdd88e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/3b2e0cb029afbb0395034509291f21191d1a4db0", - "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0", + "url": "https://api.github.com/repos/laminas/laminas-server/zipball/4aaca9174c40a2fab2e2aa77999da99f71bdd88e", + "reference": "4aaca9174c40a2fab2e2aa77999da99f71bdd88e", "shasum": "" }, "require": { - "php": "^7.1.3" + "laminas/laminas-code": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.5 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-server": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "2.8.x-dev", + "dev-develop": "2.9.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Server\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "Create Reflection-based RPC servers", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "server" ], - "description": "Symfony Process Component", - "homepage": "https://symfony.com", - "time": "2019-10-28T17:07:32+00:00" + "time": "2019-12-31T17:43:03+00:00" }, { - "name": "tedivm/jshrink", - "version": "v1.3.3", + "name": "laminas/laminas-servicemanager", + "version": "2.7.11", "source": { "type": "git", - "url": "https://github.com/tedious/JShrink.git", - "reference": "566e0c731ba4e372be2de429ef7d54f4faf4477a" + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "841abb656c6018afebeec1f355be438426d6a3dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tedious/JShrink/zipball/566e0c731ba4e372be2de429ef7d54f4faf4477a", - "reference": "566e0c731ba4e372be2de429ef7d54f4faf4477a", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/841abb656c6018afebeec1f355be438426d6a3dd", + "reference": "841abb656c6018afebeec1f355be438426d6a3dd", "shasum": "" }, "require": { - "php": "^5.6|^7.0" + "container-interop/container-interop": "~1.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-servicemanager": "self.version" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.8", - "php-coveralls/php-coveralls": "^1.1.0", - "phpunit/phpunit": "^6" + "athletic/athletic": "dev-master", + "fabpot/php-cs-fixer": "1.7.*", + "laminas/laminas-di": "~2.5", + "laminas/laminas-mvc": "~2.5", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "laminas/laminas-di": "Laminas\\Di component", + "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev", + "dev-develop": "3.0-dev" + } + }, "autoload": { - "psr-0": { - "JShrink": "src/" + "psr-4": { + "Laminas\\ServiceManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Robert Hafner", - "email": "tedivm@tedivm.com" - } - ], - "description": "Javascript Minifier built in PHP", - "homepage": "http://github.com/tedious/JShrink", + "homepage": "https://laminas.dev", "keywords": [ - "javascript", - "minifier" + "laminas", + "servicemanager" ], - "time": "2019-06-28T18:11:46+00:00" + "time": "2019-12-31T17:44:16+00:00" }, { - "name": "true/punycode", - "version": "v2.1.1", + "name": "laminas/laminas-session", + "version": "2.9.1", "source": { "type": "git", - "url": "https://github.com/true/php-punycode.git", - "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" + "url": "https://github.com/laminas/laminas-session.git", + "reference": "60b5cc844e09627d4f1a2a547e13268f376ccb3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", - "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "url": "https://api.github.com/repos/laminas/laminas-session/zipball/60b5cc844e09627d4f1a2a547e13268f376ccb3d", + "reference": "60b5cc844e09627d4f1a2a547e13268f376ccb3d", "shasum": "" }, "require": { - "php": ">=5.3.0", - "symfony/polyfill-mbstring": "^1.3" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-session": "self.version" }, "require-dev": { - "phpunit/phpunit": "~4.7", - "squizlabs/php_codesniffer": "~2.0" + "container-interop/container-interop": "^1.1", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.7", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6", + "mongodb/mongodb": "^1.0.1", + "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16" + }, + "suggest": { + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-db": "Laminas\\Db component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "Laminas\\Validator component", + "mongodb/mongodb": "If you want to use the MongoDB session save handler" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" + }, + "laminas": { + "component": "Laminas\\Session", + "config-provider": "Laminas\\Session\\ConfigProvider" + } + }, "autoload": { "psr-4": { - "TrueBV\\": "src/" + "Laminas\\Session\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Renan Gonçalves", - "email": "renan.saddam@gmail.com" - } + "BSD-3-Clause" ], - "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", - "homepage": "https://github.com/true/php-punycode", + "description": "Object-oriented interface to PHP sessions and storage", + "homepage": "https://laminas.dev", "keywords": [ - "idna", - "punycode" + "laminas", + "session" ], - "time": "2016-11-16T10:37:54+00:00" + "time": "2019-12-31T17:46:59+00:00" }, { - "name": "tubalmartin/cssmin", - "version": "v4.1.1", + "name": "laminas/laminas-soap", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port.git", - "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf" + "url": "https://github.com/laminas/laminas-soap.git", + "reference": "34f91d5c4c0a78bc5689cca2d1eaf829b27edd72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/3cbf557f4079d83a06f9c3ff9b957c022d7805cf", - "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf", + "url": "https://api.github.com/repos/laminas/laminas-soap/zipball/34f91d5c4c0a78bc5689cca2d1eaf829b27edd72", + "reference": "34f91d5c4c0a78bc5689cca2d1eaf829b27edd72", "shasum": "" }, "require": { - "ext-pcre": "*", - "php": ">=5.3.2" + "ext-soap": "*", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-uri": "^2.5.2", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-soap": "self.version" }, "require-dev": { - "cogpowered/finediff": "0.3.*", - "phpunit/phpunit": "4.8.*" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-http": "^2.5.4", + "phpunit/phpunit": "^5.7.21 || ^6.3" + }, + "suggest": { + "laminas/laminas-http": "Laminas\\Http component" }, - "bin": [ - "cssmin" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" + } + }, "autoload": { "psr-4": { - "tubalmartin\\CssMin\\": "src" + "Laminas\\Soap\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Túbal Martín", - "homepage": "http://tubalmartin.me/" - } - ], - "description": "A PHP port of the YUI CSS compressor", - "homepage": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port", + "homepage": "https://laminas.dev", "keywords": [ - "compress", - "compressor", - "css", - "cssmin", - "minify", - "yui" + "laminas", + "soap" ], - "time": "2018-01-15T15:26:51+00:00" + "time": "2019-12-31T17:48:49+00:00" }, { - "name": "webonyx/graphql-php", - "version": "v0.13.8", + "name": "laminas/laminas-stdlib", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/webonyx/graphql-php.git", - "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8" + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "2b18347625a2f06a1a485acfbc870f699dbe51c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/6829ae58f4c59121df1f86915fb9917a2ec595e8", - "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/2b18347625a2f06a1a485acfbc870f699dbe51c6", + "reference": "2b18347625a2f06a1a485acfbc870f699dbe51c6", "shasum": "" }, "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": "^7.1||^8.0" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan": "^0.11.4", - "phpstan/phpstan-phpunit": "^0.11.0", - "phpstan/phpstan-strict-rules": "^0.11.0", - "phpunit/phpcov": "^5.0", - "phpunit/phpunit": "^7.2", - "psr/http-message": "^1.0", - "react/promise": "2.*" + "replace": { + "zendframework/zend-stdlib": "self.version" }, - "suggest": { - "psr/http-message": "To use standard GraphQL server", - "react/promise": "To leverage async resolving on React PHP platform" + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" + } + }, "autoload": { "psr-4": { - "GraphQL\\": "src/" + "Laminas\\Stdlib\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "description": "A PHP port of GraphQL reference implementation", - "homepage": "https://github.com/webonyx/graphql-php", + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", "keywords": [ - "api", - "graphql" + "laminas", + "stdlib" ], - "time": "2019-08-25T10:32:47+00:00" + "time": "2019-12-31T17:51:15+00:00" }, { - "name": "wikimedia/less.php", - "version": "1.8.2", + "name": "laminas/laminas-text", + "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/wikimedia/less.php.git", - "reference": "e238ad228d74b6ffd38209c799b34e9826909266" + "url": "https://github.com/laminas/laminas-text.git", + "reference": "3601b5eacb06ed0a12f658df860cc0f9613cf4db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wikimedia/less.php/zipball/e238ad228d74b6ffd38209c799b34e9826909266", - "reference": "e238ad228d74b6ffd38209c799b34e9826909266", + "url": "https://api.github.com/repos/laminas/laminas-text/zipball/3601b5eacb06ed0a12f658df860cc0f9613cf4db", + "reference": "3601b5eacb06ed0a12f658df860cc0f9613cf4db", "shasum": "" }, "require": { - "php": ">=7.2.9" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-text": "self.version" }, "require-dev": { - "phpunit/phpunit": "7.5.14" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" }, - "bin": [ - "bin/lessc" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" + } + }, "autoload": { - "psr-0": { - "Less": "lib/" - }, - "classmap": [ - "lessc.inc.php" - ] + "psr-4": { + "Laminas\\Text\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Josh Schmidt", - "homepage": "https://github.com/oyejorge" - }, - { - "name": "Matt Agar", - "homepage": "https://github.com/agar" - }, - { - "name": "Martin Jantošovič", - "homepage": "https://github.com/Mordred" - } + "BSD-3-Clause" ], - "description": "PHP port of the Javascript version of LESS http://lesscss.org (Originally maintained by Josh Schmidt)", + "description": "Create FIGlets and text-based tables", + "homepage": "https://laminas.dev", "keywords": [ - "css", - "less", - "less.js", - "lesscss", - "php", - "stylesheet" + "laminas", + "text" ], - "time": "2019-11-06T18:30:11+00:00" + "time": "2019-12-31T17:54:52+00:00" }, { - "name": "zendframework/zend-captcha", - "version": "2.9.0", + "name": "laminas/laminas-uri", + "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-captcha.git", - "reference": "4272f3d0cde0a1fa9135d0cbc4a629fb655391d3" + "url": "https://github.com/laminas/laminas-uri.git", + "reference": "6be8ce19622f359b048ce4faebf1aa1bca73a7ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-captcha/zipball/4272f3d0cde0a1fa9135d0cbc4a629fb655391d3", - "reference": "4272f3d0cde0a1fa9135d0cbc4a629fb655391d3", + "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/6be8ce19622f359b048ce4faebf1aa1bca73a7ff", + "reference": "6be8ce19622f359b048ce4faebf1aa1bca73a7ff", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-math": "^2.7 || ^3.0", - "zendframework/zend-stdlib": "^3.2.1" + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-validator": "^2.10", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-session": "^2.8", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.10.1", - "zendframework/zendservice-recaptcha": "^3.0" + "replace": { + "zendframework/zend-uri": "self.version" }, - "suggest": { - "zendframework/zend-i18n-resources": "Translations of captcha messages", - "zendframework/zend-session": "Zend\\Session component", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha component" + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Captcha\\": "src/" + "Laminas\\Uri\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "Generate and validate CAPTCHAs using Figlets, images, ReCaptcha, and more", + "description": "A component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "homepage": "https://laminas.dev", "keywords": [ - "ZendFramework", - "captcha", - "zf" + "laminas", + "uri" ], - "time": "2019-06-18T09:32:52+00:00" + "time": "2019-12-31T17:56:00+00:00" }, { - "name": "zendframework/zend-code", - "version": "3.3.2", + "name": "laminas/laminas-validator", + "version": "2.12.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-code.git", - "reference": "936fa7ad4d53897ea3e3eb41b5b760828246a20b" + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "0813f234812d9fa9058b6da39eb13dedc90227db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/936fa7ad4d53897ea3e3eb41b5b760828246a20b", - "reference": "936fa7ad4d53897ea3e3eb41b5b760828246a20b", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/0813f234812d9fa9058b6da39eb13dedc90227db", + "reference": "0813f234812d9fa9058b6da39eb13dedc90227db", "shasum": "" }, "require": { - "php": "^7.1", - "zendframework/zend-eventmanager": "^2.6 || ^3.0" + "container-interop/container-interop": "^1.1", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "require-dev": { - "doctrine/annotations": "^1.0", - "ext-phar": "*", - "phpunit/phpunit": "^7.5.15", - "zendframework/zend-coding-standard": "^1.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "replace": { + "zendframework/zend-validator": "self.version" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.5", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "psr/http-message": "^1.0" }, "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "zendframework/zend-stdlib": "Zend\\Stdlib component" + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3.x-dev", - "dev-develop": "3.4.x-dev" + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" + }, + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Code\\": "src/" + "Laminas\\Validator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", "keywords": [ - "ZendFramework", - "code", - "zf" + "laminas", + "validator" ], - "time": "2019-08-31T14:14:34+00:00" + "time": "2019-12-31T17:57:44+00:00" }, { - "name": "zendframework/zend-config", - "version": "2.6.0", + "name": "laminas/laminas-view", + "version": "2.11.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-config.git", - "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d" + "url": "https://github.com/laminas/laminas-view.git", + "reference": "f2f321d45f91742ab9417997d705073a34d60db7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", - "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", + "url": "https://api.github.com/repos/laminas/laminas-view/zipball/f2f321d45f91742ab9417997d705073a34d60db7", + "reference": "f2f321d45f91742ab9417997d705073a34d60db7", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-json": "^2.6.1 || ^3.0", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.5", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "replace": { + "zendframework/zend-view": "self.version" + }, + "require-dev": { + "laminas/laminas-authentication": "^2.5", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-console": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-feed": "^2.7", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-log": "^2.7", + "laminas/laminas-modulemanager": "^2.7.1", + "laminas/laminas-mvc": "^2.7.14 || ^3.0", + "laminas/laminas-navigation": "^2.5", + "laminas/laminas-paginator": "^2.5", + "laminas/laminas-permissions-acl": "^2.6", + "laminas/laminas-router": "^3.0.1", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-uri": "^2.5", + "phpunit/phpunit": "^5.7.15 || ^6.0.8" }, "suggest": { - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", - "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" + "laminas/laminas-authentication": "Laminas\\Authentication component", + "laminas/laminas-escaper": "Laminas\\Escaper component", + "laminas/laminas-feed": "Laminas\\Feed component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-mvc-plugin-flashmessenger": "laminas-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with laminas-mvc versions 3 and up", + "laminas/laminas-navigation": "Laminas\\Navigation component", + "laminas/laminas-paginator": "Laminas\\Paginator component", + "laminas/laminas-permissions-acl": "Laminas\\Permissions\\Acl component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-uri": "Laminas\\Uri component" }, + "bin": [ + "bin/templatemap_generator.php" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Config\\": "src/" + "Laminas\\View\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "provides a nested object property based user interface for accessing this configuration data within application code", - "homepage": "https://github.com/zendframework/zend-config", + "description": "Flexible view layer supporting and providing multiple view layers, helpers, and more", + "homepage": "https://laminas.dev", "keywords": [ - "config", - "zf2" + "laminas", + "view" ], - "time": "2016-02-04T23:01:10+00:00" + "time": "2019-12-31T18:03:25+00:00" }, { - "name": "zendframework/zend-console", - "version": "2.8.0", + "name": "laminas/laminas-zendframework-bridge", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-console.git", - "reference": "95817ae78f73c48026972e350a2ecc31c6d9f9ae" + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "0fb9675b84a1666ab45182b6c5b29956921e818d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-console/zipball/95817ae78f73c48026972e350a2ecc31c6d9f9ae", - "reference": "95817ae78f73c48026972e350a2ecc31c6d9f9ae", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/0fb9675b84a1666ab45182b6c5b29956921e818d", + "reference": "0fb9675b84a1666ab45182b6c5b29956921e818d", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^3.2.1" + "php": "^5.6 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-filter": "^2.7.2", - "zendframework/zend-json": "^2.6 || ^3.0", - "zendframework/zend-validator": "^2.10.1" - }, - "suggest": { - "zendframework/zend-filter": "To support DefaultRouteMatcher usage", - "zendframework/zend-validator": "To support DefaultRouteMatcher usage" + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1", + "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "1.0.x-dev", + "dev-develop": "1.1.x-dev" + }, + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" } }, "autoload": { + "files": [ + "src/autoload.php" + ], "psr-4": { - "Zend\\Console\\": "src/" + "Laminas\\ZendFrameworkBridge\\": "src//" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "Build console applications using getopt syntax or routing, complete with prompts", + "description": "Alias legacy ZF class names to Laminas Project equivalents.", "keywords": [ "ZendFramework", - "console", + "autoloading", + "laminas", "zf" ], - "time": "2019-02-04T19:48:22+00:00" + "time": "2020-01-07T22:58:31+00:00" }, { - "name": "zendframework/zend-crypt", - "version": "2.6.0", + "name": "magento/composer", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-crypt.git", - "reference": "1b2f5600bf6262904167116fa67b58ab1457036d" + "url": "https://github.com/magento/composer.git", + "reference": "ea12b95be5c0833b3d9497aaefa08816c19e5dcd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-crypt/zipball/1b2f5600bf6262904167116fa67b58ab1457036d", - "reference": "1b2f5600bf6262904167116fa67b58ab1457036d", + "url": "https://api.github.com/repos/magento/composer/zipball/ea12b95be5c0833b3d9497aaefa08816c19e5dcd", + "reference": "ea12b95be5c0833b3d9497aaefa08816c19e5dcd", "shasum": "" }, "require": { - "container-interop/container-interop": "~1.0", - "php": "^5.5 || ^7.0", - "zendframework/zend-math": "^2.6", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "composer/composer": "^1.6", + "php": "~7.1.3||~7.2.0||~7.3.0", + "symfony/console": "~4.0.0 || ~4.1.0" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-mcrypt": "Required for most features of Zend\\Crypt" + "phpunit/phpunit": "~7.0.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" - } - }, "autoload": { "psr-4": { - "Zend\\Crypt\\": "src/" + "Magento\\Composer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" - ], - "homepage": "https://github.com/zendframework/zend-crypt", - "keywords": [ - "crypt", - "zf2" + "OSL-3.0", + "AFL-3.0" ], - "time": "2016-02-03T23:46:30+00:00" + "description": "Magento composer library helps to instantiate Composer application and run composer commands.", + "time": "2019-07-29T19:52:05+00:00" }, { - "name": "zendframework/zend-db", - "version": "2.10.0", + "name": "magento/magento-composer-installer", + "version": "0.1.13", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-db.git", - "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e" + "url": "https://github.com/magento/magento-composer-installer.git", + "reference": "8b6c32f53b4944a5d6656e86344cd0f9784709a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", - "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", + "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/8b6c32f53b4944a5d6656e86344cd0f9784709a1", + "reference": "8b6c32f53b4944a5d6656e86344cd0f9784709a1", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "composer-plugin-api": "^1.0" }, - "require-dev": { - "phpunit/phpunit": "^5.7.25 || ^6.4.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "replace": { + "magento-hackathon/magento-composer-installer": "*" }, - "suggest": { - "zendframework/zend-eventmanager": "Zend\\EventManager component", - "zendframework/zend-hydrator": "Zend\\Hydrator component for using HydratingResultSets", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "require-dev": { + "composer/composer": "*@dev", + "firegento/phpcs": "dev-patch-1", + "mikey179/vfsstream": "*", + "phpunit/phpunit": "*", + "phpunit/phpunit-mock-objects": "dev-master", + "squizlabs/php_codesniffer": "1.4.7", + "symfony/process": "*" }, - "type": "library", + "type": "composer-plugin", "extra": { - "branch-alias": { - "dev-master": "2.9-dev", - "dev-develop": "2.10-dev" - }, - "zf": { - "component": "Zend\\Db", - "config-provider": "Zend\\Db\\ConfigProvider" - } + "composer-command-registry": [ + "MagentoHackathon\\Composer\\Magento\\Command\\DeployCommand" + ], + "class": "MagentoHackathon\\Composer\\Magento\\Plugin" }, "autoload": { - "psr-4": { - "Zend\\Db\\": "src/" + "psr-0": { + "MagentoHackathon\\Composer\\Magento": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "OSL-3.0" ], - "description": "Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations", + "authors": [ + { + "name": "Vinai Kopp", + "email": "vinai@netzarbeiter.com" + }, + { + "name": "Daniel Fahlke aka Flyingmana", + "email": "flyingmana@googlemail.com" + }, + { + "name": "Jörg Weller", + "email": "weller@flagbit.de" + }, + { + "name": "Karl Spies", + "email": "karl.spies@gmx.net" + }, + { + "name": "Tobias Vogt", + "email": "tobi@webguys.de" + }, + { + "name": "David Fuhr", + "email": "fuhr@flagbit.de" + } + ], + "description": "Composer installer for Magento modules", + "homepage": "https://github.com/magento/magento-composer-installer", "keywords": [ - "ZendFramework", - "db", - "zf" + "composer-installer", + "magento" ], - "time": "2019-02-25T11:37:45+00:00" + "time": "2017-12-29T16:45:24+00:00" }, { - "name": "zendframework/zend-di", - "version": "2.6.1", + "name": "magento/zendframework1", + "version": "1.14.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-di.git", - "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37" + "url": "https://github.com/magento/zf1.git", + "reference": "8221062d42a198e431d183bbe672e5e1a2f98c5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-di/zipball/1fd1ba85660b5a2718741b38639dc7c4c3194b37", - "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37", + "url": "https://api.github.com/repos/magento/zf1/zipball/8221062d42a198e431d183bbe672e5e1a2f98c5f", + "reference": "8221062d42a198e431d183bbe672e5e1a2f98c5f", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.1", - "php": "^5.5 || ^7.0", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": ">=5.2.11" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0" + "phpunit/dbunit": "1.3.*", + "phpunit/phpunit": "3.7.*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" + "dev-master": "1.12.x-dev" } }, "autoload": { - "psr-4": { - "Zend\\Di\\": "src/" + "psr-0": { + "Zend_": "library/" } }, "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "library/" + ], "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-di", + "description": "Magento Zend Framework 1", + "homepage": "http://framework.zend.com/", "keywords": [ - "di", - "zf2" + "ZF1", + "framework" ], - "time": "2016-04-25T20:58:11+00:00" + "time": "2019-07-26T16:43:11+00:00" }, { - "name": "zendframework/zend-diactoros", - "version": "1.8.7", + "name": "monolog/monolog", + "version": "1.25.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b" + "url": "https://github.com/Seldaek/monolog.git", + "reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/a85e67b86e9b8520d07e6415fcbcb8391b44a75b", - "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d5e2fb341cb44f7e2ab639d12a1e5901091ec287", + "reference": "d5e2fb341cb44f7e2ab639d12a1e5901091ec287", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "psr/http-message": "^1.0" + "php": ">=5.3.0", + "psr/log": "~1.0" }, "provide": { - "psr/http-message-implementation": "1.0" + "psr/log-implementation": "1.0.0" }, "require-dev": { - "ext-dom": "*", - "ext-libxml": "*", - "php-http/psr7-integration-tests": "dev-master", - "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", - "zendframework/zend-coding-standard": "~1.0" + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" }, "type": "library", "extra": { "branch-alias": { - "dev-release-1.8": "1.8.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php" - ], "psr-4": { - "Zend\\Diactoros\\": "src/" + "Monolog\\": "src/Monolog" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "MIT" ], - "description": "PSR HTTP Message implementations", - "homepage": "https://github.com/zendframework/zend-diactoros", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", "keywords": [ - "http", - "psr", - "psr-7" + "log", + "logging", + "psr-3" ], - "time": "2019-08-06T17:53:53+00:00" + "time": "2019-11-13T10:00:05+00:00" }, { - "name": "zendframework/zend-escaper", - "version": "2.6.1", + "name": "paragonie/random_compat", + "version": "v9.99.99", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-escaper.git", - "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f" + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", - "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6.x-dev", - "dev-develop": "2.7.x-dev" - } + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, - "autoload": { - "psr-4": { - "Zend\\Escaper\\": "src/" - } + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, + "type": "library", "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", - "keywords": [ - "ZendFramework", - "escaper", - "zf" + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" ], - "time": "2019-09-05T20:03:20+00:00" + "time": "2018-07-02T15:55:56+00:00" }, { - "name": "zendframework/zend-eventmanager", - "version": "3.2.1", + "name": "paragonie/sodium_compat", + "version": "v1.12.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-eventmanager.git", - "reference": "a5e2583a211f73604691586b8406ff7296a946dd" + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "063cae9b3a7323579063e7037720f5b52b56c178" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/a5e2583a211f73604691586b8406ff7296a946dd", - "reference": "a5e2583a211f73604691586b8406ff7296a946dd", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/063cae9b3a7323579063e7037720f5b52b56c178", + "reference": "063cae9b3a7323579063e7037720f5b52b56c178", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "paragonie/random_compat": ">=1", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" }, "require-dev": { - "athletic/athletic": "^0.1", - "container-interop/container-interop": "^1.1.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-stdlib": "^2.7.3 || ^3.0" + "phpunit/phpunit": "^3|^4|^5|^6|^7" }, "suggest": { - "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", - "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev", - "dev-develop": "3.3-dev" - } - }, "autoload": { - "psr-4": { - "Zend\\EventManager\\": "src/" - } + "files": [ + "autoload.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "ISC" ], - "description": "Trigger and listen to events within a PHP application", - "homepage": "https://github.com/zendframework/zend-eventmanager", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", "keywords": [ - "event", - "eventmanager", - "events", - "zf2" + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" ], - "time": "2018-04-25T15:33:34+00:00" + "time": "2019-11-07T17:07:24+00:00" }, { - "name": "zendframework/zend-feed", - "version": "2.12.0", + "name": "pelago/emogrifier", + "version": "v2.2.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-feed.git", - "reference": "d926c5af34b93a0121d5e2641af34ddb1533d733" + "url": "https://github.com/MyIntervals/emogrifier.git", + "reference": "2472bc1c3a2dee8915ecc2256139c6100024332f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/d926c5af34b93a0121d5e2641af34ddb1533d733", - "reference": "d926c5af34b93a0121d5e2641af34ddb1533d733", + "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/2472bc1c3a2dee8915ecc2256139c6100024332f", + "reference": "2472bc1c3a2dee8915ecc2256139c6100024332f", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", - "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5.2", - "zendframework/zend-stdlib": "^3.2.1" + "php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0", + "symfony/css-selector": "^3.4.0 || ^4.0.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "psr/http-message": "^1.0.1", - "zendframework/zend-cache": "^2.7.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-http": "^2.7", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-validator": "^2.10.1" - }, - "suggest": { - "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Zend\\Feed\\Reader\\Http\\Psr7ResponseDecorator", - "zendframework/zend-cache": "Zend\\Cache component, for optionally caching feeds between requests", - "zendframework/zend-db": "Zend\\Db component, for use with PubSubHubbub", - "zendframework/zend-http": "Zend\\Http for PubSubHubbub, and optionally for use with Zend\\Feed\\Reader", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for easily extending ExtensionManager implementations", - "zendframework/zend-validator": "Zend\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" + "friendsofphp/php-cs-fixer": "^2.2.0", + "phpmd/phpmd": "^2.6.0", + "phpunit/phpunit": "^4.8.0", + "squizlabs/php_codesniffer": "^3.3.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.12.x-dev", - "dev-develop": "2.13.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Feed\\": "src/" + "Pelago\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "provides functionality for consuming RSS and Atom feeds", + "authors": [ + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Zoli Szabó", + "email": "zoli.szabo+github@gmail.com" + }, + { + "name": "John Reeve", + "email": "jreeve@pelagodesign.com" + }, + { + "name": "Jake Hotson", + "email": "jake@qzdesign.co.uk" + }, + { + "name": "Cameron Brooks" + }, + { + "name": "Jaime Prado" + } + ], + "description": "Converts CSS styles into inline style attributes in your HTML code", + "homepage": "https://www.myintervals.com/emogrifier.php", "keywords": [ - "ZendFramework", - "feed", - "zf" + "css", + "email", + "pre-processing" ], - "time": "2019-03-05T20:08:49+00:00" + "time": "2019-09-04T16:07:59+00:00" }, { - "name": "zendframework/zend-filter", - "version": "2.9.2", + "name": "php-amqplib/php-amqplib", + "version": "v2.10.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-filter.git", - "reference": "d78f2cdde1c31975e18b2a0753381ed7b61118ef" + "url": "https://github.com/php-amqplib/php-amqplib.git", + "reference": "6e2b2501e021e994fb64429e5a78118f83b5c200" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/d78f2cdde1c31975e18b2a0753381ed7b61118ef", - "reference": "d78f2cdde1c31975e18b2a0753381ed7b61118ef", + "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/6e2b2501e021e994fb64429e5a78118f83b5c200", + "reference": "6e2b2501e021e994fb64429e5a78118f83b5c200", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "ext-bcmath": "*", + "ext-sockets": "*", + "php": ">=5.6" }, - "conflict": { - "zendframework/zend-validator": "<2.10.1" + "replace": { + "videlalvaro/php-amqplib": "self.version" }, "require-dev": { - "pear/archive_tar": "^1.4.3", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "psr/http-factory": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-crypt": "^3.2.1", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-uri": "^2.6" - }, - "suggest": { - "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", - "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", - "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", - "zendframework/zend-uri": "Zend\\Uri component, for the UriNormalize filter" + "ext-curl": "*", + "nategood/httpful": "^0.2.20", + "phpunit/phpunit": "^5.7|^6.5|^7.0", + "squizlabs/php_codesniffer": "^2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" - }, - "zf": { - "component": "Zend\\Filter", - "config-provider": "Zend\\Filter\\ConfigProvider" + "dev-master": "2.10-dev" } }, "autoload": { "psr-4": { - "Zend\\Filter\\": "src/" + "PhpAmqpLib\\": "PhpAmqpLib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "LGPL-2.1-or-later" ], - "description": "Programmatically filter and normalize data and files", + "authors": [ + { + "name": "Alvaro Videla", + "role": "Original Maintainer" + }, + { + "name": "John Kelly", + "email": "johnmkelly86@gmail.com", + "role": "Maintainer" + }, + { + "name": "Raúl Araya", + "email": "nubeiro@gmail.com", + "role": "Maintainer" + }, + { + "name": "Luke Bakken", + "email": "luke@bakken.io", + "role": "Maintainer" + } + ], + "description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.", + "homepage": "https://github.com/php-amqplib/php-amqplib/", "keywords": [ - "ZendFramework", - "filter", - "zf" + "message", + "queue", + "rabbitmq" ], - "time": "2019-08-19T07:08:04+00:00" + "time": "2019-10-10T13:23:40+00:00" }, { - "name": "zendframework/zend-form", - "version": "2.14.3", + "name": "phpseclib/mcrypt_compat", + "version": "1.0.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-form.git", - "reference": "0b1616c59b1f3df194284e26f98c81ad0c377871" + "url": "https://github.com/phpseclib/mcrypt_compat.git", + "reference": "f74c7b1897b62f08f268184b8bb98d9d9ab723b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/0b1616c59b1f3df194284e26f98c81ad0c377871", - "reference": "0b1616c59b1f3df194284e26f98c81ad0c377871", + "url": "https://api.github.com/repos/phpseclib/mcrypt_compat/zipball/f74c7b1897b62f08f268184b8bb98d9d9ab723b0", + "reference": "f74c7b1897b62f08f268184b8bb98d9d9ab723b0", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-stdlib": "^3.2.1" + "php": ">=5.3.3", + "phpseclib/phpseclib": ">=2.0.11 <3.0.0" }, "require-dev": { - "doctrine/annotations": "~1.0", - "phpunit/phpunit": "^5.7.23 || ^6.5.3", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.2", - "zendframework/zendservice-recaptcha": "^3.0.0" + "phpunit/phpunit": "^4.8.35|^5.7|^6.0" }, "suggest": { - "zendframework/zend-captcha": "^2.7.1, required for using CAPTCHA form elements", - "zendframework/zend-code": "^2.6 || ^3.0, required to use zend-form annotations support", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, reuired for zend-form annotations support", - "zendframework/zend-i18n": "^2.6, required when using zend-form view helpers", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", - "zendframework/zend-view": "^2.6.2, required for using the zend-form view helpers", - "zendframework/zendservice-recaptcha": "in order to use the ReCaptcha form element" + "ext-openssl": "Will enable faster cryptographic operations" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.14.x-dev", - "dev-develop": "2.15.x-dev" - }, - "zf": { - "component": "Zend\\Form", - "config-provider": "Zend\\Form\\ConfigProvider" - } - }, "autoload": { - "psr-4": { - "Zend\\Form\\": "src/" - }, "files": [ - "autoload/formElementManagerPolyfill.php" + "lib/mcrypt.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Validate and display simple and complex forms, casting forms to business objects and vice versa", + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "homepage": "http://phpseclib.sourceforge.net" + } + ], + "description": "PHP 7.1 polyfill for the mcrypt extension from PHP <= 7.0", "keywords": [ - "ZendFramework", - "form", - "zf" + "cryptograpy", + "encryption", + "mcrypt" ], - "time": "2019-10-04T10:46:36+00:00" + "time": "2018-08-22T03:11:43+00:00" }, { - "name": "zendframework/zend-http", - "version": "2.10.0", + "name": "phpseclib/phpseclib", + "version": "2.0.23", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-http.git", - "reference": "4b4983178693a8fdda53b0bbee58552e2d2b1ac0" + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/4b4983178693a8fdda53b0bbee58552e2d2b1ac0", - "reference": "4b4983178693a8fdda53b0bbee58552e2d2b1ac0", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c78eb5058d5bb1a183133c36d4ba5b6675dfa099", + "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-loader": "^2.5.1", - "zendframework/zend-stdlib": "^3.2.1", - "zendframework/zend-uri": "^2.5.2", - "zendframework/zend-validator": "^2.10.1" + "php": ">=5.3.3" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^3.1 || ^2.6" + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "sami/sami": "~2.0", + "squizlabs/php_codesniffer": "~2.0" }, "suggest": { - "paragonie/certainty": "For automated management of cacert.pem" + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" - } - }, "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], "psr-4": { - "Zend\\Http\\": "src/" + "phpseclib\\": "phpseclib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", "keywords": [ - "ZendFramework", - "http", - "http client", - "zend", - "zf" + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" ], - "time": "2019-02-19T18:58:14+00:00" + "time": "2019-09-17T03:41:22+00:00" }, { - "name": "zendframework/zend-hydrator", - "version": "2.4.2", + "name": "psr/container", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-hydrator.git", - "reference": "2bfc6845019e7b6d38b0ab5e55190244dc510285" + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-hydrator/zipball/2bfc6845019e7b6d38b0ab5e55190244dc510285", - "reference": "2bfc6845019e7b6d38b0ab5e55190244dc510285", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-inputfilter": "^2.6", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" - }, - "suggest": { - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", - "zendframework/zend-filter": "^2.6, to support naming strategy hydrator usage", - "zendframework/zend-serializer": "^2.6.1, to use the SerializableStrategy", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-release-2.4": "2.4.x-dev" - }, - "zf": { - "component": "Zend\\Hydrator", - "config-provider": "Zend\\Hydrator\\ConfigProvider" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Hydrator\\": "src/" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Serialize objects to arrays, and vice versa", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "ZendFramework", - "hydrator", - "zf" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "time": "2019-10-04T11:17:36+00:00" + "time": "2017-02-14T16:28:37+00:00" }, { - "name": "zendframework/zend-i18n", - "version": "2.9.2", + "name": "psr/http-message", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-i18n.git", - "reference": "e17a54b3aee333ab156958f570cde630acee8b07" + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/e17a54b3aee333ab156958f570cde630acee8b07", - "reference": "e17a54b3aee333ab156958f570cde630acee8b07", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.3" - }, - "suggest": { - "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", - "zendframework/zend-filter": "You should install this package to use the provided filters", - "zendframework/zend-i18n-resources": "Translation resources", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "You should install this package to use the provided validators", - "zendframework/zend-view": "You should install this package to use the provided view helpers" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" - }, - "zf": { - "component": "Zend\\I18n", - "config-provider": "Zend\\I18n\\ConfigProvider" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Zend\\I18n\\": "src/" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Provide translations for your application, and filter and validate internationalized values", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ - "ZendFramework", - "i18n", - "zf" + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" ], - "time": "2019-09-30T12:04:37+00:00" + "time": "2016-08-06T14:39:51+00:00" }, { - "name": "zendframework/zend-inputfilter", - "version": "2.10.1", + "name": "psr/log", + "version": "1.1.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-inputfilter.git", - "reference": "1f44a2e9bc394a71638b43bc7024b572fa65410e" + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/1f44a2e9bc394a71638b43bc7024b572fa65410e", - "reference": "1f44a2e9bc394a71638b43bc7024b572fa65410e", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-filter": "^2.9.1", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.11" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.15", - "psr/http-message": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0" - }, - "suggest": { - "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" - }, - "zf": { - "component": "Zend\\InputFilter", - "config-provider": "Zend\\InputFilter\\ConfigProvider" + "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { - "Zend\\InputFilter\\": "src/" + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Normalize and validate input sets from the web, APIs, the CLI, and more, including files", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "ZendFramework", - "inputfilter", - "zf" + "log", + "psr", + "psr-3" ], - "time": "2019-08-28T19:45:32+00:00" + "time": "2019-11-01T11:05:21+00:00" }, { - "name": "zendframework/zend-json", - "version": "2.6.1", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-json.git", - "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", - "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0" + "php": ">=5.6" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.5 || ^3.0", - "zendframework/zendxml": "^1.0.2" - }, - "suggest": { - "zendframework/zend-http": "Zend\\Http component, required to use Zend\\Json\\Server", - "zendframework/zend-server": "Zend\\Server component, required to use Zend\\Json\\Server", - "zendframework/zend-stdlib": "Zend\\Stdlib component, for use with caching Zend\\Json\\Server responses", - "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" - } - }, "autoload": { - "psr-4": { - "Zend\\Json\\": "src/" - } + "files": [ + "src/getallheaders.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", - "homepage": "https://github.com/zendframework/zend-json", - "keywords": [ - "json", - "zf2" + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } ], - "time": "2016-02-04T21:20:26+00:00" + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" }, { - "name": "zendframework/zend-loader", - "version": "2.6.1", + "name": "ramsey/uuid", + "version": "3.8.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-loader.git", - "reference": "91da574d29b58547385b2298c020b257310898c6" + "url": "https://github.com/ramsey/uuid.git", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/91da574d29b58547385b2298c020b257310898c6", - "reference": "91da574d29b58547385b2298c020b257310898c6", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "paragonie/random_compat": "^1.0|^2.0|9.99.99", + "php": "^5.4 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0|^6.5", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.6.x-dev", - "dev-develop": "2.7.x-dev" + "branch-alias": { + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Loader\\": "src/" + "Ramsey\\Uuid\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Autoloading and plugin loading strategies", + "authors": [ + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + }, + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", "keywords": [ - "ZendFramework", - "loader", - "zf" + "guid", + "identifier", + "uuid" ], - "time": "2019-09-04T19:38:14+00:00" + "time": "2018-07-19T23:38:55+00:00" }, { - "name": "zendframework/zend-log", - "version": "2.11.0", + "name": "react/promise", + "version": "v2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-log.git", - "reference": "cb278772afdacb1924342248a069330977625ae6" + "url": "https://github.com/reactphp/promise.git", + "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/cb278772afdacb1924342248a069330977625ae6", - "reference": "cb278772afdacb1924342248a069330977625ae6", + "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "psr/log": "^1.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" + "php": ">=5.4.0" }, "require-dev": { - "mikey179/vfsstream": "^1.6.7", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.15", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-filter": "^2.5", - "zendframework/zend-mail": "^2.6.1", - "zendframework/zend-validator": "^2.10.1" - }, - "suggest": { - "ext-mongo": "mongo extension to use Mongo writer", - "ext-mongodb": "mongodb extension to use MongoDB writer", - "zendframework/zend-db": "Zend\\Db component to use the database log writer", - "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML log formatter", - "zendframework/zend-mail": "Zend\\Mail component to use the email log writer", - "zendframework/zend-validator": "Zend\\Validator component to block invalid log messages" + "phpunit/phpunit": "~4.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.11.x-dev", - "dev-develop": "2.12.x-dev" - }, - "zf": { - "component": "Zend\\Log", - "config-provider": "Zend\\Log\\ConfigProvider" - } - }, "autoload": { "psr-4": { - "Zend\\Log\\": "src/" - } + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Robust, composite logger with filtering, formatting, and PSR-3 support", + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "ZendFramework", - "log", - "logging", - "zf" + "promise", + "promises" ], - "time": "2019-08-23T21:28:18+00:00" + "time": "2019-01-07T21:25:54+00:00" }, { - "name": "zendframework/zend-mail", - "version": "2.10.0", + "name": "seld/jsonlint", + "version": "1.7.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mail.git", - "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8" + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", - "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", "shasum": "" }, "require": { - "ext-iconv": "*", - "php": "^5.6 || ^7.0", - "true/punycode": "^2.1", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mime": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.10.2" + "php": "^5.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-crypt": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1" - }, - "suggest": { - "zendframework/zend-crypt": "Crammd5 support in SMTP Auth", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, + "bin": [ + "bin/jsonlint" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" - }, - "zf": { - "component": "Zend\\Mail", - "config-provider": "Zend\\Mail\\ConfigProvider" - } - }, "autoload": { "psr-4": { - "Zend\\Mail\\": "src/" + "Seld\\JsonLint\\": "src/Seld/JsonLint/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", "keywords": [ - "ZendFramework", - "mail", - "zf" + "json", + "linter", + "parser", + "validator" ], - "time": "2018-06-07T13:37:07+00:00" + "time": "2019-10-24T14:27:39+00:00" }, { - "name": "zendframework/zend-math", - "version": "2.7.1", + "name": "seld/phar-utils", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-math.git", - "reference": "1abce074004dacac1a32cd54de94ad47ef960d38" + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", - "reference": "1abce074004dacac1a32cd54de94ad47ef960d38", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/7009b5139491975ef6486545a39f3e6dad5ac30a", + "reference": "7009b5139491975ef6486545a39f3e6dad5ac30a", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "ircmaxell/random-lib": "~1.1", - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-bcmath": "If using the bcmath functionality", - "ext-gmp": "If using the gmp functionality", - "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if Mcrypt extensions is unavailable" + "php": ">=5.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "2.8-dev" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Math\\": "src/" + "Seld\\PharUtils\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } ], - "homepage": "https://github.com/zendframework/zend-math", + "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ - "math", - "zf2" + "phra" ], - "time": "2018-12-04T15:34:17+00:00" + "time": "2015-10-13T18:44:15+00:00" }, { - "name": "zendframework/zend-mime", - "version": "2.7.2", + "name": "symfony/console", + "version": "v4.1.12", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mime.git", - "reference": "c91e0350be53cc9d29be15563445eec3b269d7c1" + "url": "https://github.com/symfony/console.git", + "reference": "9e87c798f67dc9fceeb4f3d57847b52d945d1a02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mime/zipball/c91e0350be53cc9d29be15563445eec3b269d7c1", - "reference": "c91e0350be53cc9d29be15563445eec3b269d7c1", + "url": "https://api.github.com/repos/symfony/console/zipball/9e87c798f67dc9fceeb4f3d57847b52d945d1a02", + "reference": "9e87c798f67dc9fceeb4f3d57847b52d945d1a02", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-mail": "^2.6" + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" }, "suggest": { - "zendframework/zend-mail": "Zend\\Mail component" + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7.x-dev", - "dev-develop": "2.8.x-dev" + "dev-master": "4.1-dev" } }, "autoload": { "psr-4": { - "Zend\\Mime\\": "src/" - } + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Create and parse MIME messages and parts", - "keywords": [ - "ZendFramework", - "mime", - "zf" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "time": "2019-10-16T19:30:37+00:00" + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2019-01-25T14:34:37+00:00" }, { - "name": "zendframework/zend-modulemanager", - "version": "2.8.4", + "name": "symfony/css-selector", + "version": "v4.3.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-modulemanager.git", - "reference": "b2596d24b9a4e36a3cd114d35d3ad0918db9a243" + "url": "https://github.com/symfony/css-selector.git", + "reference": "f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/b2596d24b9a4e36a3cd114d35d3ad0918db9a243", - "reference": "b2596d24b9a4e36a3cd114d35d3ad0918db9a243", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9", + "reference": "f4b3ff6a549d9ed28b2b0ecd1781bf67cf220ee9", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-config": "^3.1 || ^2.6", - "zendframework/zend-eventmanager": "^3.2 || ^2.6.3", - "zendframework/zend-stdlib": "^3.1 || ^2.7" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-console": "^2.6", - "zendframework/zend-di": "^2.6", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mvc": "^3.0 || ^2.7", - "zendframework/zend-servicemanager": "^3.0.3 || ^2.7.5" - }, - "suggest": { - "zendframework/zend-console": "Zend\\Console component", - "zendframework/zend-loader": "Zend\\Loader component if you are not using Composer autoloading for your modules", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Zend\\ModuleManager\\": "src/" - } + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Modular application system for zend-mvc applications", - "keywords": [ - "ZendFramework", - "modulemanager", - "zf" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "time": "2019-10-28T13:29:38+00:00" + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2019-10-02T08:36:26+00:00" }, { - "name": "zendframework/zend-mvc", - "version": "2.7.15", + "name": "symfony/event-dispatcher", + "version": "v4.3.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mvc.git", - "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "0df002fd4f500392eabd243c2947061a50937287" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", - "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0df002fd4f500392eabd243c2947061a50937287", + "reference": "0df002fd4f500392eabd243c2947061a50937287", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.1", - "php": "^5.5 || ^7.0", - "zendframework/zend-console": "^2.7", - "zendframework/zend-eventmanager": "^2.6.4 || ^3.0", - "zendframework/zend-form": "^2.11", - "zendframework/zend-hydrator": "^1.1 || ^2.4", - "zendframework/zend-psr7bridge": "^0.2", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7.5 || ^3.0" + "php": "^7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" }, - "replace": { - "zendframework/zend-router": "^2.0" + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "^4.8.36", - "sebastian/comparator": "^1.2.4", - "sebastian/version": "^1.0.4", - "zendframework/zend-authentication": "^2.6", - "zendframework/zend-cache": "^2.8", - "zendframework/zend-di": "^2.6", - "zendframework/zend-filter": "^2.8", - "zendframework/zend-http": "^2.8", - "zendframework/zend-i18n": "^2.8", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.3", - "zendframework/zend-modulemanager": "^2.8", - "zendframework/zend-serializer": "^2.8", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.7", - "zendframework/zend-uri": "^2.6", - "zendframework/zend-validator": "^2.10", - "zendframework/zend-view": "^2.9" + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "^3.4|^4.0", + "symfony/service-contracts": "^1.1", + "symfony/stopwatch": "~3.4|~4.0" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component for Identity plugin", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-di": "Zend\\Di component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component for translatable segments", - "zendframework/zend-inputfilter": "Zend\\Inputfilter component", - "zendframework/zend-json": "Zend\\Json component", - "zendframework/zend-log": "Zend\\Log component", - "zendframework/zend-modulemanager": "Zend\\ModuleManager component", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager-di": "^1.0.1, if using zend-servicemanager v3 and requiring the zend-di integration", - "zendframework/zend-session": "Zend\\Session component for FlashMessenger, PRG, and FPRG plugins", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-uri": "Zend\\Uri component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zend-view": "Zend\\View component" + "symfony/dependency-injection": "", + "symfony/http-kernel": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "3.0-dev" + "dev-master": "4.3-dev" } }, "autoload": { - "files": [ - "src/autoload.php" - ], "psr-4": { - "Zend\\Mvc\\": "src/" - } + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "homepage": "https://github.com/zendframework/zend-mvc", - "keywords": [ - "mvc", - "zf2" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "time": "2018-05-03T13:13:41+00:00" + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2019-11-03T09:04:05+00:00" }, { - "name": "zendframework/zend-psr7bridge", - "version": "0.2.2", + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.7", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-psr7bridge.git", - "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-psr7bridge/zipball/86c0b53b0c6381391c4add4a93a56e51d5c74605", - "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", "shasum": "" }, "require": { - "php": ">=5.5", - "psr/http-message": "^1.0", - "zendframework/zend-diactoros": "^1.1", - "zendframework/zend-http": "^2.5" + "php": "^7.1.3" }, - "require-dev": { - "phpunit/phpunit": "^4.7", - "squizlabs/php_codesniffer": "^2.3" + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev", - "dev-develop": "1.1-dev" + "dev-master": "1.1-dev" } }, "autoload": { "psr-4": { - "Zend\\Psr7Bridge\\": "src/" + "Symfony\\Contracts\\EventDispatcher\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "description": "PSR-7 <-> Zend\\Http bridge", - "homepage": "https://github.com/zendframework/zend-psr7bridge", + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", "keywords": [ - "http", - "psr", - "psr-7" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], - "time": "2016-05-10T21:44:39+00:00" + "time": "2019-09-17T09:54:03+00:00" }, { - "name": "zendframework/zend-serializer", - "version": "2.9.1", + "name": "symfony/filesystem", + "version": "v4.3.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-serializer.git", - "reference": "6fb7ae016cfdf0cfcdfa2b989e6a65f351170e21" + "url": "https://github.com/symfony/filesystem.git", + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/6fb7ae016cfdf0cfcdfa2b989e6a65f351170e21", - "reference": "6fb7ae016cfdf0cfcdfa2b989e6a65f351170e21", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/9abbb7ef96a51f4d7e69627bc6f63307994e4263", + "reference": "9abbb7ef96a51f4d7e69627bc6f63307994e4263", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-json": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-math": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" - }, - "suggest": { - "zendframework/zend-math": "(^2.6 || ^3.0) To support Python Pickle serialization", - "zendframework/zend-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" - }, - "zf": { - "component": "Zend\\Serializer", - "config-provider": "Zend\\Serializer\\ConfigProvider" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Zend\\Serializer\\": "src/" - } + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Serialize and deserialize PHP structures to a variety of representations", - "keywords": [ - "ZendFramework", - "serializer", - "zf" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "time": "2019-10-19T08:06:30+00:00" + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2019-08-20T14:07:54+00:00" }, { - "name": "zendframework/zend-server", - "version": "2.8.1", + "name": "symfony/finder", + "version": "v4.3.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-server.git", - "reference": "d80c44700ebb92191dd9a3005316a6ab6637c0d1" + "url": "https://github.com/symfony/finder.git", + "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-server/zipball/d80c44700ebb92191dd9a3005316a6ab6637c0d1", - "reference": "d80c44700ebb92191dd9a3005316a6ab6637c0d1", + "url": "https://api.github.com/repos/symfony/finder/zipball/72a068f77e317ae77c0a0495236ad292cfb5ce6f", + "reference": "72a068f77e317ae77c0a0495236ad292cfb5ce6f", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-code": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.5 || ^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Zend\\Server\\": "src/" - } + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Create Reflection-based RPC servers", - "keywords": [ - "ZendFramework", - "server", - "zf" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "time": "2019-10-16T18:27:05+00:00" + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2019-10-30T12:53:54+00:00" }, { - "name": "zendframework/zend-servicemanager", - "version": "2.7.11", + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-servicemanager.git", - "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", - "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { - "container-interop/container-interop": "~1.0", - "php": "^5.5 || ^7.0" - }, - "require-dev": { - "athletic/athletic": "dev-master", - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-di": "~2.5", - "zendframework/zend-mvc": "~2.5" + "php": ">=5.3.3" }, "suggest": { - "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services", - "zendframework/zend-di": "Zend\\Di component" + "ext-ctype": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "3.0-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Zend\\ServiceManager\\": "src/" - } + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "homepage": "https://github.com/zendframework/zend-servicemanager", + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", "keywords": [ - "servicemanager", - "zf2" + "compatibility", + "ctype", + "polyfill", + "portable" ], - "time": "2018-06-22T14:49:54+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "zendframework/zend-session", - "version": "2.9.1", + "name": "symfony/polyfill-mbstring", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-session.git", - "reference": "c289c4d733ec23a389e25c7c451f4d062088511f" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-session/zipball/c289c4d733ec23a389e25c7c451f4d062088511f", - "reference": "c289c4d733ec23a389e25c7c451f4d062088511f", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-stdlib": "^3.2.1" - }, - "require-dev": { - "container-interop/container-interop": "^1.1", - "mongodb/mongodb": "^1.0.1", - "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.7", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6" + "php": ">=5.3.3" }, "suggest": { - "mongodb/mongodb": "If you want to use the MongoDB session save handler", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-db": "Zend\\Db component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "Zend\\Validator component" + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" - }, - "zf": { - "component": "Zend\\Session", - "config-provider": "Zend\\Session\\ConfigProvider" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Zend\\Session\\": "src/" - } + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Object-oriented interface to PHP sessions and storage", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", "keywords": [ - "ZendFramework", - "session", - "zf" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], - "time": "2019-10-28T19:40:43+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "zendframework/zend-soap", - "version": "2.8.0", + "name": "symfony/process", + "version": "v4.3.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-soap.git", - "reference": "8762d79efa220d82529c43ce08d70554146be645" + "url": "https://github.com/symfony/process.git", + "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/8762d79efa220d82529c43ce08d70554146be645", - "reference": "8762d79efa220d82529c43ce08d70554146be645", + "url": "https://api.github.com/repos/symfony/process/zipball/3b2e0cb029afbb0395034509291f21191d1a4db0", + "reference": "3b2e0cb029afbb0395034509291f21191d1a4db0", "shasum": "" }, "require": { - "ext-soap": "*", - "php": "^5.6 || ^7.0", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-uri": "^2.5.2" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-http": "^2.5.4" - }, - "suggest": { - "zendframework/zend-http": "Zend\\Http component" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7.x-dev", - "dev-develop": "2.8.x-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Zend\\Soap\\": "src/" - } + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "homepage": "https://github.com/zendframework/zend-soap", - "keywords": [ - "soap", - "zf2" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "time": "2019-04-30T16:45:35+00:00" + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2019-10-28T17:07:32+00:00" }, { - "name": "zendframework/zend-stdlib", - "version": "3.2.1", + "name": "tedivm/jshrink", + "version": "v1.3.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "66536006722aff9e62d1b331025089b7ec71c065" + "url": "https://github.com/tedious/JShrink.git", + "reference": "566e0c731ba4e372be2de429ef7d54f4faf4477a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/66536006722aff9e62d1b331025089b7ec71c065", - "reference": "66536006722aff9e62d1b331025089b7ec71c065", + "url": "https://api.github.com/repos/tedious/JShrink/zipball/566e0c731ba4e372be2de429ef7d54f4faf4477a", + "reference": "566e0c731ba4e372be2de429ef7d54f4faf4477a", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^5.6|^7.0" }, "require-dev": { - "phpbench/phpbench": "^0.13", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0" + "friendsofphp/php-cs-fixer": "^2.8", + "php-coveralls/php-coveralls": "^1.1.0", + "phpunit/phpunit": "^6" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2.x-dev", - "dev-develop": "3.3.x-dev" - } - }, "autoload": { - "psr-4": { - "Zend\\Stdlib\\": "src/" + "psr-0": { + "JShrink": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "SPL extensions, array utilities, error handlers, and more", + "authors": [ + { + "name": "Robert Hafner", + "email": "tedivm@tedivm.com" + } + ], + "description": "Javascript Minifier built in PHP", + "homepage": "http://github.com/tedious/JShrink", "keywords": [ - "ZendFramework", - "stdlib", - "zf" + "javascript", + "minifier" ], - "time": "2018-08-28T21:34:05+00:00" + "time": "2019-06-28T18:11:46+00:00" }, { - "name": "zendframework/zend-text", - "version": "2.7.1", + "name": "true/punycode", + "version": "v2.1.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-text.git", - "reference": "41e32dafa4015e160e2f95a7039554385c71624d" + "url": "https://github.com/true/php-punycode.git", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-text/zipball/41e32dafa4015e160e2f95a7039554385c71624d", - "reference": "41e32dafa4015e160e2f95a7039554385c71624d", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6" + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7.x-dev", - "dev-develop": "2.8.x-dev" - } - }, "autoload": { "psr-4": { - "Zend\\Text\\": "src/" + "TrueBV\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Create FIGlets and text-based tables", + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "homepage": "https://github.com/true/php-punycode", "keywords": [ - "ZendFramework", - "text", - "zf" + "idna", + "punycode" ], - "time": "2019-10-16T20:36:27+00:00" + "time": "2016-11-16T10:37:54+00:00" }, { - "name": "zendframework/zend-uri", - "version": "2.7.1", + "name": "tubalmartin/cssmin", + "version": "v4.1.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-uri.git", - "reference": "bfc4a5b9a309711e968d7c72afae4ac50c650083" + "url": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port.git", + "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/bfc4a5b9a309711e968d7c72afae4ac50c650083", - "reference": "bfc4a5b9a309711e968d7c72afae4ac50c650083", + "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/3cbf557f4079d83a06f9c3ff9b957c022d7805cf", + "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-validator": "^2.10" + "ext-pcre": "*", + "php": ">=5.3.2" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "cogpowered/finediff": "0.3.*", + "phpunit/phpunit": "4.8.*" }, + "bin": [ + "cssmin" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7.x-dev", - "dev-develop": "2.8.x-dev" - } - }, "autoload": { "psr-4": { - "Zend\\Uri\\": "src/" + "tubalmartin\\CssMin\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "A component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "authors": [ + { + "name": "Túbal Martín", + "homepage": "http://tubalmartin.me/" + } + ], + "description": "A PHP port of the YUI CSS compressor", + "homepage": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port", "keywords": [ - "ZendFramework", - "uri", - "zf" + "compress", + "compressor", + "css", + "cssmin", + "minify", + "yui" ], - "time": "2019-10-07T13:35:33+00:00" + "time": "2018-01-15T15:26:51+00:00" }, { - "name": "zendframework/zend-validator", - "version": "2.12.2", + "name": "webonyx/graphql-php", + "version": "v0.13.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-validator.git", - "reference": "fd24920c2afcf2a70d11f67c3457f8f509453a62" + "url": "https://github.com/webonyx/graphql-php.git", + "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/fd24920c2afcf2a70d11f67c3457f8f509453a62", - "reference": "fd24920c2afcf2a70d11f67c3457f8f509453a62", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/6829ae58f4c59121df1f86915fb9917a2ec595e8", + "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.1", - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^3.2.1" + "ext-json": "*", + "ext-mbstring": "*", + "php": "^7.1||^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "doctrine/coding-standard": "^6.0", + "phpbench/phpbench": "^0.14.0", + "phpstan/phpstan": "^0.11.4", + "phpstan/phpstan-phpunit": "^0.11.0", + "phpstan/phpstan-strict-rules": "^0.11.0", + "phpunit/phpcov": "^5.0", + "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-db": "^2.7", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-math": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8", - "zendframework/zend-uri": "^2.5" + "react/promise": "2.*" }, "suggest": { - "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", - "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", - "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", - "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", - "zendframework/zend-i18n-resources": "Translations of validator messages", - "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", - "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", - "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + "psr/http-message": "To use standard GraphQL server", + "react/promise": "To leverage async resolving on React PHP platform" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.12.x-dev", - "dev-develop": "2.13.x-dev" - }, - "zf": { - "component": "Zend\\Validator", - "config-provider": "Zend\\Validator\\ConfigProvider" - } - }, "autoload": { "psr-4": { - "Zend\\Validator\\": "src/" + "GraphQL\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "description": "A PHP port of GraphQL reference implementation", + "homepage": "https://github.com/webonyx/graphql-php", "keywords": [ - "ZendFramework", - "validator", - "zf" + "api", + "graphql" ], - "time": "2019-10-29T08:33:25+00:00" + "time": "2019-08-25T10:32:47+00:00" }, { - "name": "zendframework/zend-view", - "version": "2.11.3", + "name": "wikimedia/less.php", + "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-view.git", - "reference": "e766457bd6ce13c5354e443bb949511b6904d7f5" + "url": "https://github.com/wikimedia/less.php.git", + "reference": "e238ad228d74b6ffd38209c799b34e9826909266" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/e766457bd6ce13c5354e443bb949511b6904d7f5", - "reference": "e766457bd6ce13c5354e443bb949511b6904d7f5", + "url": "https://api.github.com/repos/wikimedia/less.php/zipball/e238ad228d74b6ffd38209c799b34e9826909266", + "reference": "e238ad228d74b6ffd38209c799b34e9826909266", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-json": "^2.6.1 || ^3.0", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.15 || ^6.0.8", - "zendframework/zend-authentication": "^2.5", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-console": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-feed": "^2.7", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-log": "^2.7", - "zendframework/zend-modulemanager": "^2.7.1", - "zendframework/zend-mvc": "^2.7.14 || ^3.0", - "zendframework/zend-navigation": "^2.5", - "zendframework/zend-paginator": "^2.5", - "zendframework/zend-permissions-acl": "^2.6", - "zendframework/zend-router": "^3.0.1", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-uri": "^2.5" + "php": ">=7.2.9" }, - "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component", - "zendframework/zend-escaper": "Zend\\Escaper component", - "zendframework/zend-feed": "Zend\\Feed component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", - "zendframework/zend-navigation": "Zend\\Navigation component", - "zendframework/zend-paginator": "Zend\\Paginator component", - "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component" + "require-dev": { + "phpunit/phpunit": "7.5.14" }, "bin": [ - "bin/templatemap_generator.php" + "bin/lessc" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.11.x-dev", - "dev-develop": "2.12.x-dev" - } - }, "autoload": { - "psr-4": { - "Zend\\View\\": "src/" - } + "psr-0": { + "Less": "lib/" + }, + "classmap": [ + "lessc.inc.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], - "description": "Flexible view layer supporting and providing multiple view layers, helpers, and more", + "authors": [ + { + "name": "Josh Schmidt", + "homepage": "https://github.com/oyejorge" + }, + { + "name": "Matt Agar", + "homepage": "https://github.com/agar" + }, + { + "name": "Martin Jantošovič", + "homepage": "https://github.com/Mordred" + } + ], + "description": "PHP port of the Javascript version of LESS http://lesscss.org (Originally maintained by Josh Schmidt)", "keywords": [ - "ZendFramework", - "view", - "zf" + "css", + "less", + "less.js", + "lesscss", + "php", + "stylesheet" ], - "time": "2019-10-11T21:10:04+00:00" + "time": "2019-11-06T18:30:11+00:00" } ], "packages-dev": [ @@ -6486,6 +6734,7 @@ "selenium", "webdriver" ], + "abandoned": "php-webdriver/webdriver", "time": "2019-06-13T08:02:18+00:00" }, { diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php index 60dd6b57bda86..2d7fbae640ef1 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php @@ -10,7 +10,7 @@ use Magento\TestFramework\Authentication\Rest\OauthClient; use Magento\TestFramework\Helper\Bootstrap; use OAuth\Common\Consumer\Credentials; -use Zend\Stdlib\Exception\LogicException; +use Laminas\Stdlib\Exception\LogicException; use Magento\Integration\Model\Integration; class OauthHelper @@ -170,7 +170,7 @@ protected static function _rmRecursive($dir, $doSaveRoot = false) * * @param array $resources * @return \Magento\Integration\Model\Integration - * @throws \Zend\Stdlib\Exception\LogicException + * @throws \Laminas\Stdlib\Exception\LogicException */ protected static function _createIntegration($resources) { diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php index 8453edb071b3e..01c48c8410f5a 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/Webapi/Adapter/Soap.php @@ -19,7 +19,7 @@ class Soap implements \Magento\TestFramework\TestCase\Webapi\AdapterInterface /** * SOAP client initialized with different WSDLs. * - * @var \Zend\Soap\Client[] + * @var \Laminas\Soap\Client[] */ protected $_soapClients = ['custom' => [], 'default' => []]; @@ -67,7 +67,7 @@ public function call($serviceInfo, $arguments = [], $storeCode = null, $integrat * * @param string $serviceInfo PHP service interface name, should include version if present * @param string|null $storeCode - * @return \Zend\Soap\Client + * @return \Laminas\Soap\Client */ protected function _getSoapClient($serviceInfo, $storeCode = null) { @@ -75,7 +75,7 @@ protected function _getSoapClient($serviceInfo, $storeCode = null) [$this->_getSoapServiceName($serviceInfo) . $this->_getSoapServiceVersion($serviceInfo)], $storeCode ); - /** @var \Zend\Soap\Client $soapClient */ + /** @var \Laminas\Soap\Client $soapClient */ $soapClient = null; if (isset($serviceInfo['soap']['token'])) { $token = $serviceInfo['soap']['token']; @@ -104,7 +104,7 @@ protected function _getSoapClient($serviceInfo, $storeCode = null) * * @param string $wsdlUrl * @param string $token Authentication token - * @return \Zend\Soap\Client + * @return \Laminas\Soap\Client */ public function instantiateSoapClient($wsdlUrl, $token = null) { @@ -113,7 +113,7 @@ public function instantiateSoapClient($wsdlUrl, $token = null) : \Magento\TestFramework\Authentication\OauthHelper::getApiAccessCredentials()['key']; $opts = ['http' => ['header' => "Authorization: Bearer " . $accessCredentials]]; $context = stream_context_create($opts); - $soapClient = new \Zend\Soap\Client($wsdlUrl); + $soapClient = new \Laminas\Soap\Client($wsdlUrl); $soapClient->setSoapVersion(SOAP_1_2); $soapClient->setStreamContext($context); if (TESTS_XDEBUG_ENABLED) { diff --git a/dev/tests/integration/framework/Magento/TestFramework/Request.php b/dev/tests/integration/framework/Magento/TestFramework/Request.php index 756badb0f333f..ede2f5a54bf05 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Request.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Request.php @@ -5,7 +5,7 @@ */ namespace Magento\TestFramework; -use \Zend\Stdlib\ParametersInterface; +use \Laminas\Stdlib\ParametersInterface; /** * HTTP request implementation that is used instead core one for testing diff --git a/dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/RequestTest.php b/dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/RequestTest.php index a5c9a281c3ffd..6b853aebd41fa 100644 --- a/dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/RequestTest.php +++ b/dev/tests/integration/framework/tests/unit/testsuite/Magento/Test/RequestTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Test; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; class RequestTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Backend/App/Request/BackendValidatorTest.php b/dev/tests/integration/testsuite/Magento/Backend/App/Request/BackendValidatorTest.php index 21ffddf851ac4..68a9bc4d21656 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/App/Request/BackendValidatorTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/App/Request/BackendValidatorTest.php @@ -27,7 +27,7 @@ use Magento\TestFramework\Bootstrap as TestBootstrap; use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\App\Response\Http as HttpResponse; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; use Magento\Backend\Model\UrlInterface as BackendUrl; use Magento\Framework\App\Response\HttpFactory as HttpResponseFactory; diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Controller/Cards/DeleteActionTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Cards/DeleteActionTest.php index eae831743f9cd..9257130cea121 100644 --- a/dev/tests/integration/testsuite/Magento/Braintree/Controller/Cards/DeleteActionTest.php +++ b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Cards/DeleteActionTest.php @@ -9,7 +9,7 @@ use Magento\Framework\Data\Form\FormKey; use Magento\TestFramework\TestCase\AbstractController; use Magento\Vault\Model\CustomerTokenManagement; -use Zend\Http\Request; +use Laminas\Http\Request; /** * Class DeleteActionTest diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php index 32b7df03f922d..5bebfed06f322 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php @@ -10,7 +10,7 @@ use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; use Magento\TestFramework\TestCase\AbstractController; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; /** * Test cases for catalog advanced search using mysql search engine. diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index df4acf3acca91..e12b3c4ed85a3 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -29,7 +29,7 @@ use Magento\TestFramework\Response; use Magento\Theme\Controller\Result\MessagePlugin; use PHPUnit\Framework\Constraint\StringContains; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -832,14 +832,14 @@ public function testConfirmationEmailWithSpecialCharacters(): void $message = $this->transportBuilderMock->getSentMessage(); $rawMessage = $message->getRawMessage(); - /** @var \Zend\Mime\Part $messageBodyPart */ + /** @var \Laminas\Mime\Part $messageBodyPart */ $messageBodyParts = $message->getBody()->getParts(); $messageBodyPart = reset($messageBodyParts); $messageEncoding = $messageBodyPart->getCharset(); $name = 'John Smith'; if (strtoupper($messageEncoding) !== 'ASCII') { - $name = \Zend\Mail\Header\HeaderWrap::mimeEncodeValue($name, $messageEncoding); + $name = \Laminas\Mail\Header\HeaderWrap::mimeEncodeValue($name, $messageEncoding); } $nameEmail = sprintf('%s <%s>', $name, $email); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index d76c520ade3b1..4dca86223ed96 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -398,7 +398,7 @@ public function testDeleteAction() $this->getRequest()->setParam('id', 1); $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()); - $this->getRequest()->setMethod(\Zend\Http\Request::METHOD_POST); + $this->getRequest()->setMethod(\Laminas\Http\Request::METHOD_POST); $this->dispatch('backend/customer/index/delete'); $this->assertRedirect($this->stringContains('customer/index')); @@ -416,7 +416,7 @@ public function testNotExistingCustomerDeleteAction() $this->getRequest()->setParam('id', 2); $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()); - $this->getRequest()->setMethod(\Zend\Http\Request::METHOD_POST); + $this->getRequest()->setMethod(\Laminas\Http\Request::METHOD_POST); $this->dispatch('backend/customer/index/delete'); $this->assertRedirect($this->stringContains('customer/index')); diff --git a/dev/tests/integration/testsuite/Magento/Developer/Helper/DataTest.php b/dev/tests/integration/testsuite/Magento/Developer/Helper/DataTest.php index f62773a68c144..e2c98eb4aef5f 100644 --- a/dev/tests/integration/testsuite/Magento/Developer/Helper/DataTest.php +++ b/dev/tests/integration/testsuite/Magento/Developer/Helper/DataTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Developer\Helper; -use \Zend\Stdlib\Parameters; +use \Laminas\Stdlib\Parameters; class DataTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/AreaTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/AreaTest.php index 64ff52ff4ec4d..c18b1ecfa4868 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/AreaTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/AreaTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\App; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; class AreaTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/CreatePdfFileTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/CreatePdfFileTest.php index 9ac778da91f29..d7b492bf5153c 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/CreatePdfFileTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Filesystem/CreatePdfFileTest.php @@ -12,7 +12,7 @@ use Magento\Framework\App\Response\Http\FileFactory; use Magento\Framework\Filesystem; use Magento\TestFramework\Helper\Bootstrap; -use Zend\Http\Header\ContentType; +use Laminas\Http\Header\ContentType; /** * Class CreatePdfFileTest diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Request/CsrfValidatorTest.php b/dev/tests/integration/testsuite/Magento/Framework/App/Request/CsrfValidatorTest.php index 9246be52f41bf..7e5edf361e03a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Request/CsrfValidatorTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Request/CsrfValidatorTest.php @@ -19,7 +19,7 @@ use PHPUnit\Framework\TestCase; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\App\Request\Http as HttpRequest; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; use Magento\Framework\App\Response\Http as HttpResponse; use Magento\Framework\App\Response\HttpFactory as HttpResponseFactory; diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php b/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php index 8183a5878ba85..98f56984a750d 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php @@ -36,7 +36,7 @@ protected function assertHeaderPresent($name, $value) $this->interceptedResponse->sendResponse(); $header = $this->interceptedResponse->getHeader($name); - $this->assertTrue(is_subclass_of($header, \Zend\Http\Header\HeaderInterface::class, false)); + $this->assertTrue(is_subclass_of($header, \Laminas\Http\Header\HeaderInterface::class, false)); $this->assertSame( $value, $header->getFieldValue() diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/ParentClassWithNamespace.php b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/ParentClassWithNamespace.php index 01e63126e5cde..27792e092d6b8 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/ParentClassWithNamespace.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/ParentClassWithNamespace.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\Code\GeneratorTest; -use Zend\Code\Generator\DocBlockGenerator; +use Laminas\Code\Generator\DocBlockGenerator; /** * phpcs:ignoreFile @@ -15,7 +15,7 @@ class ParentClassWithNamespace /** * Public parent method * - * @param \Zend\Code\Generator\DocBlockGenerator $docBlockGenerator + * @param \Laminas\Code\Generator\DocBlockGenerator $docBlockGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -35,7 +35,7 @@ public function publicParentMethod( /** * Protected parent method * - * @param \Zend\Code\Generator\DocBlockGenerator $docBlockGenerator + * @param \Laminas\Code\Generator\DocBlockGenerator $docBlockGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -55,7 +55,7 @@ protected function _protectedParentMethod( /** * Private parent method * - * @param \Zend\Code\Generator\DocBlockGenerator $docBlockGenerator + * @param \Laminas\Code\Generator\DocBlockGenerator $docBlockGenerator * @param string $param1 * @param string $param2 * @param string $param3 diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php index b9fc351ff64e6..0bc86f36a6357 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\Code\GeneratorTest; -use Zend\Code\Generator\ClassGenerator; +use Laminas\Code\Generator\ClassGenerator; /** * phpcs:ignoreFile @@ -28,7 +28,7 @@ public function __construct($param1 = '', $param2 = '\\', $param3 = '\'') /** * Public child method * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -49,7 +49,7 @@ public function publicChildMethod( /** * Public child method with reference * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param string $param1 * @param array $array * @@ -62,7 +62,7 @@ public function publicMethodWithReference(ClassGenerator &$classGenerator, &$par /** * Protected child method * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -80,7 +80,7 @@ protected function _protectedChildMethod( /** * Private child method * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param string $param1 * @param string $param2 * @param string $param3 diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample index 83191f2d1b099..74c1522fa41f0 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample @@ -18,7 +18,7 @@ class Interceptor extends \Magento\Framework\Code\GeneratorTest\SourceClassWithN /** * {@inheritdoc} */ - public function publicChildMethod(\Zend\Code\Generator\ClassGenerator $classGenerator, $param1 = '', $param2 = '\\', $param3 = '\'', array $array = []) + public function publicChildMethod(\Laminas\Code\Generator\ClassGenerator $classGenerator, $param1 = '', $param2 = '\\', $param3 = '\'', array $array = []) { $pluginInfo = $this->pluginList->getNext($this->subjectType, 'publicChildMethod'); if (!$pluginInfo) { @@ -31,7 +31,7 @@ class Interceptor extends \Magento\Framework\Code\GeneratorTest\SourceClassWithN /** * {@inheritdoc} */ - public function publicMethodWithReference(\Zend\Code\Generator\ClassGenerator &$classGenerator, &$param1, array &$array) + public function publicMethodWithReference(\Laminas\Code\Generator\ClassGenerator &$classGenerator, &$param1, array &$array) { $pluginInfo = $this->pluginList->getNext($this->subjectType, 'publicMethodWithReference'); if (!$pluginInfo) { @@ -96,7 +96,7 @@ class Interceptor extends \Magento\Framework\Code\GeneratorTest\SourceClassWithN /** * {@inheritdoc} */ - public function publicParentMethod(\Zend\Code\Generator\DocBlockGenerator $docBlockGenerator, $param1 = '', $param2 = '\\', $param3 = '\'', array $array = []) + public function publicParentMethod(\Laminas\Code\Generator\DocBlockGenerator $docBlockGenerator, $param1 = '', $param2 = '\\', $param3 = '\'', array $array = []) { $pluginInfo = $this->pluginList->getNext($this->subjectType, 'publicParentMethod'); if (!$pluginInfo) { diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample index 359854f2d481c..42f766c786c0b 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample @@ -93,7 +93,7 @@ class Proxy extends \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespa /** * {@inheritdoc} */ - public function publicChildMethod(\Zend\Code\Generator\ClassGenerator $classGenerator, $param1 = '', $param2 = '\\', $param3 = '\'', array $array = []) + public function publicChildMethod(\Laminas\Code\Generator\ClassGenerator $classGenerator, $param1 = '', $param2 = '\\', $param3 = '\'', array $array = []) { return $this->_getSubject()->publicChildMethod($classGenerator, $param1, $param2, $param3, $array); } @@ -101,7 +101,7 @@ class Proxy extends \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespa /** * {@inheritdoc} */ - public function publicMethodWithReference(\Zend\Code\Generator\ClassGenerator &$classGenerator, &$param1, array &$array) + public function publicMethodWithReference(\Laminas\Code\Generator\ClassGenerator &$classGenerator, &$param1, array &$array) { return $this->_getSubject()->publicMethodWithReference($classGenerator, $param1, $array); } @@ -141,7 +141,7 @@ class Proxy extends \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespa /** * {@inheritdoc} */ - public function publicParentMethod(\Zend\Code\Generator\DocBlockGenerator $docBlockGenerator, $param1 = '', $param2 = '\\', $param3 = '\'', array $array = []) + public function publicParentMethod(\Laminas\Code\Generator\DocBlockGenerator $docBlockGenerator, $param1 = '', $param2 = '\\', $param3 = '\'', array $array = []) { return $this->_getSubject()->publicParentMethod($docBlockGenerator, $param1, $param2, $param3, $array); } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromClone/composer.json b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromClone/composer.json index ed9965622dc40..3e4e3c49a9eb3 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromClone/composer.json +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromClone/composer.json @@ -26,27 +26,27 @@ "phpseclib/phpseclib": "~0.3", "symfony/console": "~2.3", "tubalmartin/cssmin": "2.4.8-p6", - "zendframework/zend-code": "2.4.0", - "zendframework/zend-config": "2.4.0", - "zendframework/zend-console": "2.4.0", - "zendframework/zend-di": "2.4.0", - "zendframework/zend-eventmanager": "2.4.0", - "zendframework/zend-form": "2.4.0", - "zendframework/zend-http": "2.4.0", - "zendframework/zend-i18n": "2.4.0", - "zendframework/zend-json": "2.4.0", - "zendframework/zend-log": "2.4.0", - "zendframework/zend-modulemanager": "2.4.0", - "zendframework/zend-mvc": "2.4.0", - "zendframework/zend-serializer": "2.4.0", - "zendframework/zend-server": "2.4.0", - "zendframework/zend-servicemanager": "2.4.0", - "zendframework/zend-soap": "2.4.0", - "zendframework/zend-stdlib": "2.4.0", - "zendframework/zend-text": "2.4.0", - "zendframework/zend-uri": "2.4.0", - "zendframework/zend-validator": "2.4.0", - "zendframework/zend-view": "2.4.0" + "laminas/laminas-code": "2.4.0", + "laminas/laminas-config": "2.4.0", + "laminas/laminas-console": "2.4.0", + "laminas/laminas-di": "2.4.0", + "laminas/laminas-eventmanager": "2.4.0", + "laminas/laminas-form": "2.4.0", + "laminas/laminas-http": "2.4.0", + "laminas/laminas-i18n": "2.4.0", + "laminas/laminas-json": "2.4.0", + "laminas/laminas-log": "2.4.0", + "laminas/laminas-modulemanager": "2.4.0", + "laminas/laminas-mvc": "2.4.0", + "laminas/laminas-serializer": "2.4.0", + "laminas/laminas-server": "2.4.0", + "laminas/laminas-servicemanager": "2.4.0", + "laminas/laminas-soap": "2.4.0", + "laminas/laminas-stdlib": "2.4.0", + "laminas/laminas-text": "2.4.0", + "laminas/laminas-uri": "2.4.0", + "laminas/laminas-validator": "2.4.0", + "laminas/laminas-view": "2.4.0" }, "require-dev": { "fabpot/php-cs-fixer": "~1.2", diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromClone/composer.lock b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromClone/composer.lock index 094f899a2605b..4dd3612ccb020 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromClone/composer.lock +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromClone/composer.lock @@ -2482,38 +2482,38 @@ "time": "2018-09-02T14:59:54+00:00" }, { - "name": "zendframework/zend-captcha", + "name": "laminas/laminas-captcha", "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-captcha.git", + "url": "https://github.com/laminas/laminas-captcha.git", "reference": "37e9b6a4f632a9399eecbf2e5e325ad89083f87b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-captcha/zipball/37e9b6a4f632a9399eecbf2e5e325ad89083f87b", + "url": "https://api.github.com/repos/laminas/laminas-captcha/zipball/37e9b6a4f632a9399eecbf2e5e325ad89083f87b", "reference": "37e9b6a4f632a9399eecbf2e5e325ad89083f87b", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-math": "^2.7 || ^3.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-math": "^2.7 || ^3.0", + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-session": "^2.8", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.10.1", - "zendframework/zendservice-recaptcha": "^3.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-session": "^2.8", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.10.1", + "laminas/laminas-recaptcha": "^3.0" }, "suggest": { - "zendframework/zend-i18n-resources": "Translations of captcha messages", - "zendframework/zend-session": "Zend\\Session component", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha component" + "laminas/laminas-i18n-resources": "Translations of captcha messages", + "laminas/laminas-session": "Laminas\\Session component", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-recaptcha": "Laminas\\ReCaptcha component" }, "type": "library", "extra": { @@ -2524,7 +2524,7 @@ }, "autoload": { "psr-4": { - "Zend\\Captcha\\": "src/" + "Laminas\\Captcha\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2540,33 +2540,33 @@ "time": "2018-04-24T17:24:10+00:00" }, { - "name": "zendframework/zend-code", + "name": "laminas/laminas-code", "version": "3.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-code.git", + "url": "https://github.com/laminas/laminas-code.git", "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb", "shasum": "" }, "require": { "php": "^7.1", - "zendframework/zend-eventmanager": "^2.6 || ^3.0" + "laminas/laminas-eventmanager": "^2.6 || ^3.0" }, "require-dev": { "doctrine/annotations": "~1.0", "ext-phar": "*", "phpunit/phpunit": "^6.2.3", - "zendframework/zend-coding-standard": "^1.0.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-coding-standard": "^1.0.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "suggest": { "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "zendframework/zend-stdlib": "Zend\\Stdlib component" + "laminas/laminas-stdlib": "Laminas\\Stdlib component" }, "type": "library", "extra": { @@ -2577,7 +2577,7 @@ }, "autoload": { "psr-4": { - "Zend\\Code\\": "src/" + "Laminas\\Code\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2585,7 +2585,7 @@ "BSD-3-Clause" ], "description": "provides facilities to generate arbitrary code using an object oriented interface", - "homepage": "https://github.com/zendframework/zend-code", + "homepage": "https://github.com/laminas/laminas-code", "keywords": [ "code", "zf2" @@ -2593,36 +2593,36 @@ "time": "2018-08-13T20:36:59+00:00" }, { - "name": "zendframework/zend-config", + "name": "laminas/laminas-config", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-config.git", + "url": "https://github.com/laminas/laminas-config.git", "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", + "url": "https://api.github.com/repos/laminas/laminas-config/zipball/2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.5", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.5", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", - "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-json": "Laminas\\Json to use the Json reader or writer classes", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" }, "type": "library", "extra": { @@ -2633,7 +2633,7 @@ }, "autoload": { "psr-4": { - "Zend\\Config\\": "src/" + "Laminas\\Config\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2641,7 +2641,7 @@ "BSD-3-Clause" ], "description": "provides a nested object property based user interface for accessing this configuration data within application code", - "homepage": "https://github.com/zendframework/zend-config", + "homepage": "https://github.com/laminas/laminas-config", "keywords": [ "config", "zf2" @@ -2649,33 +2649,33 @@ "time": "2016-02-04T23:01:10+00:00" }, { - "name": "zendframework/zend-console", + "name": "laminas/laminas-console", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-console.git", + "url": "https://github.com/laminas/laminas-console.git", "reference": "e8aa08da83de3d265256c40ba45cd649115f0e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-console/zipball/e8aa08da83de3d265256c40ba45cd649115f0e18", + "url": "https://api.github.com/repos/laminas/laminas-console/zipball/e8aa08da83de3d265256c40ba45cd649115f0e18", "reference": "e8aa08da83de3d265256c40ba45cd649115f0e18", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-filter": "^2.7.2", - "zendframework/zend-json": "^2.6 || ^3.0", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-filter": "^2.7.2", + "laminas/laminas-json": "^2.6 || ^3.0", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { - "zendframework/zend-filter": "To support DefaultRouteMatcher usage", - "zendframework/zend-validator": "To support DefaultRouteMatcher usage" + "laminas/laminas-filter": "To support DefaultRouteMatcher usage", + "laminas/laminas-validator": "To support DefaultRouteMatcher usage" }, "type": "library", "extra": { @@ -2686,7 +2686,7 @@ }, "autoload": { "psr-4": { - "Zend\\Console\\": "src/" + "Laminas\\Console\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2702,31 +2702,31 @@ "time": "2018-01-25T19:08:04+00:00" }, { - "name": "zendframework/zend-crypt", + "name": "laminas/laminas-crypt", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-crypt.git", + "url": "https://github.com/laminas/laminas-crypt.git", "reference": "1b2f5600bf6262904167116fa67b58ab1457036d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-crypt/zipball/1b2f5600bf6262904167116fa67b58ab1457036d", + "url": "https://api.github.com/repos/laminas/laminas-crypt/zipball/1b2f5600bf6262904167116fa67b58ab1457036d", "reference": "1b2f5600bf6262904167116fa67b58ab1457036d", "shasum": "" }, "require": { "container-interop/container-interop": "~1.0", "php": "^5.5 || ^7.0", - "zendframework/zend-math": "^2.6", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-math": "^2.6", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0" }, "suggest": { - "ext-mcrypt": "Required for most features of Zend\\Crypt" + "ext-mcrypt": "Required for most features of Laminas\\Crypt" }, "type": "library", "extra": { @@ -2737,14 +2737,14 @@ }, "autoload": { "psr-4": { - "Zend\\Crypt\\": "src/" + "Laminas\\Crypt\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-crypt", + "homepage": "https://github.com/laminas/laminas-crypt", "keywords": [ "crypt", "zf2" @@ -2752,34 +2752,34 @@ "time": "2016-02-03T23:46:30+00:00" }, { - "name": "zendframework/zend-db", + "name": "laminas/laminas-db", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-db.git", + "url": "https://github.com/laminas/laminas-db.git", "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-eventmanager": "Zend\\EventManager component", - "zendframework/zend-hydrator": "Zend\\Hydrator component for using HydratingResultSets", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-hydrator": "Laminas\\Hydrator component for using HydratingResultSets", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { @@ -2788,13 +2788,13 @@ "dev-develop": "2.10-dev" }, "zf": { - "component": "Zend\\Db", - "config-provider": "Zend\\Db\\ConfigProvider" + "component": "Laminas\\Db", + "config-provider": "Laminas\\Db\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Db\\": "src/" + "Laminas\\Db\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2810,24 +2810,24 @@ "time": "2019-02-25T11:37:45+00:00" }, { - "name": "zendframework/zend-di", + "name": "laminas/laminas-di", "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-di.git", + "url": "https://github.com/laminas/laminas-di.git", "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-di/zipball/1fd1ba85660b5a2718741b38639dc7c4c3194b37", + "url": "https://api.github.com/repos/laminas/laminas-di/zipball/1fd1ba85660b5a2718741b38639dc7c4c3194b37", "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.5 || ^7.0", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -2842,14 +2842,14 @@ }, "autoload": { "psr-4": { - "Zend\\Di\\": "src/" + "Laminas\\Di\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-di", + "homepage": "https://github.com/laminas/laminas-di", "keywords": [ "di", "zf2" @@ -2857,16 +2857,16 @@ "time": "2016-04-25T20:58:11+00:00" }, { - "name": "zendframework/zend-diactoros", + "name": "laminas/laminas-diactoros", "version": "1.8.6", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-diactoros.git", + "url": "https://github.com/laminas/laminas-diactoros.git", "reference": "20da13beba0dde8fb648be3cc19765732790f46e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", "reference": "20da13beba0dde8fb648be3cc19765732790f46e", "shasum": "" }, @@ -2882,7 +2882,7 @@ "ext-libxml": "*", "php-http/psr7-integration-tests": "dev-master", "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", - "zendframework/zend-coding-standard": "~1.0" + "laminas/laminas-coding-standard": "~1.0" }, "type": "library", "extra": { @@ -2904,7 +2904,7 @@ "src/functions/parse_cookie_header.php" ], "psr-4": { - "Zend\\Diactoros\\": "src/" + "Laminas\\Diactoros\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2912,7 +2912,7 @@ "BSD-2-Clause" ], "description": "PSR HTTP Message implementations", - "homepage": "https://github.com/zendframework/zend-diactoros", + "homepage": "https://github.com/laminas/laminas-diactoros", "keywords": [ "http", "psr", @@ -2921,16 +2921,16 @@ "time": "2018-09-05T19:29:37+00:00" }, { - "name": "zendframework/zend-escaper", + "name": "laminas/laminas-escaper", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-escaper.git", + "url": "https://github.com/laminas/laminas-escaper.git", "reference": "31d8aafae982f9568287cb4dce987e6aff8fd074" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/31d8aafae982f9568287cb4dce987e6aff8fd074", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/31d8aafae982f9568287cb4dce987e6aff8fd074", "reference": "31d8aafae982f9568287cb4dce987e6aff8fd074", "shasum": "" }, @@ -2939,7 +2939,7 @@ }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -2950,7 +2950,7 @@ }, "autoload": { "psr-4": { - "Zend\\Escaper\\": "src/" + "Laminas\\Escaper\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -2966,22 +2966,22 @@ "time": "2018-04-25T15:48:53+00:00" }, { - "name": "zendframework/zend-eventmanager", + "name": "laminas/laminas-eventmanager", "version": "2.6.4", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-eventmanager.git", + "url": "https://github.com/laminas/laminas-eventmanager.git", "reference": "d238c443220dce4b6396579c8ab2200ec25f9108" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/d238c443220dce4b6396579c8ab2200ec25f9108", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/d238c443220dce4b6396579c8ab2200ec25f9108", "reference": "d238c443220dce4b6396579c8ab2200ec25f9108", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7" + "laminas/laminas-stdlib": "^2.7" }, "require-dev": { "athletic/athletic": "dev-master", @@ -2998,14 +2998,14 @@ }, "autoload": { "psr-4": { - "Zend\\EventManager\\": "src/" + "Laminas\\EventManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-eventmanager", + "homepage": "https://github.com/laminas/laminas-eventmanager", "keywords": [ "eventmanager", "zf2" @@ -3013,41 +3013,41 @@ "time": "2017-12-12T17:48:56+00:00" }, { - "name": "zendframework/zend-feed", + "name": "laminas/laminas-feed", "version": "2.10.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-feed.git", + "url": "https://github.com/laminas/laminas-feed.git", "reference": "6641f4cf3f4586c63f83fd70b6d19966025c8888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/6641f4cf3f4586c63f83fd70b6d19966025c8888", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/6641f4cf3f4586c63f83fd70b6d19966025c8888", "reference": "6641f4cf3f4586c63f83fd70b6d19966025c8888", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5.2", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-escaper": "^2.5.2", + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-message": "^1.0.1", - "zendframework/zend-cache": "^2.7.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-http": "^2.7", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-cache": "^2.7.2", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-http": "^2.7", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { - "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Zend\\Feed\\Reader\\Http\\Psr7ResponseDecorator", - "zendframework/zend-cache": "Zend\\Cache component, for optionally caching feeds between requests", - "zendframework/zend-db": "Zend\\Db component, for use with PubSubHubbub", - "zendframework/zend-http": "Zend\\Http for PubSubHubbub, and optionally for use with Zend\\Feed\\Reader", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for easily extending ExtensionManager implementations", - "zendframework/zend-validator": "Zend\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" + "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Laminas\\Feed\\Reader\\Http\\Psr7ResponseDecorator", + "laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests", + "laminas/laminas-db": "Laminas\\Db component, for use with PubSubHubbub", + "laminas/laminas-http": "Laminas\\Http for PubSubHubbub, and optionally for use with Laminas\\Feed\\Reader", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for easily extending ExtensionManager implementations", + "laminas/laminas-validator": "Laminas\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" }, "type": "library", "extra": { @@ -3058,7 +3058,7 @@ }, "autoload": { "psr-4": { - "Zend\\Feed\\": "src/" + "Laminas\\Feed\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3074,41 +3074,41 @@ "time": "2018-08-01T13:53:20+00:00" }, { - "name": "zendframework/zend-filter", + "name": "laminas/laminas-filter", "version": "2.9.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-filter.git", + "url": "https://github.com/laminas/laminas-filter.git", "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "conflict": { - "zendframework/zend-validator": "<2.10.1" + "laminas/laminas-validator": "<2.10.1" }, "require-dev": { "pear/archive_tar": "^1.4.3", "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-factory": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-crypt": "^3.2.1", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-uri": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^3.2.1", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-uri": "^2.6" }, "suggest": { "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", - "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", - "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", - "zendframework/zend-uri": "Zend\\Uri component, for the UriNormalize filter" + "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", + "laminas/laminas-i18n": "Laminas\\I18n component for filters depending on i18n functionality", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for using the filter chain functionality", + "laminas/laminas-uri": "Laminas\\Uri component, for the UriNormalize filter" }, "type": "library", "extra": { @@ -3117,13 +3117,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\Filter", - "config-provider": "Zend\\Filter\\ConfigProvider" + "component": "Laminas\\Filter", + "config-provider": "Laminas\\Filter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Filter\\": "src/" + "Laminas\\Filter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3139,51 +3139,51 @@ "time": "2018-12-17T16:00:04+00:00" }, { - "name": "zendframework/zend-form", + "name": "laminas/laminas-form", "version": "2.13.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-form.git", + "url": "https://github.com/laminas/laminas-form.git", "reference": "c713a12ccbd43148b71c9339e171ca11e3f8a1da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/c713a12ccbd43148b71c9339e171ca11e3f8a1da", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/c713a12ccbd43148b71c9339e171ca11e3f8a1da", "reference": "c713a12ccbd43148b71c9339e171ca11e3f8a1da", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "doctrine/annotations": "~1.0", "phpunit/phpunit": "^5.7.23 || ^6.5.3", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.2", - "zendframework/zendservice-recaptcha": "^3.0.0" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.2", + "laminas/laminas-recaptcha": "^3.0.0" }, "suggest": { - "zendframework/zend-captcha": "^2.7.1, required for using CAPTCHA form elements", - "zendframework/zend-code": "^2.6 || ^3.0, required to use zend-form annotations support", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, reuired for zend-form annotations support", - "zendframework/zend-i18n": "^2.6, required when using zend-form view helpers", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", - "zendframework/zend-view": "^2.6.2, required for using the zend-form view helpers", - "zendframework/zendservice-recaptcha": "in order to use the ReCaptcha form element" + "laminas/laminas-captcha": "^2.7.1, required for using CAPTCHA form elements", + "laminas/laminas-code": "^2.6 || ^3.0, required to use zend-form annotations support", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, reuired for zend-form annotations support", + "laminas/laminas-i18n": "^2.6, required when using zend-form view helpers", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", + "laminas/laminas-view": "^2.6.2, required for using the zend-form view helpers", + "laminas/laminas-recaptcha": "in order to use the ReCaptcha form element" }, "type": "library", "extra": { @@ -3192,13 +3192,13 @@ "dev-develop": "2.14.x-dev" }, "zf": { - "component": "Zend\\Form", - "config-provider": "Zend\\Form\\ConfigProvider" + "component": "Laminas\\Form", + "config-provider": "Laminas\\Form\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Form\\": "src/" + "Laminas\\Form\\": "src/" }, "files": [ "autoload/formElementManagerPolyfill.php" @@ -3217,30 +3217,30 @@ "time": "2018-12-11T22:51:29+00:00" }, { - "name": "zendframework/zend-http", + "name": "laminas/laminas-http", "version": "2.8.4", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-http.git", + "url": "https://github.com/laminas/laminas-http.git", "reference": "d160aedc096be230af0fe9c31151b2b33ad4e807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/d160aedc096be230af0fe9c31151b2b33ad4e807", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/d160aedc096be230af0fe9c31151b2b33ad4e807", "reference": "d160aedc096be230af0fe9c31151b2b33ad4e807", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-loader": "^2.5.1", - "zendframework/zend-stdlib": "^3.1 || ^2.7.7", - "zendframework/zend-uri": "^2.5.2", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-loader": "^2.5.1", + "laminas/laminas-stdlib": "^3.1 || ^2.7.7", + "laminas/laminas-uri": "^2.5.2", + "laminas/laminas-validator": "^2.10.1" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^3.1 || ^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^3.1 || ^2.6" }, "suggest": { "paragonie/certainty": "For automated management of cacert.pem" @@ -3254,7 +3254,7 @@ }, "autoload": { "psr-4": { - "Zend\\Http\\": "src/" + "Laminas\\Http\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3272,37 +3272,37 @@ "time": "2019-02-07T17:47:08+00:00" }, { - "name": "zendframework/zend-hydrator", + "name": "laminas/laminas-hydrator", "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-hydrator.git", + "url": "https://github.com/laminas/laminas-hydrator.git", "reference": "22652e1661a5a10b3f564cf7824a2206cf5a4a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-hydrator/zipball/22652e1661a5a10b3f564cf7824a2206cf5a4a65", + "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/22652e1661a5a10b3f564cf7824a2206cf5a4a65", "reference": "22652e1661a5a10b3f564cf7824a2206cf5a4a65", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "~4.0", "squizlabs/php_codesniffer": "^2.0@dev", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-inputfilter": "^2.6", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-inputfilter": "^2.6", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", - "zendframework/zend-filter": "^2.6, to support naming strategy hydrator usage", - "zendframework/zend-serializer": "^2.6.1, to use the SerializableStrategy", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", + "laminas/laminas-filter": "^2.6, to support naming strategy hydrator usage", + "laminas/laminas-serializer": "^2.6.1, to use the SerializableStrategy", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" }, "type": "library", "extra": { @@ -3315,14 +3315,14 @@ }, "autoload": { "psr-4": { - "Zend\\Hydrator\\": "src/" + "Laminas\\Hydrator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-hydrator", + "homepage": "https://github.com/laminas/laminas-hydrator", "keywords": [ "hydrator", "zf2" @@ -3330,44 +3330,44 @@ "time": "2016-02-18T22:38:26+00:00" }, { - "name": "zendframework/zend-i18n", + "name": "laminas/laminas-i18n", "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-i18n.git", + "url": "https://github.com/laminas/laminas-i18n.git", "reference": "6d69af5a04e1a4de7250043cb1322f077a0cdb7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/6d69af5a04e1a4de7250043cb1322f077a0cdb7f", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/6d69af5a04e1a4de7250043cb1322f077a0cdb7f", "reference": "6d69af5a04e1a4de7250043cb1322f077a0cdb7f", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.3" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.3" }, "suggest": { - "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", - "zendframework/zend-filter": "You should install this package to use the provided filters", - "zendframework/zend-i18n-resources": "Translation resources", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "You should install this package to use the provided validators", - "zendframework/zend-view": "You should install this package to use the provided view helpers" + "ext-intl": "Required for most features of Laminas\\I18n; included in default builds of PHP", + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-eventmanager": "You should install this package to use the events in the translator", + "laminas/laminas-filter": "You should install this package to use the provided filters", + "laminas/laminas-i18n-resources": "Translation resources", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "You should install this package to use the provided validators", + "laminas/laminas-view": "You should install this package to use the provided view helpers" }, "type": "library", "extra": { @@ -3376,13 +3376,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\I18n", - "config-provider": "Zend\\I18n\\ConfigProvider" + "component": "Laminas\\I18n", + "config-provider": "Laminas\\I18n\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\I18n\\": "src/" + "Laminas\\I18n\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3398,30 +3398,30 @@ "time": "2018-05-16T16:39:13+00:00" }, { - "name": "zendframework/zend-inputfilter", + "name": "laminas/laminas-inputfilter", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-inputfilter.git", + "url": "https://github.com/laminas/laminas-inputfilter.git", "reference": "4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", + "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", "reference": "4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-filter": "^2.9.1", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.11" + "laminas/laminas-filter": "^2.9.1", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.11" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-message": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "suggest": { "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" @@ -3433,13 +3433,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\InputFilter", - "config-provider": "Zend\\InputFilter\\ConfigProvider" + "component": "Laminas\\InputFilter", + "config-provider": "Laminas\\InputFilter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\InputFilter\\": "src/" + "Laminas\\InputFilter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3455,16 +3455,16 @@ "time": "2019-01-30T16:58:51+00:00" }, { - "name": "zendframework/zend-json", + "name": "laminas/laminas-json", "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-json.git", + "url": "https://github.com/laminas/laminas-json.git", "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", + "url": "https://api.github.com/repos/laminas/laminas-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", "shasum": "" }, @@ -3474,16 +3474,16 @@ "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.5 || ^3.0", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.5 || ^3.0", "zendframework/zendxml": "^1.0.2" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component, required to use Zend\\Json\\Server", - "zendframework/zend-server": "Zend\\Server component, required to use Zend\\Json\\Server", - "zendframework/zend-stdlib": "Zend\\Stdlib component, for use with caching Zend\\Json\\Server responses", - "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" + "laminas/laminas-http": "Laminas\\Http component, required to use Laminas\\Json\\Server", + "laminas/laminas-server": "Laminas\\Server component, required to use Laminas\\Json\\Server", + "laminas/laminas-stdlib": "Laminas\\Stdlib component, for use with caching Laminas\\Json\\Server responses", + "zendframework/zendxml": "To support Laminas\\Json\\Json::fromXml() usage" }, "type": "library", "extra": { @@ -3494,7 +3494,7 @@ }, "autoload": { "psr-4": { - "Zend\\Json\\": "src/" + "Laminas\\Json\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3502,7 +3502,7 @@ "BSD-3-Clause" ], "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", - "homepage": "https://github.com/zendframework/zend-json", + "homepage": "https://github.com/laminas/laminas-json", "keywords": [ "json", "zf2" @@ -3510,16 +3510,16 @@ "time": "2016-02-04T21:20:26+00:00" }, { - "name": "zendframework/zend-loader", + "name": "laminas/laminas-loader", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-loader.git", + "url": "https://github.com/laminas/laminas-loader.git", "reference": "78f11749ea340f6ca316bca5958eef80b38f9b6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/78f11749ea340f6ca316bca5958eef80b38f9b6c", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/78f11749ea340f6ca316bca5958eef80b38f9b6c", "reference": "78f11749ea340f6ca316bca5958eef80b38f9b6c", "shasum": "" }, @@ -3528,7 +3528,7 @@ }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -3539,7 +3539,7 @@ }, "autoload": { "psr-4": { - "Zend\\Loader\\": "src/" + "Laminas\\Loader\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3555,24 +3555,24 @@ "time": "2018-04-30T15:20:54+00:00" }, { - "name": "zendframework/zend-log", + "name": "laminas/laminas-log", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-log.git", + "url": "https://github.com/laminas/laminas-log.git", "reference": "9cec3b092acb39963659c2f32441cccc56b3f430" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/9cec3b092acb39963659c2f32441cccc56b3f430", + "url": "https://api.github.com/repos/laminas/laminas-log/zipball/9cec3b092acb39963659c2f32441cccc56b3f430", "reference": "9cec3b092acb39963659c2f32441cccc56b3f430", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", "psr/log": "^1.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "provide": { "psr/log-implementation": "1.0.0" @@ -3580,21 +3580,21 @@ "require-dev": { "mikey179/vfsstream": "^1.6", "phpunit/phpunit": "^5.7.15 || ^6.0.8", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-filter": "^2.5", - "zendframework/zend-mail": "^2.6.1", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-filter": "^2.5", + "laminas/laminas-mail": "^2.6.1", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { "ext-mongo": "mongo extension to use Mongo writer", "ext-mongodb": "mongodb extension to use MongoDB writer", - "zendframework/zend-console": "Zend\\Console component to use the RequestID log processor", - "zendframework/zend-db": "Zend\\Db component to use the database log writer", - "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML log formatter", - "zendframework/zend-mail": "Zend\\Mail component to use the email log writer", - "zendframework/zend-validator": "Zend\\Validator component to block invalid log messages" + "laminas/laminas-console": "Laminas\\Console component to use the RequestID log processor", + "laminas/laminas-db": "Laminas\\Db component to use the database log writer", + "laminas/laminas-escaper": "Laminas\\Escaper component, for use in the XML log formatter", + "laminas/laminas-mail": "Laminas\\Mail component to use the email log writer", + "laminas/laminas-validator": "Laminas\\Validator component to block invalid log messages" }, "type": "library", "extra": { @@ -3603,13 +3603,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\Log", - "config-provider": "Zend\\Log\\ConfigProvider" + "component": "Laminas\\Log", + "config-provider": "Laminas\\Log\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Log\\": "src/" + "Laminas\\Log\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3617,7 +3617,7 @@ "BSD-3-Clause" ], "description": "component for general purpose logging", - "homepage": "https://github.com/zendframework/zend-log", + "homepage": "https://github.com/laminas/laminas-log", "keywords": [ "log", "logging", @@ -3626,16 +3626,16 @@ "time": "2018-04-09T21:59:51+00:00" }, { - "name": "zendframework/zend-mail", + "name": "laminas/laminas-mail", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mail.git", + "url": "https://github.com/laminas/laminas-mail.git", "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8", "shasum": "" }, @@ -3643,21 +3643,21 @@ "ext-iconv": "*", "php": "^5.6 || ^7.0", "true/punycode": "^2.1", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mime": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.10.2" + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-crypt": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1" }, "suggest": { - "zendframework/zend-crypt": "Crammd5 support in SMTP Auth", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" }, "type": "library", "extra": { @@ -3666,13 +3666,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\Mail", - "config-provider": "Zend\\Mail\\ConfigProvider" + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Mail\\": "src/" + "Laminas\\Mail\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3688,16 +3688,16 @@ "time": "2018-06-07T13:37:07+00:00" }, { - "name": "zendframework/zend-math", + "name": "laminas/laminas-math", "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-math.git", + "url": "https://github.com/laminas/laminas-math.git", "reference": "1abce074004dacac1a32cd54de94ad47ef960d38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", + "url": "https://api.github.com/repos/laminas/laminas-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", "reference": "1abce074004dacac1a32cd54de94ad47ef960d38", "shasum": "" }, @@ -3712,7 +3712,7 @@ "suggest": { "ext-bcmath": "If using the bcmath functionality", "ext-gmp": "If using the gmp functionality", - "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if Mcrypt extensions is unavailable" + "ircmaxell/random-lib": "Fallback random byte generator for Laminas\\Math\\Rand if Mcrypt extensions is unavailable" }, "type": "library", "extra": { @@ -3723,14 +3723,14 @@ }, "autoload": { "psr-4": { - "Zend\\Math\\": "src/" + "Laminas\\Math\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-math", + "homepage": "https://github.com/laminas/laminas-math", "keywords": [ "math", "zf2" @@ -3738,30 +3738,30 @@ "time": "2018-12-04T15:34:17+00:00" }, { - "name": "zendframework/zend-mime", + "name": "laminas/laminas-mime", "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mime.git", + "url": "https://github.com/laminas/laminas-mime.git", "reference": "52ae5fa9f12845cae749271034a2d594f0e4c6f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mime/zipball/52ae5fa9f12845cae749271034a2d594f0e4c6f2", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/52ae5fa9f12845cae749271034a2d594f0e4c6f2", "reference": "52ae5fa9f12845cae749271034a2d594f0e4c6f2", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-mail": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-mail": "^2.6" }, "suggest": { - "zendframework/zend-mail": "Zend\\Mail component" + "laminas/laminas-mail": "Laminas\\Mail component" }, "type": "library", "extra": { @@ -3772,7 +3772,7 @@ }, "autoload": { "psr-4": { - "Zend\\Mime\\": "src/" + "Laminas\\Mime\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3780,7 +3780,7 @@ "BSD-3-Clause" ], "description": "Create and parse MIME messages and parts", - "homepage": "https://github.com/zendframework/zend-mime", + "homepage": "https://github.com/laminas/laminas-mime", "keywords": [ "ZendFramework", "mime", @@ -3789,39 +3789,39 @@ "time": "2018-05-14T19:02:50+00:00" }, { - "name": "zendframework/zend-modulemanager", + "name": "laminas/laminas-modulemanager", "version": "2.8.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-modulemanager.git", + "url": "https://github.com/laminas/laminas-modulemanager.git", "reference": "394df6e12248ac430a312d4693f793ee7120baa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/394df6e12248ac430a312d4693f793ee7120baa6", + "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/394df6e12248ac430a312d4693f793ee7120baa6", "reference": "394df6e12248ac430a312d4693f793ee7120baa6", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-config": "^3.1 || ^2.6", - "zendframework/zend-eventmanager": "^3.2 || ^2.6.3", - "zendframework/zend-stdlib": "^3.1 || ^2.7" + "laminas/laminas-config": "^3.1 || ^2.6", + "laminas/laminas-eventmanager": "^3.2 || ^2.6.3", + "laminas/laminas-stdlib": "^3.1 || ^2.7" }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-console": "^2.6", - "zendframework/zend-di": "^2.6", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mvc": "^3.0 || ^2.7", - "zendframework/zend-servicemanager": "^3.0.3 || ^2.7.5" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-console": "^2.6", + "laminas/laminas-di": "^2.6", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mvc": "^3.0 || ^2.7", + "laminas/laminas-servicemanager": "^3.0.3 || ^2.7.5" }, "suggest": { - "zendframework/zend-console": "Zend\\Console component", - "zendframework/zend-loader": "Zend\\Loader component if you are not using Composer autoloading for your modules", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "laminas/laminas-console": "Laminas\\Console component", + "laminas/laminas-loader": "Laminas\\Loader component if you are not using Composer autoloading for your modules", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { @@ -3832,7 +3832,7 @@ }, "autoload": { "psr-4": { - "Zend\\ModuleManager\\": "src/" + "Laminas\\ModuleManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3840,7 +3840,7 @@ "BSD-3-Clause" ], "description": "Modular application system for zend-mvc applications", - "homepage": "https://github.com/zendframework/zend-modulemanager", + "homepage": "https://github.com/laminas/laminas-modulemanager", "keywords": [ "ZendFramework", "modulemanager", @@ -3849,73 +3849,73 @@ "time": "2017-12-02T06:11:18+00:00" }, { - "name": "zendframework/zend-mvc", + "name": "laminas/laminas-mvc", "version": "2.7.15", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mvc.git", + "url": "https://github.com/laminas/laminas-mvc.git", "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", + "url": "https://api.github.com/repos/laminas/laminas-mvc/zipball/a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.5 || ^7.0", - "zendframework/zend-console": "^2.7", - "zendframework/zend-eventmanager": "^2.6.4 || ^3.0", - "zendframework/zend-form": "^2.11", - "zendframework/zend-hydrator": "^1.1 || ^2.4", - "zendframework/zend-psr7bridge": "^0.2", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7.5 || ^3.0" + "laminas/laminas-console": "^2.7", + "laminas/laminas-eventmanager": "^2.6.4 || ^3.0", + "laminas/laminas-form": "^2.11", + "laminas/laminas-hydrator": "^1.1 || ^2.4", + "laminas/laminas-psr7bridge": "^0.2", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7.5 || ^3.0" }, "replace": { - "zendframework/zend-router": "^2.0" + "laminas/laminas-router": "^2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "1.7.*", "phpunit/phpunit": "^4.8.36", "sebastian/comparator": "^1.2.4", "sebastian/version": "^1.0.4", - "zendframework/zend-authentication": "^2.6", - "zendframework/zend-cache": "^2.8", - "zendframework/zend-di": "^2.6", - "zendframework/zend-filter": "^2.8", - "zendframework/zend-http": "^2.8", - "zendframework/zend-i18n": "^2.8", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.3", - "zendframework/zend-modulemanager": "^2.8", - "zendframework/zend-serializer": "^2.8", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.7", - "zendframework/zend-uri": "^2.6", - "zendframework/zend-validator": "^2.10", - "zendframework/zend-view": "^2.9" + "laminas/laminas-authentication": "^2.6", + "laminas/laminas-cache": "^2.8", + "laminas/laminas-di": "^2.6", + "laminas/laminas-filter": "^2.8", + "laminas/laminas-http": "^2.8", + "laminas/laminas-i18n": "^2.8", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.3", + "laminas/laminas-modulemanager": "^2.8", + "laminas/laminas-serializer": "^2.8", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.7", + "laminas/laminas-uri": "^2.6", + "laminas/laminas-validator": "^2.10", + "laminas/laminas-view": "^2.9" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component for Identity plugin", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-di": "Zend\\Di component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component for translatable segments", - "zendframework/zend-inputfilter": "Zend\\Inputfilter component", - "zendframework/zend-json": "Zend\\Json component", - "zendframework/zend-log": "Zend\\Log component", - "zendframework/zend-modulemanager": "Zend\\ModuleManager component", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager-di": "^1.0.1, if using zend-servicemanager v3 and requiring the zend-di integration", - "zendframework/zend-session": "Zend\\Session component for FlashMessenger, PRG, and FPRG plugins", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-uri": "Zend\\Uri component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zend-view": "Zend\\View component" + "laminas/laminas-authentication": "Laminas\\Authentication component for Identity plugin", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-di": "Laminas\\Di component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component for translatable segments", + "laminas/laminas-inputfilter": "Zend\\Inputfilter component", + "laminas/laminas-json": "Laminas\\Json component", + "laminas/laminas-log": "Laminas\\Log component", + "laminas/laminas-modulemanager": "Laminas\\ModuleManager component", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager-di": "^1.0.1, if using zend-servicemanager v3 and requiring the zend-di integration", + "laminas/laminas-session": "Laminas\\Session component for FlashMessenger, PRG, and FPRG plugins", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-uri": "Laminas\\Uri component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-view": "Laminas\\View component" }, "type": "library", "extra": { @@ -3929,14 +3929,14 @@ "src/autoload.php" ], "psr-4": { - "Zend\\Mvc\\": "src/" + "Laminas\\Mvc\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-mvc", + "homepage": "https://github.com/laminas/laminas-mvc", "keywords": [ "mvc", "zf2" @@ -3944,24 +3944,24 @@ "time": "2018-05-03T13:13:41+00:00" }, { - "name": "zendframework/zend-psr7bridge", + "name": "laminas/laminas-psr7bridge", "version": "0.2.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-psr7bridge.git", + "url": "https://github.com/laminas/laminas-psr7bridge.git", "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-psr7bridge/zipball/86c0b53b0c6381391c4add4a93a56e51d5c74605", + "url": "https://api.github.com/repos/laminas/laminas-psr7bridge/zipball/86c0b53b0c6381391c4add4a93a56e51d5c74605", "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605", "shasum": "" }, "require": { "php": ">=5.5", "psr/http-message": "^1.0", - "zendframework/zend-diactoros": "^1.1", - "zendframework/zend-http": "^2.5" + "laminas/laminas-diactoros": "^1.1", + "laminas/laminas-http": "^2.5" }, "require-dev": { "phpunit/phpunit": "^4.7", @@ -3976,15 +3976,15 @@ }, "autoload": { "psr-4": { - "Zend\\Psr7Bridge\\": "src/" + "Laminas\\Psr7Bridge\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "PSR-7 <-> Zend\\Http bridge", - "homepage": "https://github.com/zendframework/zend-psr7bridge", + "description": "PSR-7 <-> Laminas\\Http bridge", + "homepage": "https://github.com/laminas/laminas-psr7bridge", "keywords": [ "http", "psr", @@ -3993,33 +3993,33 @@ "time": "2016-05-10T21:44:39+00:00" }, { - "name": "zendframework/zend-serializer", + "name": "laminas/laminas-serializer", "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-serializer.git", + "url": "https://github.com/laminas/laminas-serializer.git", "reference": "0172690db48d8935edaf625c4cba38b79719892c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/0172690db48d8935edaf625c4cba38b79719892c", + "url": "https://api.github.com/repos/laminas/laminas-serializer/zipball/0172690db48d8935edaf625c4cba38b79719892c", "reference": "0172690db48d8935edaf625c4cba38b79719892c", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-json": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-json": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-math": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-math": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-math": "(^2.6 || ^3.0) To support Python Pickle serialization", - "zendframework/zend-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" + "laminas/laminas-math": "(^2.6 || ^3.0) To support Python Pickle serialization", + "laminas/laminas-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" }, "type": "library", "extra": { @@ -4028,13 +4028,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\Serializer", - "config-provider": "Zend\\Serializer\\ConfigProvider" + "component": "Laminas\\Serializer", + "config-provider": "Laminas\\Serializer\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Serializer\\": "src/" + "Laminas\\Serializer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4050,27 +4050,27 @@ "time": "2018-05-14T18:45:18+00:00" }, { - "name": "zendframework/zend-server", + "name": "laminas/laminas-server", "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-server.git", + "url": "https://github.com/laminas/laminas-server.git", "reference": "23a2e9a5599c83c05da831cb7c649e8a7809595e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-server/zipball/23a2e9a5599c83c05da831cb7c649e8a7809595e", + "url": "https://api.github.com/repos/laminas/laminas-server/zipball/23a2e9a5599c83c05da831cb7c649e8a7809595e", "reference": "23a2e9a5599c83c05da831cb7c649e8a7809595e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-code": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.5 || ^3.0" + "laminas/laminas-code": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.5 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -4081,7 +4081,7 @@ }, "autoload": { "psr-4": { - "Zend\\Server\\": "src/" + "Laminas\\Server\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4097,16 +4097,16 @@ "time": "2018-04-30T22:21:28+00:00" }, { - "name": "zendframework/zend-servicemanager", + "name": "laminas/laminas-servicemanager", "version": "2.7.11", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-servicemanager.git", + "url": "https://github.com/laminas/laminas-servicemanager.git", "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7", "shasum": "" }, @@ -4118,12 +4118,12 @@ "athletic/athletic": "dev-master", "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-di": "~2.5", - "zendframework/zend-mvc": "~2.5" + "laminas/laminas-di": "~2.5", + "laminas/laminas-mvc": "~2.5" }, "suggest": { "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services", - "zendframework/zend-di": "Zend\\Di component" + "laminas/laminas-di": "Laminas\\Di component" }, "type": "library", "extra": { @@ -4134,14 +4134,14 @@ }, "autoload": { "psr-4": { - "Zend\\ServiceManager\\": "src/" + "Laminas\\ServiceManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-servicemanager", + "homepage": "https://github.com/laminas/laminas-servicemanager", "keywords": [ "servicemanager", "zf2" @@ -4149,43 +4149,43 @@ "time": "2018-06-22T14:49:54+00:00" }, { - "name": "zendframework/zend-session", + "name": "laminas/laminas-session", "version": "2.8.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-session.git", + "url": "https://github.com/laminas/laminas-session.git", "reference": "2cfd90e1a2f6b066b9f908599251d8f64f07021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-session/zipball/2cfd90e1a2f6b066b9f908599251d8f64f07021b", + "url": "https://api.github.com/repos/laminas/laminas-session/zipball/2cfd90e1a2f6b066b9f908599251d8f64f07021b", "reference": "2cfd90e1a2f6b066b9f908599251d8f64f07021b", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "container-interop/container-interop": "^1.1", "mongodb/mongodb": "^1.0.1", "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", "phpunit/phpunit": "^5.7.5 || >=6.0.13 <6.5.0", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.7", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.7", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6" }, "suggest": { "mongodb/mongodb": "If you want to use the MongoDB session save handler", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-db": "Zend\\Db component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "Zend\\Validator component" + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-db": "Laminas\\Db component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "Laminas\\Validator component" }, "type": "library", "extra": { @@ -4194,13 +4194,13 @@ "dev-develop": "2.9-dev" }, "zf": { - "component": "Zend\\Session", - "config-provider": "Zend\\Session\\ConfigProvider" + "component": "Laminas\\Session", + "config-provider": "Laminas\\Session\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Session\\": "src/" + "Laminas\\Session\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4216,34 +4216,34 @@ "time": "2018-02-22T16:33:54+00:00" }, { - "name": "zendframework/zend-soap", + "name": "laminas/laminas-soap", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-soap.git", + "url": "https://github.com/laminas/laminas-soap.git", "reference": "af03c32f0db2b899b3df8cfe29aeb2b49857d284" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/af03c32f0db2b899b3df8cfe29aeb2b49857d284", + "url": "https://api.github.com/repos/laminas/laminas-soap/zipball/af03c32f0db2b899b3df8cfe29aeb2b49857d284", "reference": "af03c32f0db2b899b3df8cfe29aeb2b49857d284", "shasum": "" }, "require": { "ext-soap": "*", "php": "^5.6 || ^7.0", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-uri": "^2.5.2" + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-uri": "^2.5.2" }, "require-dev": { "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-http": "^2.5.4" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-http": "^2.5.4" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component" + "laminas/laminas-http": "Laminas\\Http component" }, "type": "library", "extra": { @@ -4254,14 +4254,14 @@ }, "autoload": { "psr-4": { - "Zend\\Soap\\": "src/" + "Laminas\\Soap\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-soap", + "homepage": "https://github.com/laminas/laminas-soap", "keywords": [ "soap", "zf2" @@ -4269,39 +4269,39 @@ "time": "2018-01-29T17:51:26+00:00" }, { - "name": "zendframework/zend-stdlib", + "name": "laminas/laminas-stdlib", "version": "2.7.7", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-stdlib.git", + "url": "https://github.com/laminas/laminas-stdlib.git", "reference": "0e44eb46788f65e09e077eb7f44d2659143bcc1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/0e44eb46788f65e09e077eb7f44d2659143bcc1f", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/0e44eb46788f65e09e077eb7f44d2659143bcc1f", "reference": "0e44eb46788f65e09e077eb7f44d2659143bcc1f", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-hydrator": "~1.1" + "laminas/laminas-hydrator": "~1.1" }, "require-dev": { "athletic/athletic": "~0.1", "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-config": "~2.5", - "zendframework/zend-eventmanager": "~2.5", - "zendframework/zend-filter": "~2.5", - "zendframework/zend-inputfilter": "~2.5", - "zendframework/zend-serializer": "~2.5", - "zendframework/zend-servicemanager": "~2.5" + "laminas/laminas-config": "~2.5", + "laminas/laminas-eventmanager": "~2.5", + "laminas/laminas-filter": "~2.5", + "laminas/laminas-inputfilter": "~2.5", + "laminas/laminas-serializer": "~2.5", + "laminas/laminas-servicemanager": "~2.5" }, "suggest": { - "zendframework/zend-eventmanager": "To support aggregate hydrator usage", - "zendframework/zend-filter": "To support naming strategy hydrator usage", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + "laminas/laminas-eventmanager": "To support aggregate hydrator usage", + "laminas/laminas-filter": "To support naming strategy hydrator usage", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager": "To support hydrator plugin manager usage" }, "type": "library", "extra": { @@ -4313,14 +4313,14 @@ }, "autoload": { "psr-4": { - "Zend\\Stdlib\\": "src/" + "Laminas\\Stdlib\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-stdlib", + "homepage": "https://github.com/laminas/laminas-stdlib", "keywords": [ "stdlib", "zf2" @@ -4328,28 +4328,28 @@ "time": "2016-04-12T21:17:31+00:00" }, { - "name": "zendframework/zend-text", + "name": "laminas/laminas-text", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-text.git", + "url": "https://github.com/laminas/laminas-text.git", "reference": "ca987dd4594f5f9508771fccd82c89bc7fbb39ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-text/zipball/ca987dd4594f5f9508771fccd82c89bc7fbb39ac", + "url": "https://api.github.com/repos/laminas/laminas-text/zipball/ca987dd4594f5f9508771fccd82c89bc7fbb39ac", "reference": "ca987dd4594f5f9508771fccd82c89bc7fbb39ac", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6" }, "type": "library", "extra": { @@ -4360,7 +4360,7 @@ }, "autoload": { "psr-4": { - "Zend\\Text\\": "src/" + "Laminas\\Text\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4376,27 +4376,27 @@ "time": "2018-04-30T14:55:10+00:00" }, { - "name": "zendframework/zend-uri", + "name": "laminas/laminas-uri", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-uri.git", + "url": "https://github.com/laminas/laminas-uri.git", "reference": "b2785cd38fe379a784645449db86f21b7739b1ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/b2785cd38fe379a784645449db86f21b7739b1ee", + "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/b2785cd38fe379a784645449db86f21b7739b1ee", "reference": "b2785cd38fe379a784645449db86f21b7739b1ee", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-validator": "^2.10" + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-validator": "^2.10" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -4407,7 +4407,7 @@ }, "autoload": { "psr-4": { - "Zend\\Uri\\": "src/" + "Laminas\\Uri\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4423,49 +4423,49 @@ "time": "2019-02-27T21:39:04+00:00" }, { - "name": "zendframework/zend-validator", + "name": "laminas/laminas-validator", "version": "2.11.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-validator.git", + "url": "https://github.com/laminas/laminas-validator.git", "reference": "3c28dfe4e5951ba38059cea895244d9d206190b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/3c28dfe4e5951ba38059cea895244d9d206190b3", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/3c28dfe4e5951ba38059cea895244d9d206190b3", "reference": "3c28dfe4e5951ba38059cea895244d9d206190b3", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.6 || ^3.1" + "laminas/laminas-stdlib": "^2.7.6 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", "psr/http-message": "^1.0", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-db": "^2.7", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-math": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8", - "zendframework/zend-uri": "^2.5" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.5" }, "suggest": { "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", - "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", - "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", - "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", - "zendframework/zend-i18n-resources": "Translations of validator messages", - "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", - "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", - "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators" }, "type": "library", "extra": { @@ -4474,13 +4474,13 @@ "dev-develop": "2.12.x-dev" }, "zf": { - "component": "Zend\\Validator", - "config-provider": "Zend\\Validator\\ConfigProvider" + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Validator\\": "src/" + "Laminas\\Validator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4488,7 +4488,7 @@ "BSD-3-Clause" ], "description": "provides a set of commonly needed validators", - "homepage": "https://github.com/zendframework/zend-validator", + "homepage": "https://github.com/laminas/laminas-validator", "keywords": [ "validator", "zf2" @@ -4496,64 +4496,64 @@ "time": "2019-01-29T22:26:39+00:00" }, { - "name": "zendframework/zend-view", + "name": "laminas/laminas-view", "version": "2.10.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-view.git", + "url": "https://github.com/laminas/laminas-view.git", "reference": "c1a3f2043fb75b5983ab9adfc369ae396601be7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/c1a3f2043fb75b5983ab9adfc369ae396601be7e", + "url": "https://api.github.com/repos/laminas/laminas-view/zipball/c1a3f2043fb75b5983ab9adfc369ae396601be7e", "reference": "c1a3f2043fb75b5983ab9adfc369ae396601be7e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.15 || ^6.0.8", - "zendframework/zend-authentication": "^2.5", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-console": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-feed": "^2.7", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-log": "^2.7", - "zendframework/zend-modulemanager": "^2.7.1", - "zendframework/zend-mvc": "^2.7.14 || ^3.0", - "zendframework/zend-navigation": "^2.5", - "zendframework/zend-paginator": "^2.5", - "zendframework/zend-permissions-acl": "^2.6", - "zendframework/zend-router": "^3.0.1", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-uri": "^2.5" + "laminas/laminas-authentication": "^2.5", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-console": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-feed": "^2.7", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-log": "^2.7", + "laminas/laminas-modulemanager": "^2.7.1", + "laminas/laminas-mvc": "^2.7.14 || ^3.0", + "laminas/laminas-navigation": "^2.5", + "laminas/laminas-paginator": "^2.5", + "laminas/laminas-permissions-acl": "^2.6", + "laminas/laminas-router": "^3.0.1", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-uri": "^2.5" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component", - "zendframework/zend-escaper": "Zend\\Escaper component", - "zendframework/zend-feed": "Zend\\Feed component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", - "zendframework/zend-navigation": "Zend\\Navigation component", - "zendframework/zend-paginator": "Zend\\Paginator component", - "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component" + "laminas/laminas-authentication": "Laminas\\Authentication component", + "laminas/laminas-escaper": "Laminas\\Escaper component", + "laminas/laminas-feed": "Laminas\\Feed component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", + "laminas/laminas-navigation": "Laminas\\Navigation component", + "laminas/laminas-paginator": "Laminas\\Paginator component", + "laminas/laminas-permissions-acl": "Laminas\\Permissions\\Acl component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-uri": "Laminas\\Uri component" }, "bin": [ "bin/templatemap_generator.php" @@ -4567,7 +4567,7 @@ }, "autoload": { "psr-4": { - "Zend\\View\\": "src/" + "Laminas\\View\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4575,7 +4575,7 @@ "BSD-3-Clause" ], "description": "provides a system of helpers, output filters, and variable escaping", - "homepage": "https://github.com/zendframework/zend-view", + "homepage": "https://github.com/laminas/laminas-view", "keywords": [ "view", "zf2" diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromCreateProject/composer.lock b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromCreateProject/composer.lock index 4fb998ab77b34..8f575e8fd48c6 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromCreateProject/composer.lock +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromCreateProject/composer.lock @@ -1870,13 +1870,13 @@ "symfony/console": "~4.1.0", "symfony/process": "~4.1.0", "tedivm/jshrink": "~1.3.0", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-stdlib": "^2.7.7", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0" + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-stdlib": "^2.7.7", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0" }, "suggest": { "ext-imagick": "Use Image Magick >=3.0.0 as an optional alternative image processing library" @@ -2335,28 +2335,28 @@ "symfony/event-dispatcher": "~4.1.0", "tedivm/jshrink": "~1.3.0", "tubalmartin/cssmin": "4.1.1", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-config": "^2.6.0", - "zendframework/zend-console": "^2.6.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-di": "^2.6.1", - "zendframework/zend-eventmanager": "^2.6.3", - "zendframework/zend-form": "^2.10.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-i18n": "^2.7.3", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.1", - "zendframework/zend-modulemanager": "^2.7", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-serializer": "^2.7.2", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.8", - "zendframework/zend-soap": "^2.7.0", - "zendframework/zend-stdlib": "^2.7.7", - "zendframework/zend-text": "^2.6.0", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-view": "~2.10.0" + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-config": "^2.6.0", + "laminas/laminas-console": "^2.6.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-di": "^2.6.1", + "laminas/laminas-eventmanager": "^2.6.3", + "laminas/laminas-form": "^2.10.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-i18n": "^2.7.3", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.1", + "laminas/laminas-modulemanager": "^2.7", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-serializer": "^2.7.2", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.8", + "laminas/laminas-soap": "^2.7.0", + "laminas/laminas-stdlib": "^2.7.7", + "laminas/laminas-text": "^2.6.0", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0", + "laminas/laminas-view": "~2.10.0" }, "conflict": { "gene/bluefoot": "*" @@ -3485,9 +3485,9 @@ "magento/module-customer": "102.0.*", "magento/module-store": "101.0.*", "php": "~7.1.3||~7.2.0", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-session": "^2.7.3" + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-session": "^2.7.3" }, "type": "magento2-module", "autoload": { @@ -9878,33 +9878,33 @@ "tubalmartin/cssmin": "4.1.1", "vertex/product-magento-module": "3.1.0", "webonyx/graphql-php": "^0.12.6", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-config": "^2.6.0", - "zendframework/zend-console": "^2.6.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-di": "^2.6.1", - "zendframework/zend-eventmanager": "^2.6.3", - "zendframework/zend-feed": "^2.9.0", - "zendframework/zend-form": "^2.10.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-i18n": "^2.7.3", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.1", - "zendframework/zend-mail": "^2.9.0", - "zendframework/zend-modulemanager": "^2.7", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-serializer": "^2.7.2", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.8", - "zendframework/zend-session": "^2.7.3", - "zendframework/zend-soap": "^2.7.0", - "zendframework/zend-stdlib": "^2.7.7", - "zendframework/zend-text": "^2.6.0", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-view": "~2.10.0" + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-config": "^2.6.0", + "laminas/laminas-console": "^2.6.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-di": "^2.6.1", + "laminas/laminas-eventmanager": "^2.6.3", + "laminas/laminas-feed": "^2.9.0", + "laminas/laminas-form": "^2.10.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-i18n": "^2.7.3", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.1", + "laminas/laminas-mail": "^2.9.0", + "laminas/laminas-modulemanager": "^2.7", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-serializer": "^2.7.2", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.8", + "laminas/laminas-session": "^2.7.3", + "laminas/laminas-soap": "^2.7.0", + "laminas/laminas-stdlib": "^2.7.7", + "laminas/laminas-text": "^2.6.0", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0", + "laminas/laminas-view": "~2.10.0" }, "type": "metapackage", "license": [ @@ -12065,8 +12065,8 @@ "monolog/monolog": "^1.17.0", "php": "~7.1.3||~7.2.0", "psr/log": "~1.0", - "zendframework/zend-barcode": "^2.7.0", - "zendframework/zend-http": "^2.6.0" + "laminas/laminas-barcode": "^2.7.0", + "laminas/laminas-http": "^2.6.0" }, "suggest": { "magento/module-rma": "^101.1.0", @@ -12399,29 +12399,29 @@ "time": "2018-09-07T08:16:44+00:00" }, { - "name": "zendframework/zend-barcode", + "name": "laminas/laminas-barcode", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-barcode.git", + "url": "https://github.com/laminas/laminas-barcode.git", "reference": "50f24f604ef2172a0127efe91e786bc2caf2e8cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-barcode/zipball/50f24f604ef2172a0127efe91e786bc2caf2e8cf", + "url": "https://api.github.com/repos/laminas/laminas-barcode/zipball/50f24f604ef2172a0127efe91e786bc2caf2e8cf", "reference": "50f24f604ef2172a0127efe91e786bc2caf2e8cf", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-stdlib": "^2.7.7 || ^3.1", + "laminas/laminas-validator": "^2.10.1" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6 || ^3.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6 || ^3.1", "zendframework/zendpdf": "^2.0.2" }, "suggest": { @@ -12436,7 +12436,7 @@ }, "autoload": { "psr-4": { - "Zend\\Barcode\\": "src/" + "Laminas\\Barcode\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12452,38 +12452,38 @@ "time": "2017-12-11T15:30:02+00:00" }, { - "name": "zendframework/zend-captcha", + "name": "laminas/laminas-captcha", "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-captcha.git", + "url": "https://github.com/laminas/laminas-captcha.git", "reference": "37e9b6a4f632a9399eecbf2e5e325ad89083f87b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-captcha/zipball/37e9b6a4f632a9399eecbf2e5e325ad89083f87b", + "url": "https://api.github.com/repos/laminas/laminas-captcha/zipball/37e9b6a4f632a9399eecbf2e5e325ad89083f87b", "reference": "37e9b6a4f632a9399eecbf2e5e325ad89083f87b", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-math": "^2.7 || ^3.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-math": "^2.7 || ^3.0", + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-session": "^2.8", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.10.1", - "zendframework/zendservice-recaptcha": "^3.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-session": "^2.8", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.10.1", + "laminas/laminas-recaptcha": "^3.0" }, "suggest": { - "zendframework/zend-i18n-resources": "Translations of captcha messages", - "zendframework/zend-session": "Zend\\Session component", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha component" + "laminas/laminas-i18n-resources": "Translations of captcha messages", + "laminas/laminas-session": "Laminas\\Session component", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-recaptcha": "Laminas\\ReCaptcha component" }, "type": "library", "extra": { @@ -12494,7 +12494,7 @@ }, "autoload": { "psr-4": { - "Zend\\Captcha\\": "src/" + "Laminas\\Captcha\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12510,33 +12510,33 @@ "time": "2018-04-24T17:24:10+00:00" }, { - "name": "zendframework/zend-code", + "name": "laminas/laminas-code", "version": "3.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-code.git", + "url": "https://github.com/laminas/laminas-code.git", "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb", "shasum": "" }, "require": { "php": "^7.1", - "zendframework/zend-eventmanager": "^2.6 || ^3.0" + "laminas/laminas-eventmanager": "^2.6 || ^3.0" }, "require-dev": { "doctrine/annotations": "~1.0", "ext-phar": "*", "phpunit/phpunit": "^6.2.3", - "zendframework/zend-coding-standard": "^1.0.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-coding-standard": "^1.0.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "suggest": { "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "zendframework/zend-stdlib": "Zend\\Stdlib component" + "laminas/laminas-stdlib": "Laminas\\Stdlib component" }, "type": "library", "extra": { @@ -12547,7 +12547,7 @@ }, "autoload": { "psr-4": { - "Zend\\Code\\": "src/" + "Laminas\\Code\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12555,7 +12555,7 @@ "BSD-3-Clause" ], "description": "provides facilities to generate arbitrary code using an object oriented interface", - "homepage": "https://github.com/zendframework/zend-code", + "homepage": "https://github.com/laminas/laminas-code", "keywords": [ "code", "zf2" @@ -12563,36 +12563,36 @@ "time": "2018-08-13T20:36:59+00:00" }, { - "name": "zendframework/zend-config", + "name": "laminas/laminas-config", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-config.git", + "url": "https://github.com/laminas/laminas-config.git", "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", + "url": "https://api.github.com/repos/laminas/laminas-config/zipball/2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.5", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.5", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", - "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-json": "Laminas\\Json to use the Json reader or writer classes", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" }, "type": "library", "extra": { @@ -12603,7 +12603,7 @@ }, "autoload": { "psr-4": { - "Zend\\Config\\": "src/" + "Laminas\\Config\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12611,7 +12611,7 @@ "BSD-3-Clause" ], "description": "provides a nested object property based user interface for accessing this configuration data within application code", - "homepage": "https://github.com/zendframework/zend-config", + "homepage": "https://github.com/laminas/laminas-config", "keywords": [ "config", "zf2" @@ -12619,33 +12619,33 @@ "time": "2016-02-04T23:01:10+00:00" }, { - "name": "zendframework/zend-console", + "name": "laminas/laminas-console", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-console.git", + "url": "https://github.com/laminas/laminas-console.git", "reference": "e8aa08da83de3d265256c40ba45cd649115f0e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-console/zipball/e8aa08da83de3d265256c40ba45cd649115f0e18", + "url": "https://api.github.com/repos/laminas/laminas-console/zipball/e8aa08da83de3d265256c40ba45cd649115f0e18", "reference": "e8aa08da83de3d265256c40ba45cd649115f0e18", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-filter": "^2.7.2", - "zendframework/zend-json": "^2.6 || ^3.0", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-filter": "^2.7.2", + "laminas/laminas-json": "^2.6 || ^3.0", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { - "zendframework/zend-filter": "To support DefaultRouteMatcher usage", - "zendframework/zend-validator": "To support DefaultRouteMatcher usage" + "laminas/laminas-filter": "To support DefaultRouteMatcher usage", + "laminas/laminas-validator": "To support DefaultRouteMatcher usage" }, "type": "library", "extra": { @@ -12656,7 +12656,7 @@ }, "autoload": { "psr-4": { - "Zend\\Console\\": "src/" + "Laminas\\Console\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12672,31 +12672,31 @@ "time": "2018-01-25T19:08:04+00:00" }, { - "name": "zendframework/zend-crypt", + "name": "laminas/laminas-crypt", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-crypt.git", + "url": "https://github.com/laminas/laminas-crypt.git", "reference": "1b2f5600bf6262904167116fa67b58ab1457036d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-crypt/zipball/1b2f5600bf6262904167116fa67b58ab1457036d", + "url": "https://api.github.com/repos/laminas/laminas-crypt/zipball/1b2f5600bf6262904167116fa67b58ab1457036d", "reference": "1b2f5600bf6262904167116fa67b58ab1457036d", "shasum": "" }, "require": { "container-interop/container-interop": "~1.0", "php": "^5.5 || ^7.0", - "zendframework/zend-math": "^2.6", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-math": "^2.6", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0" }, "suggest": { - "ext-mcrypt": "Required for most features of Zend\\Crypt" + "ext-mcrypt": "Required for most features of Laminas\\Crypt" }, "type": "library", "extra": { @@ -12707,14 +12707,14 @@ }, "autoload": { "psr-4": { - "Zend\\Crypt\\": "src/" + "Laminas\\Crypt\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-crypt", + "homepage": "https://github.com/laminas/laminas-crypt", "keywords": [ "crypt", "zf2" @@ -12722,34 +12722,34 @@ "time": "2016-02-03T23:46:30+00:00" }, { - "name": "zendframework/zend-db", + "name": "laminas/laminas-db", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-db.git", + "url": "https://github.com/laminas/laminas-db.git", "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-eventmanager": "Zend\\EventManager component", - "zendframework/zend-hydrator": "Zend\\Hydrator component for using HydratingResultSets", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-hydrator": "Laminas\\Hydrator component for using HydratingResultSets", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { @@ -12758,13 +12758,13 @@ "dev-develop": "2.10-dev" }, "zf": { - "component": "Zend\\Db", - "config-provider": "Zend\\Db\\ConfigProvider" + "component": "Laminas\\Db", + "config-provider": "Laminas\\Db\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Db\\": "src/" + "Laminas\\Db\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12780,24 +12780,24 @@ "time": "2019-02-25T11:37:45+00:00" }, { - "name": "zendframework/zend-di", + "name": "laminas/laminas-di", "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-di.git", + "url": "https://github.com/laminas/laminas-di.git", "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-di/zipball/1fd1ba85660b5a2718741b38639dc7c4c3194b37", + "url": "https://api.github.com/repos/laminas/laminas-di/zipball/1fd1ba85660b5a2718741b38639dc7c4c3194b37", "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.5 || ^7.0", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -12812,14 +12812,14 @@ }, "autoload": { "psr-4": { - "Zend\\Di\\": "src/" + "Laminas\\Di\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-di", + "homepage": "https://github.com/laminas/laminas-di", "keywords": [ "di", "zf2" @@ -12827,16 +12827,16 @@ "time": "2016-04-25T20:58:11+00:00" }, { - "name": "zendframework/zend-diactoros", + "name": "laminas/laminas-diactoros", "version": "1.8.6", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-diactoros.git", + "url": "https://github.com/laminas/laminas-diactoros.git", "reference": "20da13beba0dde8fb648be3cc19765732790f46e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", "reference": "20da13beba0dde8fb648be3cc19765732790f46e", "shasum": "" }, @@ -12852,7 +12852,7 @@ "ext-libxml": "*", "php-http/psr7-integration-tests": "dev-master", "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", - "zendframework/zend-coding-standard": "~1.0" + "laminas/laminas-coding-standard": "~1.0" }, "type": "library", "extra": { @@ -12874,7 +12874,7 @@ "src/functions/parse_cookie_header.php" ], "psr-4": { - "Zend\\Diactoros\\": "src/" + "Laminas\\Diactoros\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12882,7 +12882,7 @@ "BSD-2-Clause" ], "description": "PSR HTTP Message implementations", - "homepage": "https://github.com/zendframework/zend-diactoros", + "homepage": "https://github.com/laminas/laminas-diactoros", "keywords": [ "http", "psr", @@ -12891,16 +12891,16 @@ "time": "2018-09-05T19:29:37+00:00" }, { - "name": "zendframework/zend-escaper", + "name": "laminas/laminas-escaper", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-escaper.git", + "url": "https://github.com/laminas/laminas-escaper.git", "reference": "31d8aafae982f9568287cb4dce987e6aff8fd074" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/31d8aafae982f9568287cb4dce987e6aff8fd074", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/31d8aafae982f9568287cb4dce987e6aff8fd074", "reference": "31d8aafae982f9568287cb4dce987e6aff8fd074", "shasum": "" }, @@ -12909,7 +12909,7 @@ }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -12920,7 +12920,7 @@ }, "autoload": { "psr-4": { - "Zend\\Escaper\\": "src/" + "Laminas\\Escaper\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12936,22 +12936,22 @@ "time": "2018-04-25T15:48:53+00:00" }, { - "name": "zendframework/zend-eventmanager", + "name": "laminas/laminas-eventmanager", "version": "2.6.4", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-eventmanager.git", + "url": "https://github.com/laminas/laminas-eventmanager.git", "reference": "d238c443220dce4b6396579c8ab2200ec25f9108" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/d238c443220dce4b6396579c8ab2200ec25f9108", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/d238c443220dce4b6396579c8ab2200ec25f9108", "reference": "d238c443220dce4b6396579c8ab2200ec25f9108", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7" + "laminas/laminas-stdlib": "^2.7" }, "require-dev": { "athletic/athletic": "dev-master", @@ -12968,14 +12968,14 @@ }, "autoload": { "psr-4": { - "Zend\\EventManager\\": "src/" + "Laminas\\EventManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-eventmanager", + "homepage": "https://github.com/laminas/laminas-eventmanager", "keywords": [ "eventmanager", "zf2" @@ -12983,41 +12983,41 @@ "time": "2017-12-12T17:48:56+00:00" }, { - "name": "zendframework/zend-feed", + "name": "laminas/laminas-feed", "version": "2.10.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-feed.git", + "url": "https://github.com/laminas/laminas-feed.git", "reference": "6641f4cf3f4586c63f83fd70b6d19966025c8888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/6641f4cf3f4586c63f83fd70b6d19966025c8888", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/6641f4cf3f4586c63f83fd70b6d19966025c8888", "reference": "6641f4cf3f4586c63f83fd70b6d19966025c8888", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5.2", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-escaper": "^2.5.2", + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-message": "^1.0.1", - "zendframework/zend-cache": "^2.7.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-http": "^2.7", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-cache": "^2.7.2", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-http": "^2.7", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { - "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Zend\\Feed\\Reader\\Http\\Psr7ResponseDecorator", - "zendframework/zend-cache": "Zend\\Cache component, for optionally caching feeds between requests", - "zendframework/zend-db": "Zend\\Db component, for use with PubSubHubbub", - "zendframework/zend-http": "Zend\\Http for PubSubHubbub, and optionally for use with Zend\\Feed\\Reader", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for easily extending ExtensionManager implementations", - "zendframework/zend-validator": "Zend\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" + "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Laminas\\Feed\\Reader\\Http\\Psr7ResponseDecorator", + "laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests", + "laminas/laminas-db": "Laminas\\Db component, for use with PubSubHubbub", + "laminas/laminas-http": "Laminas\\Http for PubSubHubbub, and optionally for use with Laminas\\Feed\\Reader", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for easily extending ExtensionManager implementations", + "laminas/laminas-validator": "Laminas\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" }, "type": "library", "extra": { @@ -13028,7 +13028,7 @@ }, "autoload": { "psr-4": { - "Zend\\Feed\\": "src/" + "Laminas\\Feed\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13044,41 +13044,41 @@ "time": "2018-08-01T13:53:20+00:00" }, { - "name": "zendframework/zend-filter", + "name": "laminas/laminas-filter", "version": "2.9.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-filter.git", + "url": "https://github.com/laminas/laminas-filter.git", "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "conflict": { - "zendframework/zend-validator": "<2.10.1" + "laminas/laminas-validator": "<2.10.1" }, "require-dev": { "pear/archive_tar": "^1.4.3", "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-factory": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-crypt": "^3.2.1", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-uri": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^3.2.1", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-uri": "^2.6" }, "suggest": { "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", - "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", - "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", - "zendframework/zend-uri": "Zend\\Uri component, for the UriNormalize filter" + "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", + "laminas/laminas-i18n": "Laminas\\I18n component for filters depending on i18n functionality", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for using the filter chain functionality", + "laminas/laminas-uri": "Laminas\\Uri component, for the UriNormalize filter" }, "type": "library", "extra": { @@ -13087,13 +13087,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\Filter", - "config-provider": "Zend\\Filter\\ConfigProvider" + "component": "Laminas\\Filter", + "config-provider": "Laminas\\Filter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Filter\\": "src/" + "Laminas\\Filter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13109,51 +13109,51 @@ "time": "2018-12-17T16:00:04+00:00" }, { - "name": "zendframework/zend-form", + "name": "laminas/laminas-form", "version": "2.13.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-form.git", + "url": "https://github.com/laminas/laminas-form.git", "reference": "c713a12ccbd43148b71c9339e171ca11e3f8a1da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/c713a12ccbd43148b71c9339e171ca11e3f8a1da", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/c713a12ccbd43148b71c9339e171ca11e3f8a1da", "reference": "c713a12ccbd43148b71c9339e171ca11e3f8a1da", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "doctrine/annotations": "~1.0", "phpunit/phpunit": "^5.7.23 || ^6.5.3", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.2", - "zendframework/zendservice-recaptcha": "^3.0.0" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.2", + "laminas/laminas-recaptcha": "^3.0.0" }, "suggest": { - "zendframework/zend-captcha": "^2.7.1, required for using CAPTCHA form elements", - "zendframework/zend-code": "^2.6 || ^3.0, required to use zend-form annotations support", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, reuired for zend-form annotations support", - "zendframework/zend-i18n": "^2.6, required when using zend-form view helpers", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", - "zendframework/zend-view": "^2.6.2, required for using the zend-form view helpers", - "zendframework/zendservice-recaptcha": "in order to use the ReCaptcha form element" + "laminas/laminas-captcha": "^2.7.1, required for using CAPTCHA form elements", + "laminas/laminas-code": "^2.6 || ^3.0, required to use zend-form annotations support", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, reuired for zend-form annotations support", + "laminas/laminas-i18n": "^2.6, required when using zend-form view helpers", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", + "laminas/laminas-view": "^2.6.2, required for using the zend-form view helpers", + "laminas/laminas-recaptcha": "in order to use the ReCaptcha form element" }, "type": "library", "extra": { @@ -13162,13 +13162,13 @@ "dev-develop": "2.14.x-dev" }, "zf": { - "component": "Zend\\Form", - "config-provider": "Zend\\Form\\ConfigProvider" + "component": "Laminas\\Form", + "config-provider": "Laminas\\Form\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Form\\": "src/" + "Laminas\\Form\\": "src/" }, "files": [ "autoload/formElementManagerPolyfill.php" @@ -13187,30 +13187,30 @@ "time": "2018-12-11T22:51:29+00:00" }, { - "name": "zendframework/zend-http", + "name": "laminas/laminas-http", "version": "2.8.4", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-http.git", + "url": "https://github.com/laminas/laminas-http.git", "reference": "d160aedc096be230af0fe9c31151b2b33ad4e807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/d160aedc096be230af0fe9c31151b2b33ad4e807", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/d160aedc096be230af0fe9c31151b2b33ad4e807", "reference": "d160aedc096be230af0fe9c31151b2b33ad4e807", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-loader": "^2.5.1", - "zendframework/zend-stdlib": "^3.1 || ^2.7.7", - "zendframework/zend-uri": "^2.5.2", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-loader": "^2.5.1", + "laminas/laminas-stdlib": "^3.1 || ^2.7.7", + "laminas/laminas-uri": "^2.5.2", + "laminas/laminas-validator": "^2.10.1" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^3.1 || ^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^3.1 || ^2.6" }, "suggest": { "paragonie/certainty": "For automated management of cacert.pem" @@ -13224,7 +13224,7 @@ }, "autoload": { "psr-4": { - "Zend\\Http\\": "src/" + "Laminas\\Http\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13242,37 +13242,37 @@ "time": "2019-02-07T17:47:08+00:00" }, { - "name": "zendframework/zend-hydrator", + "name": "laminas/laminas-hydrator", "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-hydrator.git", + "url": "https://github.com/laminas/laminas-hydrator.git", "reference": "22652e1661a5a10b3f564cf7824a2206cf5a4a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-hydrator/zipball/22652e1661a5a10b3f564cf7824a2206cf5a4a65", + "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/22652e1661a5a10b3f564cf7824a2206cf5a4a65", "reference": "22652e1661a5a10b3f564cf7824a2206cf5a4a65", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "~4.0", "squizlabs/php_codesniffer": "^2.0@dev", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-inputfilter": "^2.6", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-inputfilter": "^2.6", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", - "zendframework/zend-filter": "^2.6, to support naming strategy hydrator usage", - "zendframework/zend-serializer": "^2.6.1, to use the SerializableStrategy", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", + "laminas/laminas-filter": "^2.6, to support naming strategy hydrator usage", + "laminas/laminas-serializer": "^2.6.1, to use the SerializableStrategy", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" }, "type": "library", "extra": { @@ -13285,14 +13285,14 @@ }, "autoload": { "psr-4": { - "Zend\\Hydrator\\": "src/" + "Laminas\\Hydrator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-hydrator", + "homepage": "https://github.com/laminas/laminas-hydrator", "keywords": [ "hydrator", "zf2" @@ -13300,44 +13300,44 @@ "time": "2016-02-18T22:38:26+00:00" }, { - "name": "zendframework/zend-i18n", + "name": "laminas/laminas-i18n", "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-i18n.git", + "url": "https://github.com/laminas/laminas-i18n.git", "reference": "6d69af5a04e1a4de7250043cb1322f077a0cdb7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/6d69af5a04e1a4de7250043cb1322f077a0cdb7f", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/6d69af5a04e1a4de7250043cb1322f077a0cdb7f", "reference": "6d69af5a04e1a4de7250043cb1322f077a0cdb7f", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.3" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.3" }, "suggest": { - "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", - "zendframework/zend-filter": "You should install this package to use the provided filters", - "zendframework/zend-i18n-resources": "Translation resources", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "You should install this package to use the provided validators", - "zendframework/zend-view": "You should install this package to use the provided view helpers" + "ext-intl": "Required for most features of Laminas\\I18n; included in default builds of PHP", + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-eventmanager": "You should install this package to use the events in the translator", + "laminas/laminas-filter": "You should install this package to use the provided filters", + "laminas/laminas-i18n-resources": "Translation resources", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "You should install this package to use the provided validators", + "laminas/laminas-view": "You should install this package to use the provided view helpers" }, "type": "library", "extra": { @@ -13346,13 +13346,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\I18n", - "config-provider": "Zend\\I18n\\ConfigProvider" + "component": "Laminas\\I18n", + "config-provider": "Laminas\\I18n\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\I18n\\": "src/" + "Laminas\\I18n\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13368,30 +13368,30 @@ "time": "2018-05-16T16:39:13+00:00" }, { - "name": "zendframework/zend-inputfilter", + "name": "laminas/laminas-inputfilter", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-inputfilter.git", + "url": "https://github.com/laminas/laminas-inputfilter.git", "reference": "4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", + "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", "reference": "4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-filter": "^2.9.1", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.11" + "laminas/laminas-filter": "^2.9.1", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.11" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-message": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "suggest": { "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" @@ -13403,13 +13403,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\InputFilter", - "config-provider": "Zend\\InputFilter\\ConfigProvider" + "component": "Laminas\\InputFilter", + "config-provider": "Laminas\\InputFilter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\InputFilter\\": "src/" + "Laminas\\InputFilter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13425,16 +13425,16 @@ "time": "2019-01-30T16:58:51+00:00" }, { - "name": "zendframework/zend-json", + "name": "laminas/laminas-json", "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-json.git", + "url": "https://github.com/laminas/laminas-json.git", "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", + "url": "https://api.github.com/repos/laminas/laminas-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", "shasum": "" }, @@ -13444,16 +13444,16 @@ "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.5 || ^3.0", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.5 || ^3.0", "zendframework/zendxml": "^1.0.2" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component, required to use Zend\\Json\\Server", - "zendframework/zend-server": "Zend\\Server component, required to use Zend\\Json\\Server", - "zendframework/zend-stdlib": "Zend\\Stdlib component, for use with caching Zend\\Json\\Server responses", - "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" + "laminas/laminas-http": "Laminas\\Http component, required to use Laminas\\Json\\Server", + "laminas/laminas-server": "Laminas\\Server component, required to use Laminas\\Json\\Server", + "laminas/laminas-stdlib": "Laminas\\Stdlib component, for use with caching Laminas\\Json\\Server responses", + "zendframework/zendxml": "To support Laminas\\Json\\Json::fromXml() usage" }, "type": "library", "extra": { @@ -13464,7 +13464,7 @@ }, "autoload": { "psr-4": { - "Zend\\Json\\": "src/" + "Laminas\\Json\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13472,7 +13472,7 @@ "BSD-3-Clause" ], "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", - "homepage": "https://github.com/zendframework/zend-json", + "homepage": "https://github.com/laminas/laminas-json", "keywords": [ "json", "zf2" @@ -13480,16 +13480,16 @@ "time": "2016-02-04T21:20:26+00:00" }, { - "name": "zendframework/zend-loader", + "name": "laminas/laminas-loader", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-loader.git", + "url": "https://github.com/laminas/laminas-loader.git", "reference": "78f11749ea340f6ca316bca5958eef80b38f9b6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/78f11749ea340f6ca316bca5958eef80b38f9b6c", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/78f11749ea340f6ca316bca5958eef80b38f9b6c", "reference": "78f11749ea340f6ca316bca5958eef80b38f9b6c", "shasum": "" }, @@ -13498,7 +13498,7 @@ }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -13509,7 +13509,7 @@ }, "autoload": { "psr-4": { - "Zend\\Loader\\": "src/" + "Laminas\\Loader\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13525,24 +13525,24 @@ "time": "2018-04-30T15:20:54+00:00" }, { - "name": "zendframework/zend-log", + "name": "laminas/laminas-log", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-log.git", + "url": "https://github.com/laminas/laminas-log.git", "reference": "9cec3b092acb39963659c2f32441cccc56b3f430" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/9cec3b092acb39963659c2f32441cccc56b3f430", + "url": "https://api.github.com/repos/laminas/laminas-log/zipball/9cec3b092acb39963659c2f32441cccc56b3f430", "reference": "9cec3b092acb39963659c2f32441cccc56b3f430", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", "psr/log": "^1.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "provide": { "psr/log-implementation": "1.0.0" @@ -13550,21 +13550,21 @@ "require-dev": { "mikey179/vfsstream": "^1.6", "phpunit/phpunit": "^5.7.15 || ^6.0.8", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-filter": "^2.5", - "zendframework/zend-mail": "^2.6.1", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-filter": "^2.5", + "laminas/laminas-mail": "^2.6.1", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { "ext-mongo": "mongo extension to use Mongo writer", "ext-mongodb": "mongodb extension to use MongoDB writer", - "zendframework/zend-console": "Zend\\Console component to use the RequestID log processor", - "zendframework/zend-db": "Zend\\Db component to use the database log writer", - "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML log formatter", - "zendframework/zend-mail": "Zend\\Mail component to use the email log writer", - "zendframework/zend-validator": "Zend\\Validator component to block invalid log messages" + "laminas/laminas-console": "Laminas\\Console component to use the RequestID log processor", + "laminas/laminas-db": "Laminas\\Db component to use the database log writer", + "laminas/laminas-escaper": "Laminas\\Escaper component, for use in the XML log formatter", + "laminas/laminas-mail": "Laminas\\Mail component to use the email log writer", + "laminas/laminas-validator": "Laminas\\Validator component to block invalid log messages" }, "type": "library", "extra": { @@ -13573,13 +13573,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\Log", - "config-provider": "Zend\\Log\\ConfigProvider" + "component": "Laminas\\Log", + "config-provider": "Laminas\\Log\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Log\\": "src/" + "Laminas\\Log\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13587,7 +13587,7 @@ "BSD-3-Clause" ], "description": "component for general purpose logging", - "homepage": "https://github.com/zendframework/zend-log", + "homepage": "https://github.com/laminas/laminas-log", "keywords": [ "log", "logging", @@ -13596,16 +13596,16 @@ "time": "2018-04-09T21:59:51+00:00" }, { - "name": "zendframework/zend-mail", + "name": "laminas/laminas-mail", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mail.git", + "url": "https://github.com/laminas/laminas-mail.git", "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8", "shasum": "" }, @@ -13613,21 +13613,21 @@ "ext-iconv": "*", "php": "^5.6 || ^7.0", "true/punycode": "^2.1", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mime": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.10.2" + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-crypt": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1" }, "suggest": { - "zendframework/zend-crypt": "Crammd5 support in SMTP Auth", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" }, "type": "library", "extra": { @@ -13636,13 +13636,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\Mail", - "config-provider": "Zend\\Mail\\ConfigProvider" + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Mail\\": "src/" + "Laminas\\Mail\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13658,16 +13658,16 @@ "time": "2018-06-07T13:37:07+00:00" }, { - "name": "zendframework/zend-math", + "name": "laminas/laminas-math", "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-math.git", + "url": "https://github.com/laminas/laminas-math.git", "reference": "1abce074004dacac1a32cd54de94ad47ef960d38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", + "url": "https://api.github.com/repos/laminas/laminas-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", "reference": "1abce074004dacac1a32cd54de94ad47ef960d38", "shasum": "" }, @@ -13682,7 +13682,7 @@ "suggest": { "ext-bcmath": "If using the bcmath functionality", "ext-gmp": "If using the gmp functionality", - "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if Mcrypt extensions is unavailable" + "ircmaxell/random-lib": "Fallback random byte generator for Laminas\\Math\\Rand if Mcrypt extensions is unavailable" }, "type": "library", "extra": { @@ -13693,14 +13693,14 @@ }, "autoload": { "psr-4": { - "Zend\\Math\\": "src/" + "Laminas\\Math\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-math", + "homepage": "https://github.com/laminas/laminas-math", "keywords": [ "math", "zf2" @@ -13708,30 +13708,30 @@ "time": "2018-12-04T15:34:17+00:00" }, { - "name": "zendframework/zend-mime", + "name": "laminas/laminas-mime", "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mime.git", + "url": "https://github.com/laminas/laminas-mime.git", "reference": "52ae5fa9f12845cae749271034a2d594f0e4c6f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mime/zipball/52ae5fa9f12845cae749271034a2d594f0e4c6f2", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/52ae5fa9f12845cae749271034a2d594f0e4c6f2", "reference": "52ae5fa9f12845cae749271034a2d594f0e4c6f2", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-mail": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-mail": "^2.6" }, "suggest": { - "zendframework/zend-mail": "Zend\\Mail component" + "laminas/laminas-mail": "Laminas\\Mail component" }, "type": "library", "extra": { @@ -13742,7 +13742,7 @@ }, "autoload": { "psr-4": { - "Zend\\Mime\\": "src/" + "Laminas\\Mime\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13750,7 +13750,7 @@ "BSD-3-Clause" ], "description": "Create and parse MIME messages and parts", - "homepage": "https://github.com/zendframework/zend-mime", + "homepage": "https://github.com/laminas/laminas-mime", "keywords": [ "ZendFramework", "mime", @@ -13759,39 +13759,39 @@ "time": "2018-05-14T19:02:50+00:00" }, { - "name": "zendframework/zend-modulemanager", + "name": "laminas/laminas-modulemanager", "version": "2.8.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-modulemanager.git", + "url": "https://github.com/laminas/laminas-modulemanager.git", "reference": "394df6e12248ac430a312d4693f793ee7120baa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/394df6e12248ac430a312d4693f793ee7120baa6", + "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/394df6e12248ac430a312d4693f793ee7120baa6", "reference": "394df6e12248ac430a312d4693f793ee7120baa6", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-config": "^3.1 || ^2.6", - "zendframework/zend-eventmanager": "^3.2 || ^2.6.3", - "zendframework/zend-stdlib": "^3.1 || ^2.7" + "laminas/laminas-config": "^3.1 || ^2.6", + "laminas/laminas-eventmanager": "^3.2 || ^2.6.3", + "laminas/laminas-stdlib": "^3.1 || ^2.7" }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-console": "^2.6", - "zendframework/zend-di": "^2.6", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mvc": "^3.0 || ^2.7", - "zendframework/zend-servicemanager": "^3.0.3 || ^2.7.5" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-console": "^2.6", + "laminas/laminas-di": "^2.6", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mvc": "^3.0 || ^2.7", + "laminas/laminas-servicemanager": "^3.0.3 || ^2.7.5" }, "suggest": { - "zendframework/zend-console": "Zend\\Console component", - "zendframework/zend-loader": "Zend\\Loader component if you are not using Composer autoloading for your modules", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "laminas/laminas-console": "Laminas\\Console component", + "laminas/laminas-loader": "Laminas\\Loader component if you are not using Composer autoloading for your modules", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { @@ -13802,7 +13802,7 @@ }, "autoload": { "psr-4": { - "Zend\\ModuleManager\\": "src/" + "Laminas\\ModuleManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13810,7 +13810,7 @@ "BSD-3-Clause" ], "description": "Modular application system for zend-mvc applications", - "homepage": "https://github.com/zendframework/zend-modulemanager", + "homepage": "https://github.com/laminas/laminas-modulemanager", "keywords": [ "ZendFramework", "modulemanager", @@ -13819,73 +13819,73 @@ "time": "2017-12-02T06:11:18+00:00" }, { - "name": "zendframework/zend-mvc", + "name": "laminas/laminas-mvc", "version": "2.7.15", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mvc.git", + "url": "https://github.com/laminas/laminas-mvc.git", "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", + "url": "https://api.github.com/repos/laminas/laminas-mvc/zipball/a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.5 || ^7.0", - "zendframework/zend-console": "^2.7", - "zendframework/zend-eventmanager": "^2.6.4 || ^3.0", - "zendframework/zend-form": "^2.11", - "zendframework/zend-hydrator": "^1.1 || ^2.4", - "zendframework/zend-psr7bridge": "^0.2", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7.5 || ^3.0" + "laminas/laminas-console": "^2.7", + "laminas/laminas-eventmanager": "^2.6.4 || ^3.0", + "laminas/laminas-form": "^2.11", + "laminas/laminas-hydrator": "^1.1 || ^2.4", + "laminas/laminas-psr7bridge": "^0.2", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7.5 || ^3.0" }, "replace": { - "zendframework/zend-router": "^2.0" + "laminas/laminas-router": "^2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "1.7.*", "phpunit/phpunit": "^4.8.36", "sebastian/comparator": "^1.2.4", "sebastian/version": "^1.0.4", - "zendframework/zend-authentication": "^2.6", - "zendframework/zend-cache": "^2.8", - "zendframework/zend-di": "^2.6", - "zendframework/zend-filter": "^2.8", - "zendframework/zend-http": "^2.8", - "zendframework/zend-i18n": "^2.8", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.3", - "zendframework/zend-modulemanager": "^2.8", - "zendframework/zend-serializer": "^2.8", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.7", - "zendframework/zend-uri": "^2.6", - "zendframework/zend-validator": "^2.10", - "zendframework/zend-view": "^2.9" + "laminas/laminas-authentication": "^2.6", + "laminas/laminas-cache": "^2.8", + "laminas/laminas-di": "^2.6", + "laminas/laminas-filter": "^2.8", + "laminas/laminas-http": "^2.8", + "laminas/laminas-i18n": "^2.8", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.3", + "laminas/laminas-modulemanager": "^2.8", + "laminas/laminas-serializer": "^2.8", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.7", + "laminas/laminas-uri": "^2.6", + "laminas/laminas-validator": "^2.10", + "laminas/laminas-view": "^2.9" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component for Identity plugin", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-di": "Zend\\Di component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component for translatable segments", - "zendframework/zend-inputfilter": "Zend\\Inputfilter component", - "zendframework/zend-json": "Zend\\Json component", - "zendframework/zend-log": "Zend\\Log component", - "zendframework/zend-modulemanager": "Zend\\ModuleManager component", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager-di": "^1.0.1, if using zend-servicemanager v3 and requiring the zend-di integration", - "zendframework/zend-session": "Zend\\Session component for FlashMessenger, PRG, and FPRG plugins", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-uri": "Zend\\Uri component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zend-view": "Zend\\View component" + "laminas/laminas-authentication": "Laminas\\Authentication component for Identity plugin", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-di": "Laminas\\Di component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component for translatable segments", + "laminas/laminas-inputfilter": "Zend\\Inputfilter component", + "laminas/laminas-json": "Laminas\\Json component", + "laminas/laminas-log": "Laminas\\Log component", + "laminas/laminas-modulemanager": "Laminas\\ModuleManager component", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager-di": "^1.0.1, if using zend-servicemanager v3 and requiring the zend-di integration", + "laminas/laminas-session": "Laminas\\Session component for FlashMessenger, PRG, and FPRG plugins", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-uri": "Laminas\\Uri component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-view": "Laminas\\View component" }, "type": "library", "extra": { @@ -13899,14 +13899,14 @@ "src/autoload.php" ], "psr-4": { - "Zend\\Mvc\\": "src/" + "Laminas\\Mvc\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-mvc", + "homepage": "https://github.com/laminas/laminas-mvc", "keywords": [ "mvc", "zf2" @@ -13914,24 +13914,24 @@ "time": "2018-05-03T13:13:41+00:00" }, { - "name": "zendframework/zend-psr7bridge", + "name": "laminas/laminas-psr7bridge", "version": "0.2.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-psr7bridge.git", + "url": "https://github.com/laminas/laminas-psr7bridge.git", "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-psr7bridge/zipball/86c0b53b0c6381391c4add4a93a56e51d5c74605", + "url": "https://api.github.com/repos/laminas/laminas-psr7bridge/zipball/86c0b53b0c6381391c4add4a93a56e51d5c74605", "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605", "shasum": "" }, "require": { "php": ">=5.5", "psr/http-message": "^1.0", - "zendframework/zend-diactoros": "^1.1", - "zendframework/zend-http": "^2.5" + "laminas/laminas-diactoros": "^1.1", + "laminas/laminas-http": "^2.5" }, "require-dev": { "phpunit/phpunit": "^4.7", @@ -13946,15 +13946,15 @@ }, "autoload": { "psr-4": { - "Zend\\Psr7Bridge\\": "src/" + "Laminas\\Psr7Bridge\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "PSR-7 <-> Zend\\Http bridge", - "homepage": "https://github.com/zendframework/zend-psr7bridge", + "description": "PSR-7 <-> Laminas\\Http bridge", + "homepage": "https://github.com/laminas/laminas-psr7bridge", "keywords": [ "http", "psr", @@ -13963,33 +13963,33 @@ "time": "2016-05-10T21:44:39+00:00" }, { - "name": "zendframework/zend-serializer", + "name": "laminas/laminas-serializer", "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-serializer.git", + "url": "https://github.com/laminas/laminas-serializer.git", "reference": "0172690db48d8935edaf625c4cba38b79719892c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/0172690db48d8935edaf625c4cba38b79719892c", + "url": "https://api.github.com/repos/laminas/laminas-serializer/zipball/0172690db48d8935edaf625c4cba38b79719892c", "reference": "0172690db48d8935edaf625c4cba38b79719892c", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-json": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-json": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-math": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-math": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-math": "(^2.6 || ^3.0) To support Python Pickle serialization", - "zendframework/zend-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" + "laminas/laminas-math": "(^2.6 || ^3.0) To support Python Pickle serialization", + "laminas/laminas-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" }, "type": "library", "extra": { @@ -13998,13 +13998,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\Serializer", - "config-provider": "Zend\\Serializer\\ConfigProvider" + "component": "Laminas\\Serializer", + "config-provider": "Laminas\\Serializer\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Serializer\\": "src/" + "Laminas\\Serializer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14020,27 +14020,27 @@ "time": "2018-05-14T18:45:18+00:00" }, { - "name": "zendframework/zend-server", + "name": "laminas/laminas-server", "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-server.git", + "url": "https://github.com/laminas/laminas-server.git", "reference": "23a2e9a5599c83c05da831cb7c649e8a7809595e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-server/zipball/23a2e9a5599c83c05da831cb7c649e8a7809595e", + "url": "https://api.github.com/repos/laminas/laminas-server/zipball/23a2e9a5599c83c05da831cb7c649e8a7809595e", "reference": "23a2e9a5599c83c05da831cb7c649e8a7809595e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-code": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.5 || ^3.0" + "laminas/laminas-code": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.5 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -14051,7 +14051,7 @@ }, "autoload": { "psr-4": { - "Zend\\Server\\": "src/" + "Laminas\\Server\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14067,16 +14067,16 @@ "time": "2018-04-30T22:21:28+00:00" }, { - "name": "zendframework/zend-servicemanager", + "name": "laminas/laminas-servicemanager", "version": "2.7.11", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-servicemanager.git", + "url": "https://github.com/laminas/laminas-servicemanager.git", "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7", "shasum": "" }, @@ -14088,12 +14088,12 @@ "athletic/athletic": "dev-master", "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-di": "~2.5", - "zendframework/zend-mvc": "~2.5" + "laminas/laminas-di": "~2.5", + "laminas/laminas-mvc": "~2.5" }, "suggest": { "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services", - "zendframework/zend-di": "Zend\\Di component" + "laminas/laminas-di": "Laminas\\Di component" }, "type": "library", "extra": { @@ -14104,14 +14104,14 @@ }, "autoload": { "psr-4": { - "Zend\\ServiceManager\\": "src/" + "Laminas\\ServiceManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-servicemanager", + "homepage": "https://github.com/laminas/laminas-servicemanager", "keywords": [ "servicemanager", "zf2" @@ -14119,43 +14119,43 @@ "time": "2018-06-22T14:49:54+00:00" }, { - "name": "zendframework/zend-session", + "name": "laminas/laminas-session", "version": "2.8.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-session.git", + "url": "https://github.com/laminas/laminas-session.git", "reference": "2cfd90e1a2f6b066b9f908599251d8f64f07021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-session/zipball/2cfd90e1a2f6b066b9f908599251d8f64f07021b", + "url": "https://api.github.com/repos/laminas/laminas-session/zipball/2cfd90e1a2f6b066b9f908599251d8f64f07021b", "reference": "2cfd90e1a2f6b066b9f908599251d8f64f07021b", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "container-interop/container-interop": "^1.1", "mongodb/mongodb": "^1.0.1", "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", "phpunit/phpunit": "^5.7.5 || >=6.0.13 <6.5.0", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.7", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.7", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6" }, "suggest": { "mongodb/mongodb": "If you want to use the MongoDB session save handler", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-db": "Zend\\Db component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "Zend\\Validator component" + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-db": "Laminas\\Db component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "Laminas\\Validator component" }, "type": "library", "extra": { @@ -14164,13 +14164,13 @@ "dev-develop": "2.9-dev" }, "zf": { - "component": "Zend\\Session", - "config-provider": "Zend\\Session\\ConfigProvider" + "component": "Laminas\\Session", + "config-provider": "Laminas\\Session\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Session\\": "src/" + "Laminas\\Session\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14186,34 +14186,34 @@ "time": "2018-02-22T16:33:54+00:00" }, { - "name": "zendframework/zend-soap", + "name": "laminas/laminas-soap", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-soap.git", + "url": "https://github.com/laminas/laminas-soap.git", "reference": "af03c32f0db2b899b3df8cfe29aeb2b49857d284" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/af03c32f0db2b899b3df8cfe29aeb2b49857d284", + "url": "https://api.github.com/repos/laminas/laminas-soap/zipball/af03c32f0db2b899b3df8cfe29aeb2b49857d284", "reference": "af03c32f0db2b899b3df8cfe29aeb2b49857d284", "shasum": "" }, "require": { "ext-soap": "*", "php": "^5.6 || ^7.0", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-uri": "^2.5.2" + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-uri": "^2.5.2" }, "require-dev": { "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-http": "^2.5.4" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-http": "^2.5.4" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component" + "laminas/laminas-http": "Laminas\\Http component" }, "type": "library", "extra": { @@ -14224,14 +14224,14 @@ }, "autoload": { "psr-4": { - "Zend\\Soap\\": "src/" + "Laminas\\Soap\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-soap", + "homepage": "https://github.com/laminas/laminas-soap", "keywords": [ "soap", "zf2" @@ -14239,39 +14239,39 @@ "time": "2018-01-29T17:51:26+00:00" }, { - "name": "zendframework/zend-stdlib", + "name": "laminas/laminas-stdlib", "version": "2.7.7", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-stdlib.git", + "url": "https://github.com/laminas/laminas-stdlib.git", "reference": "0e44eb46788f65e09e077eb7f44d2659143bcc1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/0e44eb46788f65e09e077eb7f44d2659143bcc1f", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/0e44eb46788f65e09e077eb7f44d2659143bcc1f", "reference": "0e44eb46788f65e09e077eb7f44d2659143bcc1f", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-hydrator": "~1.1" + "laminas/laminas-hydrator": "~1.1" }, "require-dev": { "athletic/athletic": "~0.1", "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-config": "~2.5", - "zendframework/zend-eventmanager": "~2.5", - "zendframework/zend-filter": "~2.5", - "zendframework/zend-inputfilter": "~2.5", - "zendframework/zend-serializer": "~2.5", - "zendframework/zend-servicemanager": "~2.5" + "laminas/laminas-config": "~2.5", + "laminas/laminas-eventmanager": "~2.5", + "laminas/laminas-filter": "~2.5", + "laminas/laminas-inputfilter": "~2.5", + "laminas/laminas-serializer": "~2.5", + "laminas/laminas-servicemanager": "~2.5" }, "suggest": { - "zendframework/zend-eventmanager": "To support aggregate hydrator usage", - "zendframework/zend-filter": "To support naming strategy hydrator usage", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + "laminas/laminas-eventmanager": "To support aggregate hydrator usage", + "laminas/laminas-filter": "To support naming strategy hydrator usage", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager": "To support hydrator plugin manager usage" }, "type": "library", "extra": { @@ -14283,14 +14283,14 @@ }, "autoload": { "psr-4": { - "Zend\\Stdlib\\": "src/" + "Laminas\\Stdlib\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-stdlib", + "homepage": "https://github.com/laminas/laminas-stdlib", "keywords": [ "stdlib", "zf2" @@ -14298,28 +14298,28 @@ "time": "2016-04-12T21:17:31+00:00" }, { - "name": "zendframework/zend-text", + "name": "laminas/laminas-text", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-text.git", + "url": "https://github.com/laminas/laminas-text.git", "reference": "ca987dd4594f5f9508771fccd82c89bc7fbb39ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-text/zipball/ca987dd4594f5f9508771fccd82c89bc7fbb39ac", + "url": "https://api.github.com/repos/laminas/laminas-text/zipball/ca987dd4594f5f9508771fccd82c89bc7fbb39ac", "reference": "ca987dd4594f5f9508771fccd82c89bc7fbb39ac", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6" }, "type": "library", "extra": { @@ -14330,7 +14330,7 @@ }, "autoload": { "psr-4": { - "Zend\\Text\\": "src/" + "Laminas\\Text\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14346,27 +14346,27 @@ "time": "2018-04-30T14:55:10+00:00" }, { - "name": "zendframework/zend-uri", + "name": "laminas/laminas-uri", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-uri.git", + "url": "https://github.com/laminas/laminas-uri.git", "reference": "b2785cd38fe379a784645449db86f21b7739b1ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/b2785cd38fe379a784645449db86f21b7739b1ee", + "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/b2785cd38fe379a784645449db86f21b7739b1ee", "reference": "b2785cd38fe379a784645449db86f21b7739b1ee", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-validator": "^2.10" + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-validator": "^2.10" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -14377,7 +14377,7 @@ }, "autoload": { "psr-4": { - "Zend\\Uri\\": "src/" + "Laminas\\Uri\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14393,49 +14393,49 @@ "time": "2019-02-27T21:39:04+00:00" }, { - "name": "zendframework/zend-validator", + "name": "laminas/laminas-validator", "version": "2.11.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-validator.git", + "url": "https://github.com/laminas/laminas-validator.git", "reference": "3c28dfe4e5951ba38059cea895244d9d206190b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/3c28dfe4e5951ba38059cea895244d9d206190b3", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/3c28dfe4e5951ba38059cea895244d9d206190b3", "reference": "3c28dfe4e5951ba38059cea895244d9d206190b3", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.6 || ^3.1" + "laminas/laminas-stdlib": "^2.7.6 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", "psr/http-message": "^1.0", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-db": "^2.7", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-math": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8", - "zendframework/zend-uri": "^2.5" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.5" }, "suggest": { "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", - "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", - "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", - "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", - "zendframework/zend-i18n-resources": "Translations of validator messages", - "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", - "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", - "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators" }, "type": "library", "extra": { @@ -14444,13 +14444,13 @@ "dev-develop": "2.12.x-dev" }, "zf": { - "component": "Zend\\Validator", - "config-provider": "Zend\\Validator\\ConfigProvider" + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Validator\\": "src/" + "Laminas\\Validator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14458,7 +14458,7 @@ "BSD-3-Clause" ], "description": "provides a set of commonly needed validators", - "homepage": "https://github.com/zendframework/zend-validator", + "homepage": "https://github.com/laminas/laminas-validator", "keywords": [ "validator", "zf2" @@ -14466,64 +14466,64 @@ "time": "2019-01-29T22:26:39+00:00" }, { - "name": "zendframework/zend-view", + "name": "laminas/laminas-view", "version": "2.10.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-view.git", + "url": "https://github.com/laminas/laminas-view.git", "reference": "c1a3f2043fb75b5983ab9adfc369ae396601be7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/c1a3f2043fb75b5983ab9adfc369ae396601be7e", + "url": "https://api.github.com/repos/laminas/laminas-view/zipball/c1a3f2043fb75b5983ab9adfc369ae396601be7e", "reference": "c1a3f2043fb75b5983ab9adfc369ae396601be7e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.15 || ^6.0.8", - "zendframework/zend-authentication": "^2.5", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-console": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-feed": "^2.7", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-log": "^2.7", - "zendframework/zend-modulemanager": "^2.7.1", - "zendframework/zend-mvc": "^2.7.14 || ^3.0", - "zendframework/zend-navigation": "^2.5", - "zendframework/zend-paginator": "^2.5", - "zendframework/zend-permissions-acl": "^2.6", - "zendframework/zend-router": "^3.0.1", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-uri": "^2.5" + "laminas/laminas-authentication": "^2.5", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-console": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-feed": "^2.7", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-log": "^2.7", + "laminas/laminas-modulemanager": "^2.7.1", + "laminas/laminas-mvc": "^2.7.14 || ^3.0", + "laminas/laminas-navigation": "^2.5", + "laminas/laminas-paginator": "^2.5", + "laminas/laminas-permissions-acl": "^2.6", + "laminas/laminas-router": "^3.0.1", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-uri": "^2.5" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component", - "zendframework/zend-escaper": "Zend\\Escaper component", - "zendframework/zend-feed": "Zend\\Feed component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", - "zendframework/zend-navigation": "Zend\\Navigation component", - "zendframework/zend-paginator": "Zend\\Paginator component", - "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component" + "laminas/laminas-authentication": "Laminas\\Authentication component", + "laminas/laminas-escaper": "Laminas\\Escaper component", + "laminas/laminas-feed": "Laminas\\Feed component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", + "laminas/laminas-navigation": "Laminas\\Navigation component", + "laminas/laminas-paginator": "Laminas\\Paginator component", + "laminas/laminas-permissions-acl": "Laminas\\Permissions\\Acl component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-uri": "Laminas\\Uri component" }, "bin": [ "bin/templatemap_generator.php" @@ -14537,7 +14537,7 @@ }, "autoload": { "psr-4": { - "Zend\\View\\": "src/" + "Laminas\\View\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14545,7 +14545,7 @@ "BSD-3-Clause" ], "description": "provides a system of helpers, output filters, and variable escaping", - "homepage": "https://github.com/zendframework/zend-view", + "homepage": "https://github.com/laminas/laminas-view", "keywords": [ "view", "zf2" diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testSkeleton/composer.lock b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testSkeleton/composer.lock index 36a98e6cd9596..a3e60ad227bf7 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testSkeleton/composer.lock +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testSkeleton/composer.lock @@ -1870,13 +1870,13 @@ "symfony/console": "~4.1.0", "symfony/process": "~4.1.0", "tedivm/jshrink": "~1.3.0", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-stdlib": "^2.7.7", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0" + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-stdlib": "^2.7.7", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0" }, "suggest": { "ext-imagick": "Use Image Magick >=3.0.0 as an optional alternative image processing library" @@ -2335,28 +2335,28 @@ "symfony/event-dispatcher": "~4.1.0", "tedivm/jshrink": "~1.3.0", "tubalmartin/cssmin": "4.1.1", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-config": "^2.6.0", - "zendframework/zend-console": "^2.6.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-di": "^2.6.1", - "zendframework/zend-eventmanager": "^2.6.3", - "zendframework/zend-form": "^2.10.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-i18n": "^2.7.3", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.1", - "zendframework/zend-modulemanager": "^2.7", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-serializer": "^2.7.2", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.8", - "zendframework/zend-soap": "^2.7.0", - "zendframework/zend-stdlib": "^2.7.7", - "zendframework/zend-text": "^2.6.0", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-view": "~2.10.0" + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-config": "^2.6.0", + "laminas/laminas-console": "^2.6.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-di": "^2.6.1", + "laminas/laminas-eventmanager": "^2.6.3", + "laminas/laminas-form": "^2.10.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-i18n": "^2.7.3", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.1", + "laminas/laminas-modulemanager": "^2.7", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-serializer": "^2.7.2", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.8", + "laminas/laminas-soap": "^2.7.0", + "laminas/laminas-stdlib": "^2.7.7", + "laminas/laminas-text": "^2.6.0", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0", + "laminas/laminas-view": "~2.10.0" }, "conflict": { "gene/bluefoot": "*" @@ -3485,9 +3485,9 @@ "magento/module-customer": "102.0.*", "magento/module-store": "101.0.*", "php": "~7.1.3||~7.2.0", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-session": "^2.7.3" + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-session": "^2.7.3" }, "type": "magento2-module", "autoload": { @@ -9878,33 +9878,33 @@ "tubalmartin/cssmin": "4.1.1", "vertex/product-magento-module": "3.1.0", "webonyx/graphql-php": "^0.12.6", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-config": "^2.6.0", - "zendframework/zend-console": "^2.6.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-di": "^2.6.1", - "zendframework/zend-eventmanager": "^2.6.3", - "zendframework/zend-feed": "^2.9.0", - "zendframework/zend-form": "^2.10.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-i18n": "^2.7.3", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.1", - "zendframework/zend-mail": "^2.9.0", - "zendframework/zend-modulemanager": "^2.7", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-serializer": "^2.7.2", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.8", - "zendframework/zend-session": "^2.7.3", - "zendframework/zend-soap": "^2.7.0", - "zendframework/zend-stdlib": "^2.7.7", - "zendframework/zend-text": "^2.6.0", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-view": "~2.10.0" + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-config": "^2.6.0", + "laminas/laminas-console": "^2.6.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-di": "^2.6.1", + "laminas/laminas-eventmanager": "^2.6.3", + "laminas/laminas-feed": "^2.9.0", + "laminas/laminas-form": "^2.10.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-i18n": "^2.7.3", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.1", + "laminas/laminas-mail": "^2.9.0", + "laminas/laminas-modulemanager": "^2.7", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-serializer": "^2.7.2", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.8", + "laminas/laminas-session": "^2.7.3", + "laminas/laminas-soap": "^2.7.0", + "laminas/laminas-stdlib": "^2.7.7", + "laminas/laminas-text": "^2.6.0", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0", + "laminas/laminas-view": "~2.10.0" }, "type": "metapackage", "license": [ @@ -12065,8 +12065,8 @@ "monolog/monolog": "^1.17.0", "php": "~7.1.3||~7.2.0", "psr/log": "~1.0", - "zendframework/zend-barcode": "^2.7.0", - "zendframework/zend-http": "^2.6.0" + "laminas/laminas-barcode": "^2.7.0", + "laminas/laminas-http": "^2.6.0" }, "suggest": { "magento/module-rma": "^101.1.0", @@ -12399,29 +12399,29 @@ "time": "2018-09-07T08:16:44+00:00" }, { - "name": "zendframework/zend-barcode", + "name": "laminas/laminas-barcode", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-barcode.git", + "url": "https://github.com/laminas/laminas-barcode.git", "reference": "50f24f604ef2172a0127efe91e786bc2caf2e8cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-barcode/zipball/50f24f604ef2172a0127efe91e786bc2caf2e8cf", + "url": "https://api.github.com/repos/laminas/laminas-barcode/zipball/50f24f604ef2172a0127efe91e786bc2caf2e8cf", "reference": "50f24f604ef2172a0127efe91e786bc2caf2e8cf", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-stdlib": "^2.7.7 || ^3.1", + "laminas/laminas-validator": "^2.10.1" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6 || ^3.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6 || ^3.1", "zendframework/zendpdf": "^2.0.2" }, "suggest": { @@ -12436,7 +12436,7 @@ }, "autoload": { "psr-4": { - "Zend\\Barcode\\": "src/" + "Laminas\\Barcode\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12452,38 +12452,38 @@ "time": "2017-12-11T15:30:02+00:00" }, { - "name": "zendframework/zend-captcha", + "name": "laminas/laminas-captcha", "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-captcha.git", + "url": "https://github.com/laminas/laminas-captcha.git", "reference": "37e9b6a4f632a9399eecbf2e5e325ad89083f87b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-captcha/zipball/37e9b6a4f632a9399eecbf2e5e325ad89083f87b", + "url": "https://api.github.com/repos/laminas/laminas-captcha/zipball/37e9b6a4f632a9399eecbf2e5e325ad89083f87b", "reference": "37e9b6a4f632a9399eecbf2e5e325ad89083f87b", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-math": "^2.7 || ^3.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-math": "^2.7 || ^3.0", + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-session": "^2.8", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.10.1", - "zendframework/zendservice-recaptcha": "^3.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-session": "^2.8", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.10.1", + "laminas/laminas-recaptcha": "^3.0" }, "suggest": { - "zendframework/zend-i18n-resources": "Translations of captcha messages", - "zendframework/zend-session": "Zend\\Session component", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha component" + "laminas/laminas-i18n-resources": "Translations of captcha messages", + "laminas/laminas-session": "Laminas\\Session component", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-recaptcha": "Laminas\\ReCaptcha component" }, "type": "library", "extra": { @@ -12494,7 +12494,7 @@ }, "autoload": { "psr-4": { - "Zend\\Captcha\\": "src/" + "Laminas\\Captcha\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12510,33 +12510,33 @@ "time": "2018-04-24T17:24:10+00:00" }, { - "name": "zendframework/zend-code", + "name": "laminas/laminas-code", "version": "3.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-code.git", + "url": "https://github.com/laminas/laminas-code.git", "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/c21db169075c6ec4b342149f446e7b7b724f95eb", "reference": "c21db169075c6ec4b342149f446e7b7b724f95eb", "shasum": "" }, "require": { "php": "^7.1", - "zendframework/zend-eventmanager": "^2.6 || ^3.0" + "laminas/laminas-eventmanager": "^2.6 || ^3.0" }, "require-dev": { "doctrine/annotations": "~1.0", "ext-phar": "*", "phpunit/phpunit": "^6.2.3", - "zendframework/zend-coding-standard": "^1.0.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-coding-standard": "^1.0.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "suggest": { "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "zendframework/zend-stdlib": "Zend\\Stdlib component" + "laminas/laminas-stdlib": "Laminas\\Stdlib component" }, "type": "library", "extra": { @@ -12547,7 +12547,7 @@ }, "autoload": { "psr-4": { - "Zend\\Code\\": "src/" + "Laminas\\Code\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12555,7 +12555,7 @@ "BSD-3-Clause" ], "description": "provides facilities to generate arbitrary code using an object oriented interface", - "homepage": "https://github.com/zendframework/zend-code", + "homepage": "https://github.com/laminas/laminas-code", "keywords": [ "code", "zf2" @@ -12563,36 +12563,36 @@ "time": "2018-08-13T20:36:59+00:00" }, { - "name": "zendframework/zend-config", + "name": "laminas/laminas-config", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-config.git", + "url": "https://github.com/laminas/laminas-config.git", "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", + "url": "https://api.github.com/repos/laminas/laminas-config/zipball/2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.5", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.5", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", - "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-json": "Laminas\\Json to use the Json reader or writer classes", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" }, "type": "library", "extra": { @@ -12603,7 +12603,7 @@ }, "autoload": { "psr-4": { - "Zend\\Config\\": "src/" + "Laminas\\Config\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12611,7 +12611,7 @@ "BSD-3-Clause" ], "description": "provides a nested object property based user interface for accessing this configuration data within application code", - "homepage": "https://github.com/zendframework/zend-config", + "homepage": "https://github.com/laminas/laminas-config", "keywords": [ "config", "zf2" @@ -12619,33 +12619,33 @@ "time": "2016-02-04T23:01:10+00:00" }, { - "name": "zendframework/zend-console", + "name": "laminas/laminas-console", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-console.git", + "url": "https://github.com/laminas/laminas-console.git", "reference": "e8aa08da83de3d265256c40ba45cd649115f0e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-console/zipball/e8aa08da83de3d265256c40ba45cd649115f0e18", + "url": "https://api.github.com/repos/laminas/laminas-console/zipball/e8aa08da83de3d265256c40ba45cd649115f0e18", "reference": "e8aa08da83de3d265256c40ba45cd649115f0e18", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-filter": "^2.7.2", - "zendframework/zend-json": "^2.6 || ^3.0", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-filter": "^2.7.2", + "laminas/laminas-json": "^2.6 || ^3.0", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { - "zendframework/zend-filter": "To support DefaultRouteMatcher usage", - "zendframework/zend-validator": "To support DefaultRouteMatcher usage" + "laminas/laminas-filter": "To support DefaultRouteMatcher usage", + "laminas/laminas-validator": "To support DefaultRouteMatcher usage" }, "type": "library", "extra": { @@ -12656,7 +12656,7 @@ }, "autoload": { "psr-4": { - "Zend\\Console\\": "src/" + "Laminas\\Console\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12672,31 +12672,31 @@ "time": "2018-01-25T19:08:04+00:00" }, { - "name": "zendframework/zend-crypt", + "name": "laminas/laminas-crypt", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-crypt.git", + "url": "https://github.com/laminas/laminas-crypt.git", "reference": "1b2f5600bf6262904167116fa67b58ab1457036d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-crypt/zipball/1b2f5600bf6262904167116fa67b58ab1457036d", + "url": "https://api.github.com/repos/laminas/laminas-crypt/zipball/1b2f5600bf6262904167116fa67b58ab1457036d", "reference": "1b2f5600bf6262904167116fa67b58ab1457036d", "shasum": "" }, "require": { "container-interop/container-interop": "~1.0", "php": "^5.5 || ^7.0", - "zendframework/zend-math": "^2.6", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-math": "^2.6", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0" }, "suggest": { - "ext-mcrypt": "Required for most features of Zend\\Crypt" + "ext-mcrypt": "Required for most features of Laminas\\Crypt" }, "type": "library", "extra": { @@ -12707,14 +12707,14 @@ }, "autoload": { "psr-4": { - "Zend\\Crypt\\": "src/" + "Laminas\\Crypt\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-crypt", + "homepage": "https://github.com/laminas/laminas-crypt", "keywords": [ "crypt", "zf2" @@ -12722,34 +12722,34 @@ "time": "2016-02-03T23:46:30+00:00" }, { - "name": "zendframework/zend-db", + "name": "laminas/laminas-db", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-db.git", + "url": "https://github.com/laminas/laminas-db.git", "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-eventmanager": "Zend\\EventManager component", - "zendframework/zend-hydrator": "Zend\\Hydrator component for using HydratingResultSets", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-hydrator": "Laminas\\Hydrator component for using HydratingResultSets", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { @@ -12758,13 +12758,13 @@ "dev-develop": "2.10-dev" }, "zf": { - "component": "Zend\\Db", - "config-provider": "Zend\\Db\\ConfigProvider" + "component": "Laminas\\Db", + "config-provider": "Laminas\\Db\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Db\\": "src/" + "Laminas\\Db\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12780,24 +12780,24 @@ "time": "2019-02-25T11:37:45+00:00" }, { - "name": "zendframework/zend-di", + "name": "laminas/laminas-di", "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-di.git", + "url": "https://github.com/laminas/laminas-di.git", "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-di/zipball/1fd1ba85660b5a2718741b38639dc7c4c3194b37", + "url": "https://api.github.com/repos/laminas/laminas-di/zipball/1fd1ba85660b5a2718741b38639dc7c4c3194b37", "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.5 || ^7.0", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -12812,14 +12812,14 @@ }, "autoload": { "psr-4": { - "Zend\\Di\\": "src/" + "Laminas\\Di\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-di", + "homepage": "https://github.com/laminas/laminas-di", "keywords": [ "di", "zf2" @@ -12827,16 +12827,16 @@ "time": "2016-04-25T20:58:11+00:00" }, { - "name": "zendframework/zend-diactoros", + "name": "laminas/laminas-diactoros", "version": "1.8.6", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-diactoros.git", + "url": "https://github.com/laminas/laminas-diactoros.git", "reference": "20da13beba0dde8fb648be3cc19765732790f46e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", "reference": "20da13beba0dde8fb648be3cc19765732790f46e", "shasum": "" }, @@ -12852,7 +12852,7 @@ "ext-libxml": "*", "php-http/psr7-integration-tests": "dev-master", "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", - "zendframework/zend-coding-standard": "~1.0" + "laminas/laminas-coding-standard": "~1.0" }, "type": "library", "extra": { @@ -12874,7 +12874,7 @@ "src/functions/parse_cookie_header.php" ], "psr-4": { - "Zend\\Diactoros\\": "src/" + "Laminas\\Diactoros\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12882,7 +12882,7 @@ "BSD-2-Clause" ], "description": "PSR HTTP Message implementations", - "homepage": "https://github.com/zendframework/zend-diactoros", + "homepage": "https://github.com/laminas/laminas-diactoros", "keywords": [ "http", "psr", @@ -12891,16 +12891,16 @@ "time": "2018-09-05T19:29:37+00:00" }, { - "name": "zendframework/zend-escaper", + "name": "laminas/laminas-escaper", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-escaper.git", + "url": "https://github.com/laminas/laminas-escaper.git", "reference": "31d8aafae982f9568287cb4dce987e6aff8fd074" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/31d8aafae982f9568287cb4dce987e6aff8fd074", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/31d8aafae982f9568287cb4dce987e6aff8fd074", "reference": "31d8aafae982f9568287cb4dce987e6aff8fd074", "shasum": "" }, @@ -12909,7 +12909,7 @@ }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -12920,7 +12920,7 @@ }, "autoload": { "psr-4": { - "Zend\\Escaper\\": "src/" + "Laminas\\Escaper\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -12936,22 +12936,22 @@ "time": "2018-04-25T15:48:53+00:00" }, { - "name": "zendframework/zend-eventmanager", + "name": "laminas/laminas-eventmanager", "version": "2.6.4", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-eventmanager.git", + "url": "https://github.com/laminas/laminas-eventmanager.git", "reference": "d238c443220dce4b6396579c8ab2200ec25f9108" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/d238c443220dce4b6396579c8ab2200ec25f9108", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/d238c443220dce4b6396579c8ab2200ec25f9108", "reference": "d238c443220dce4b6396579c8ab2200ec25f9108", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7" + "laminas/laminas-stdlib": "^2.7" }, "require-dev": { "athletic/athletic": "dev-master", @@ -12968,14 +12968,14 @@ }, "autoload": { "psr-4": { - "Zend\\EventManager\\": "src/" + "Laminas\\EventManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-eventmanager", + "homepage": "https://github.com/laminas/laminas-eventmanager", "keywords": [ "eventmanager", "zf2" @@ -12983,41 +12983,41 @@ "time": "2017-12-12T17:48:56+00:00" }, { - "name": "zendframework/zend-feed", + "name": "laminas/laminas-feed", "version": "2.10.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-feed.git", + "url": "https://github.com/laminas/laminas-feed.git", "reference": "6641f4cf3f4586c63f83fd70b6d19966025c8888" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/6641f4cf3f4586c63f83fd70b6d19966025c8888", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/6641f4cf3f4586c63f83fd70b6d19966025c8888", "reference": "6641f4cf3f4586c63f83fd70b6d19966025c8888", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5.2", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-escaper": "^2.5.2", + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-message": "^1.0.1", - "zendframework/zend-cache": "^2.7.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-http": "^2.7", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-cache": "^2.7.2", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-http": "^2.7", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { - "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Zend\\Feed\\Reader\\Http\\Psr7ResponseDecorator", - "zendframework/zend-cache": "Zend\\Cache component, for optionally caching feeds between requests", - "zendframework/zend-db": "Zend\\Db component, for use with PubSubHubbub", - "zendframework/zend-http": "Zend\\Http for PubSubHubbub, and optionally for use with Zend\\Feed\\Reader", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for easily extending ExtensionManager implementations", - "zendframework/zend-validator": "Zend\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" + "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Laminas\\Feed\\Reader\\Http\\Psr7ResponseDecorator", + "laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests", + "laminas/laminas-db": "Laminas\\Db component, for use with PubSubHubbub", + "laminas/laminas-http": "Laminas\\Http for PubSubHubbub, and optionally for use with Laminas\\Feed\\Reader", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for easily extending ExtensionManager implementations", + "laminas/laminas-validator": "Laminas\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" }, "type": "library", "extra": { @@ -13028,7 +13028,7 @@ }, "autoload": { "psr-4": { - "Zend\\Feed\\": "src/" + "Laminas\\Feed\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13044,41 +13044,41 @@ "time": "2018-08-01T13:53:20+00:00" }, { - "name": "zendframework/zend-filter", + "name": "laminas/laminas-filter", "version": "2.9.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-filter.git", + "url": "https://github.com/laminas/laminas-filter.git", "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" + "laminas/laminas-stdlib": "^2.7.7 || ^3.1" }, "conflict": { - "zendframework/zend-validator": "<2.10.1" + "laminas/laminas-validator": "<2.10.1" }, "require-dev": { "pear/archive_tar": "^1.4.3", "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-factory": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-crypt": "^3.2.1", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-uri": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^3.2.1", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-uri": "^2.6" }, "suggest": { "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", - "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", - "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", - "zendframework/zend-uri": "Zend\\Uri component, for the UriNormalize filter" + "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", + "laminas/laminas-i18n": "Laminas\\I18n component for filters depending on i18n functionality", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for using the filter chain functionality", + "laminas/laminas-uri": "Laminas\\Uri component, for the UriNormalize filter" }, "type": "library", "extra": { @@ -13087,13 +13087,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\Filter", - "config-provider": "Zend\\Filter\\ConfigProvider" + "component": "Laminas\\Filter", + "config-provider": "Laminas\\Filter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Filter\\": "src/" + "Laminas\\Filter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13109,51 +13109,51 @@ "time": "2018-12-17T16:00:04+00:00" }, { - "name": "zendframework/zend-form", + "name": "laminas/laminas-form", "version": "2.13.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-form.git", + "url": "https://github.com/laminas/laminas-form.git", "reference": "c713a12ccbd43148b71c9339e171ca11e3f8a1da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/c713a12ccbd43148b71c9339e171ca11e3f8a1da", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/c713a12ccbd43148b71c9339e171ca11e3f8a1da", "reference": "c713a12ccbd43148b71c9339e171ca11e3f8a1da", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "doctrine/annotations": "~1.0", "phpunit/phpunit": "^5.7.23 || ^6.5.3", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.2", - "zendframework/zendservice-recaptcha": "^3.0.0" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.2", + "laminas/laminas-recaptcha": "^3.0.0" }, "suggest": { - "zendframework/zend-captcha": "^2.7.1, required for using CAPTCHA form elements", - "zendframework/zend-code": "^2.6 || ^3.0, required to use zend-form annotations support", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, reuired for zend-form annotations support", - "zendframework/zend-i18n": "^2.6, required when using zend-form view helpers", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", - "zendframework/zend-view": "^2.6.2, required for using the zend-form view helpers", - "zendframework/zendservice-recaptcha": "in order to use the ReCaptcha form element" + "laminas/laminas-captcha": "^2.7.1, required for using CAPTCHA form elements", + "laminas/laminas-code": "^2.6 || ^3.0, required to use zend-form annotations support", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, reuired for zend-form annotations support", + "laminas/laminas-i18n": "^2.6, required when using zend-form view helpers", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", + "laminas/laminas-view": "^2.6.2, required for using the zend-form view helpers", + "laminas/laminas-recaptcha": "in order to use the ReCaptcha form element" }, "type": "library", "extra": { @@ -13162,13 +13162,13 @@ "dev-develop": "2.14.x-dev" }, "zf": { - "component": "Zend\\Form", - "config-provider": "Zend\\Form\\ConfigProvider" + "component": "Laminas\\Form", + "config-provider": "Laminas\\Form\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Form\\": "src/" + "Laminas\\Form\\": "src/" }, "files": [ "autoload/formElementManagerPolyfill.php" @@ -13187,30 +13187,30 @@ "time": "2018-12-11T22:51:29+00:00" }, { - "name": "zendframework/zend-http", + "name": "laminas/laminas-http", "version": "2.8.4", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-http.git", + "url": "https://github.com/laminas/laminas-http.git", "reference": "d160aedc096be230af0fe9c31151b2b33ad4e807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/d160aedc096be230af0fe9c31151b2b33ad4e807", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/d160aedc096be230af0fe9c31151b2b33ad4e807", "reference": "d160aedc096be230af0fe9c31151b2b33ad4e807", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-loader": "^2.5.1", - "zendframework/zend-stdlib": "^3.1 || ^2.7.7", - "zendframework/zend-uri": "^2.5.2", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-loader": "^2.5.1", + "laminas/laminas-stdlib": "^3.1 || ^2.7.7", + "laminas/laminas-uri": "^2.5.2", + "laminas/laminas-validator": "^2.10.1" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^3.1 || ^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^3.1 || ^2.6" }, "suggest": { "paragonie/certainty": "For automated management of cacert.pem" @@ -13224,7 +13224,7 @@ }, "autoload": { "psr-4": { - "Zend\\Http\\": "src/" + "Laminas\\Http\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13242,37 +13242,37 @@ "time": "2019-02-07T17:47:08+00:00" }, { - "name": "zendframework/zend-hydrator", + "name": "laminas/laminas-hydrator", "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-hydrator.git", + "url": "https://github.com/laminas/laminas-hydrator.git", "reference": "22652e1661a5a10b3f564cf7824a2206cf5a4a65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-hydrator/zipball/22652e1661a5a10b3f564cf7824a2206cf5a4a65", + "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/22652e1661a5a10b3f564cf7824a2206cf5a4a65", "reference": "22652e1661a5a10b3f564cf7824a2206cf5a4a65", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "~4.0", "squizlabs/php_codesniffer": "^2.0@dev", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-inputfilter": "^2.6", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-inputfilter": "^2.6", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", - "zendframework/zend-filter": "^2.6, to support naming strategy hydrator usage", - "zendframework/zend-serializer": "^2.6.1, to use the SerializableStrategy", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", + "laminas/laminas-filter": "^2.6, to support naming strategy hydrator usage", + "laminas/laminas-serializer": "^2.6.1, to use the SerializableStrategy", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" }, "type": "library", "extra": { @@ -13285,14 +13285,14 @@ }, "autoload": { "psr-4": { - "Zend\\Hydrator\\": "src/" + "Laminas\\Hydrator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-hydrator", + "homepage": "https://github.com/laminas/laminas-hydrator", "keywords": [ "hydrator", "zf2" @@ -13300,44 +13300,44 @@ "time": "2016-02-18T22:38:26+00:00" }, { - "name": "zendframework/zend-i18n", + "name": "laminas/laminas-i18n", "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-i18n.git", + "url": "https://github.com/laminas/laminas-i18n.git", "reference": "6d69af5a04e1a4de7250043cb1322f077a0cdb7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/6d69af5a04e1a4de7250043cb1322f077a0cdb7f", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/6d69af5a04e1a4de7250043cb1322f077a0cdb7f", "reference": "6d69af5a04e1a4de7250043cb1322f077a0cdb7f", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.3" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.3" }, "suggest": { - "ext-intl": "Required for most features of Zend\\I18n; included in default builds of PHP", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", - "zendframework/zend-filter": "You should install this package to use the provided filters", - "zendframework/zend-i18n-resources": "Translation resources", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "You should install this package to use the provided validators", - "zendframework/zend-view": "You should install this package to use the provided view helpers" + "ext-intl": "Required for most features of Laminas\\I18n; included in default builds of PHP", + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-eventmanager": "You should install this package to use the events in the translator", + "laminas/laminas-filter": "You should install this package to use the provided filters", + "laminas/laminas-i18n-resources": "Translation resources", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "You should install this package to use the provided validators", + "laminas/laminas-view": "You should install this package to use the provided view helpers" }, "type": "library", "extra": { @@ -13346,13 +13346,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\I18n", - "config-provider": "Zend\\I18n\\ConfigProvider" + "component": "Laminas\\I18n", + "config-provider": "Laminas\\I18n\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\I18n\\": "src/" + "Laminas\\I18n\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13368,30 +13368,30 @@ "time": "2018-05-16T16:39:13+00:00" }, { - "name": "zendframework/zend-inputfilter", + "name": "laminas/laminas-inputfilter", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-inputfilter.git", + "url": "https://github.com/laminas/laminas-inputfilter.git", "reference": "4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", + "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", "reference": "4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-filter": "^2.9.1", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.11" + "laminas/laminas-filter": "^2.9.1", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.11" }, "require-dev": { "phpunit/phpunit": "^5.7.23 || ^6.4.3", "psr/http-message": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "suggest": { "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" @@ -13403,13 +13403,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\InputFilter", - "config-provider": "Zend\\InputFilter\\ConfigProvider" + "component": "Laminas\\InputFilter", + "config-provider": "Laminas\\InputFilter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\InputFilter\\": "src/" + "Laminas\\InputFilter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13425,16 +13425,16 @@ "time": "2019-01-30T16:58:51+00:00" }, { - "name": "zendframework/zend-json", + "name": "laminas/laminas-json", "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-json.git", + "url": "https://github.com/laminas/laminas-json.git", "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", + "url": "https://api.github.com/repos/laminas/laminas-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", "shasum": "" }, @@ -13444,16 +13444,16 @@ "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.5 || ^3.0", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.5 || ^3.0", "zendframework/zendxml": "^1.0.2" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component, required to use Zend\\Json\\Server", - "zendframework/zend-server": "Zend\\Server component, required to use Zend\\Json\\Server", - "zendframework/zend-stdlib": "Zend\\Stdlib component, for use with caching Zend\\Json\\Server responses", - "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" + "laminas/laminas-http": "Laminas\\Http component, required to use Laminas\\Json\\Server", + "laminas/laminas-server": "Laminas\\Server component, required to use Laminas\\Json\\Server", + "laminas/laminas-stdlib": "Laminas\\Stdlib component, for use with caching Laminas\\Json\\Server responses", + "zendframework/zendxml": "To support Laminas\\Json\\Json::fromXml() usage" }, "type": "library", "extra": { @@ -13464,7 +13464,7 @@ }, "autoload": { "psr-4": { - "Zend\\Json\\": "src/" + "Laminas\\Json\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13472,7 +13472,7 @@ "BSD-3-Clause" ], "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", - "homepage": "https://github.com/zendframework/zend-json", + "homepage": "https://github.com/laminas/laminas-json", "keywords": [ "json", "zf2" @@ -13480,16 +13480,16 @@ "time": "2016-02-04T21:20:26+00:00" }, { - "name": "zendframework/zend-loader", + "name": "laminas/laminas-loader", "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-loader.git", + "url": "https://github.com/laminas/laminas-loader.git", "reference": "78f11749ea340f6ca316bca5958eef80b38f9b6c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/78f11749ea340f6ca316bca5958eef80b38f9b6c", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/78f11749ea340f6ca316bca5958eef80b38f9b6c", "reference": "78f11749ea340f6ca316bca5958eef80b38f9b6c", "shasum": "" }, @@ -13498,7 +13498,7 @@ }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -13509,7 +13509,7 @@ }, "autoload": { "psr-4": { - "Zend\\Loader\\": "src/" + "Laminas\\Loader\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13525,24 +13525,24 @@ "time": "2018-04-30T15:20:54+00:00" }, { - "name": "zendframework/zend-log", + "name": "laminas/laminas-log", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-log.git", + "url": "https://github.com/laminas/laminas-log.git", "reference": "9cec3b092acb39963659c2f32441cccc56b3f430" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/9cec3b092acb39963659c2f32441cccc56b3f430", + "url": "https://api.github.com/repos/laminas/laminas-log/zipball/9cec3b092acb39963659c2f32441cccc56b3f430", "reference": "9cec3b092acb39963659c2f32441cccc56b3f430", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", "psr/log": "^1.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "provide": { "psr/log-implementation": "1.0.0" @@ -13550,21 +13550,21 @@ "require-dev": { "mikey179/vfsstream": "^1.6", "phpunit/phpunit": "^5.7.15 || ^6.0.8", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-filter": "^2.5", - "zendframework/zend-mail": "^2.6.1", - "zendframework/zend-validator": "^2.10.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-filter": "^2.5", + "laminas/laminas-mail": "^2.6.1", + "laminas/laminas-validator": "^2.10.1" }, "suggest": { "ext-mongo": "mongo extension to use Mongo writer", "ext-mongodb": "mongodb extension to use MongoDB writer", - "zendframework/zend-console": "Zend\\Console component to use the RequestID log processor", - "zendframework/zend-db": "Zend\\Db component to use the database log writer", - "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML log formatter", - "zendframework/zend-mail": "Zend\\Mail component to use the email log writer", - "zendframework/zend-validator": "Zend\\Validator component to block invalid log messages" + "laminas/laminas-console": "Laminas\\Console component to use the RequestID log processor", + "laminas/laminas-db": "Laminas\\Db component to use the database log writer", + "laminas/laminas-escaper": "Laminas\\Escaper component, for use in the XML log formatter", + "laminas/laminas-mail": "Laminas\\Mail component to use the email log writer", + "laminas/laminas-validator": "Laminas\\Validator component to block invalid log messages" }, "type": "library", "extra": { @@ -13573,13 +13573,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\Log", - "config-provider": "Zend\\Log\\ConfigProvider" + "component": "Laminas\\Log", + "config-provider": "Laminas\\Log\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Log\\": "src/" + "Laminas\\Log\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13587,7 +13587,7 @@ "BSD-3-Clause" ], "description": "component for general purpose logging", - "homepage": "https://github.com/zendframework/zend-log", + "homepage": "https://github.com/laminas/laminas-log", "keywords": [ "log", "logging", @@ -13596,16 +13596,16 @@ "time": "2018-04-09T21:59:51+00:00" }, { - "name": "zendframework/zend-mail", + "name": "laminas/laminas-mail", "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mail.git", + "url": "https://github.com/laminas/laminas-mail.git", "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8", "shasum": "" }, @@ -13613,21 +13613,21 @@ "ext-iconv": "*", "php": "^5.6 || ^7.0", "true/punycode": "^2.1", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mime": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.10.2" + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-crypt": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1" }, "suggest": { - "zendframework/zend-crypt": "Crammd5 support in SMTP Auth", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" }, "type": "library", "extra": { @@ -13636,13 +13636,13 @@ "dev-develop": "2.11.x-dev" }, "zf": { - "component": "Zend\\Mail", - "config-provider": "Zend\\Mail\\ConfigProvider" + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Mail\\": "src/" + "Laminas\\Mail\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13658,16 +13658,16 @@ "time": "2018-06-07T13:37:07+00:00" }, { - "name": "zendframework/zend-math", + "name": "laminas/laminas-math", "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-math.git", + "url": "https://github.com/laminas/laminas-math.git", "reference": "1abce074004dacac1a32cd54de94ad47ef960d38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", + "url": "https://api.github.com/repos/laminas/laminas-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", "reference": "1abce074004dacac1a32cd54de94ad47ef960d38", "shasum": "" }, @@ -13682,7 +13682,7 @@ "suggest": { "ext-bcmath": "If using the bcmath functionality", "ext-gmp": "If using the gmp functionality", - "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if Mcrypt extensions is unavailable" + "ircmaxell/random-lib": "Fallback random byte generator for Laminas\\Math\\Rand if Mcrypt extensions is unavailable" }, "type": "library", "extra": { @@ -13693,14 +13693,14 @@ }, "autoload": { "psr-4": { - "Zend\\Math\\": "src/" + "Laminas\\Math\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-math", + "homepage": "https://github.com/laminas/laminas-math", "keywords": [ "math", "zf2" @@ -13708,30 +13708,30 @@ "time": "2018-12-04T15:34:17+00:00" }, { - "name": "zendframework/zend-mime", + "name": "laminas/laminas-mime", "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mime.git", + "url": "https://github.com/laminas/laminas-mime.git", "reference": "52ae5fa9f12845cae749271034a2d594f0e4c6f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mime/zipball/52ae5fa9f12845cae749271034a2d594f0e4c6f2", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/52ae5fa9f12845cae749271034a2d594f0e4c6f2", "reference": "52ae5fa9f12845cae749271034a2d594f0e4c6f2", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-mail": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-mail": "^2.6" }, "suggest": { - "zendframework/zend-mail": "Zend\\Mail component" + "laminas/laminas-mail": "Laminas\\Mail component" }, "type": "library", "extra": { @@ -13742,7 +13742,7 @@ }, "autoload": { "psr-4": { - "Zend\\Mime\\": "src/" + "Laminas\\Mime\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13750,7 +13750,7 @@ "BSD-3-Clause" ], "description": "Create and parse MIME messages and parts", - "homepage": "https://github.com/zendframework/zend-mime", + "homepage": "https://github.com/laminas/laminas-mime", "keywords": [ "ZendFramework", "mime", @@ -13759,39 +13759,39 @@ "time": "2018-05-14T19:02:50+00:00" }, { - "name": "zendframework/zend-modulemanager", + "name": "laminas/laminas-modulemanager", "version": "2.8.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-modulemanager.git", + "url": "https://github.com/laminas/laminas-modulemanager.git", "reference": "394df6e12248ac430a312d4693f793ee7120baa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/394df6e12248ac430a312d4693f793ee7120baa6", + "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/394df6e12248ac430a312d4693f793ee7120baa6", "reference": "394df6e12248ac430a312d4693f793ee7120baa6", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-config": "^3.1 || ^2.6", - "zendframework/zend-eventmanager": "^3.2 || ^2.6.3", - "zendframework/zend-stdlib": "^3.1 || ^2.7" + "laminas/laminas-config": "^3.1 || ^2.6", + "laminas/laminas-eventmanager": "^3.2 || ^2.6.3", + "laminas/laminas-stdlib": "^3.1 || ^2.7" }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-console": "^2.6", - "zendframework/zend-di": "^2.6", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mvc": "^3.0 || ^2.7", - "zendframework/zend-servicemanager": "^3.0.3 || ^2.7.5" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-console": "^2.6", + "laminas/laminas-di": "^2.6", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mvc": "^3.0 || ^2.7", + "laminas/laminas-servicemanager": "^3.0.3 || ^2.7.5" }, "suggest": { - "zendframework/zend-console": "Zend\\Console component", - "zendframework/zend-loader": "Zend\\Loader component if you are not using Composer autoloading for your modules", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "laminas/laminas-console": "Laminas\\Console component", + "laminas/laminas-loader": "Laminas\\Loader component if you are not using Composer autoloading for your modules", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { @@ -13802,7 +13802,7 @@ }, "autoload": { "psr-4": { - "Zend\\ModuleManager\\": "src/" + "Laminas\\ModuleManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -13810,7 +13810,7 @@ "BSD-3-Clause" ], "description": "Modular application system for zend-mvc applications", - "homepage": "https://github.com/zendframework/zend-modulemanager", + "homepage": "https://github.com/laminas/laminas-modulemanager", "keywords": [ "ZendFramework", "modulemanager", @@ -13819,73 +13819,73 @@ "time": "2017-12-02T06:11:18+00:00" }, { - "name": "zendframework/zend-mvc", + "name": "laminas/laminas-mvc", "version": "2.7.15", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mvc.git", + "url": "https://github.com/laminas/laminas-mvc.git", "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", + "url": "https://api.github.com/repos/laminas/laminas-mvc/zipball/a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.5 || ^7.0", - "zendframework/zend-console": "^2.7", - "zendframework/zend-eventmanager": "^2.6.4 || ^3.0", - "zendframework/zend-form": "^2.11", - "zendframework/zend-hydrator": "^1.1 || ^2.4", - "zendframework/zend-psr7bridge": "^0.2", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7.5 || ^3.0" + "laminas/laminas-console": "^2.7", + "laminas/laminas-eventmanager": "^2.6.4 || ^3.0", + "laminas/laminas-form": "^2.11", + "laminas/laminas-hydrator": "^1.1 || ^2.4", + "laminas/laminas-psr7bridge": "^0.2", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7.5 || ^3.0" }, "replace": { - "zendframework/zend-router": "^2.0" + "laminas/laminas-router": "^2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "1.7.*", "phpunit/phpunit": "^4.8.36", "sebastian/comparator": "^1.2.4", "sebastian/version": "^1.0.4", - "zendframework/zend-authentication": "^2.6", - "zendframework/zend-cache": "^2.8", - "zendframework/zend-di": "^2.6", - "zendframework/zend-filter": "^2.8", - "zendframework/zend-http": "^2.8", - "zendframework/zend-i18n": "^2.8", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.3", - "zendframework/zend-modulemanager": "^2.8", - "zendframework/zend-serializer": "^2.8", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.7", - "zendframework/zend-uri": "^2.6", - "zendframework/zend-validator": "^2.10", - "zendframework/zend-view": "^2.9" + "laminas/laminas-authentication": "^2.6", + "laminas/laminas-cache": "^2.8", + "laminas/laminas-di": "^2.6", + "laminas/laminas-filter": "^2.8", + "laminas/laminas-http": "^2.8", + "laminas/laminas-i18n": "^2.8", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.3", + "laminas/laminas-modulemanager": "^2.8", + "laminas/laminas-serializer": "^2.8", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.7", + "laminas/laminas-uri": "^2.6", + "laminas/laminas-validator": "^2.10", + "laminas/laminas-view": "^2.9" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component for Identity plugin", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-di": "Zend\\Di component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component for translatable segments", - "zendframework/zend-inputfilter": "Zend\\Inputfilter component", - "zendframework/zend-json": "Zend\\Json component", - "zendframework/zend-log": "Zend\\Log component", - "zendframework/zend-modulemanager": "Zend\\ModuleManager component", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager-di": "^1.0.1, if using zend-servicemanager v3 and requiring the zend-di integration", - "zendframework/zend-session": "Zend\\Session component for FlashMessenger, PRG, and FPRG plugins", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-uri": "Zend\\Uri component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zend-view": "Zend\\View component" + "laminas/laminas-authentication": "Laminas\\Authentication component for Identity plugin", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-di": "Laminas\\Di component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component for translatable segments", + "laminas/laminas-inputfilter": "Zend\\Inputfilter component", + "laminas/laminas-json": "Laminas\\Json component", + "laminas/laminas-log": "Laminas\\Log component", + "laminas/laminas-modulemanager": "Laminas\\ModuleManager component", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager-di": "^1.0.1, if using zend-servicemanager v3 and requiring the zend-di integration", + "laminas/laminas-session": "Laminas\\Session component for FlashMessenger, PRG, and FPRG plugins", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-uri": "Laminas\\Uri component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-view": "Laminas\\View component" }, "type": "library", "extra": { @@ -13899,14 +13899,14 @@ "src/autoload.php" ], "psr-4": { - "Zend\\Mvc\\": "src/" + "Laminas\\Mvc\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-mvc", + "homepage": "https://github.com/laminas/laminas-mvc", "keywords": [ "mvc", "zf2" @@ -13914,24 +13914,24 @@ "time": "2018-05-03T13:13:41+00:00" }, { - "name": "zendframework/zend-psr7bridge", + "name": "laminas/laminas-psr7bridge", "version": "0.2.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-psr7bridge.git", + "url": "https://github.com/laminas/laminas-psr7bridge.git", "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-psr7bridge/zipball/86c0b53b0c6381391c4add4a93a56e51d5c74605", + "url": "https://api.github.com/repos/laminas/laminas-psr7bridge/zipball/86c0b53b0c6381391c4add4a93a56e51d5c74605", "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605", "shasum": "" }, "require": { "php": ">=5.5", "psr/http-message": "^1.0", - "zendframework/zend-diactoros": "^1.1", - "zendframework/zend-http": "^2.5" + "laminas/laminas-diactoros": "^1.1", + "laminas/laminas-http": "^2.5" }, "require-dev": { "phpunit/phpunit": "^4.7", @@ -13946,15 +13946,15 @@ }, "autoload": { "psr-4": { - "Zend\\Psr7Bridge\\": "src/" + "Laminas\\Psr7Bridge\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "PSR-7 <-> Zend\\Http bridge", - "homepage": "https://github.com/zendframework/zend-psr7bridge", + "description": "PSR-7 <-> Laminas\\Http bridge", + "homepage": "https://github.com/laminas/laminas-psr7bridge", "keywords": [ "http", "psr", @@ -13963,33 +13963,33 @@ "time": "2016-05-10T21:44:39+00:00" }, { - "name": "zendframework/zend-serializer", + "name": "laminas/laminas-serializer", "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-serializer.git", + "url": "https://github.com/laminas/laminas-serializer.git", "reference": "0172690db48d8935edaf625c4cba38b79719892c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/0172690db48d8935edaf625c4cba38b79719892c", + "url": "https://api.github.com/repos/laminas/laminas-serializer/zipball/0172690db48d8935edaf625c4cba38b79719892c", "reference": "0172690db48d8935edaf625c4cba38b79719892c", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-json": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-json": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.25 || ^6.4.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-math": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-math": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { - "zendframework/zend-math": "(^2.6 || ^3.0) To support Python Pickle serialization", - "zendframework/zend-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" + "laminas/laminas-math": "(^2.6 || ^3.0) To support Python Pickle serialization", + "laminas/laminas-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" }, "type": "library", "extra": { @@ -13998,13 +13998,13 @@ "dev-develop": "2.10.x-dev" }, "zf": { - "component": "Zend\\Serializer", - "config-provider": "Zend\\Serializer\\ConfigProvider" + "component": "Laminas\\Serializer", + "config-provider": "Laminas\\Serializer\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Serializer\\": "src/" + "Laminas\\Serializer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14020,27 +14020,27 @@ "time": "2018-05-14T18:45:18+00:00" }, { - "name": "zendframework/zend-server", + "name": "laminas/laminas-server", "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-server.git", + "url": "https://github.com/laminas/laminas-server.git", "reference": "23a2e9a5599c83c05da831cb7c649e8a7809595e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-server/zipball/23a2e9a5599c83c05da831cb7c649e8a7809595e", + "url": "https://api.github.com/repos/laminas/laminas-server/zipball/23a2e9a5599c83c05da831cb7c649e8a7809595e", "reference": "23a2e9a5599c83c05da831cb7c649e8a7809595e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-code": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.5 || ^3.0" + "laminas/laminas-code": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.5 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -14051,7 +14051,7 @@ }, "autoload": { "psr-4": { - "Zend\\Server\\": "src/" + "Laminas\\Server\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14067,16 +14067,16 @@ "time": "2018-04-30T22:21:28+00:00" }, { - "name": "zendframework/zend-servicemanager", + "name": "laminas/laminas-servicemanager", "version": "2.7.11", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-servicemanager.git", + "url": "https://github.com/laminas/laminas-servicemanager.git", "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7", "shasum": "" }, @@ -14088,12 +14088,12 @@ "athletic/athletic": "dev-master", "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-di": "~2.5", - "zendframework/zend-mvc": "~2.5" + "laminas/laminas-di": "~2.5", + "laminas/laminas-mvc": "~2.5" }, "suggest": { "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services", - "zendframework/zend-di": "Zend\\Di component" + "laminas/laminas-di": "Laminas\\Di component" }, "type": "library", "extra": { @@ -14104,14 +14104,14 @@ }, "autoload": { "psr-4": { - "Zend\\ServiceManager\\": "src/" + "Laminas\\ServiceManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-servicemanager", + "homepage": "https://github.com/laminas/laminas-servicemanager", "keywords": [ "servicemanager", "zf2" @@ -14119,43 +14119,43 @@ "time": "2018-06-22T14:49:54+00:00" }, { - "name": "zendframework/zend-session", + "name": "laminas/laminas-session", "version": "2.8.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-session.git", + "url": "https://github.com/laminas/laminas-session.git", "reference": "2cfd90e1a2f6b066b9f908599251d8f64f07021b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-session/zipball/2cfd90e1a2f6b066b9f908599251d8f64f07021b", + "url": "https://api.github.com/repos/laminas/laminas-session/zipball/2cfd90e1a2f6b066b9f908599251d8f64f07021b", "reference": "2cfd90e1a2f6b066b9f908599251d8f64f07021b", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "container-interop/container-interop": "^1.1", "mongodb/mongodb": "^1.0.1", "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", "phpunit/phpunit": "^5.7.5 || >=6.0.13 <6.5.0", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.7", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.7", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6" }, "suggest": { "mongodb/mongodb": "If you want to use the MongoDB session save handler", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-db": "Zend\\Db component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "Zend\\Validator component" + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-db": "Laminas\\Db component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "Laminas\\Validator component" }, "type": "library", "extra": { @@ -14164,13 +14164,13 @@ "dev-develop": "2.9-dev" }, "zf": { - "component": "Zend\\Session", - "config-provider": "Zend\\Session\\ConfigProvider" + "component": "Laminas\\Session", + "config-provider": "Laminas\\Session\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Session\\": "src/" + "Laminas\\Session\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14186,34 +14186,34 @@ "time": "2018-02-22T16:33:54+00:00" }, { - "name": "zendframework/zend-soap", + "name": "laminas/laminas-soap", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-soap.git", + "url": "https://github.com/laminas/laminas-soap.git", "reference": "af03c32f0db2b899b3df8cfe29aeb2b49857d284" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/af03c32f0db2b899b3df8cfe29aeb2b49857d284", + "url": "https://api.github.com/repos/laminas/laminas-soap/zipball/af03c32f0db2b899b3df8cfe29aeb2b49857d284", "reference": "af03c32f0db2b899b3df8cfe29aeb2b49857d284", "shasum": "" }, "require": { "ext-soap": "*", "php": "^5.6 || ^7.0", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-uri": "^2.5.2" + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-uri": "^2.5.2" }, "require-dev": { "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-http": "^2.5.4" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-http": "^2.5.4" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component" + "laminas/laminas-http": "Laminas\\Http component" }, "type": "library", "extra": { @@ -14224,14 +14224,14 @@ }, "autoload": { "psr-4": { - "Zend\\Soap\\": "src/" + "Laminas\\Soap\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-soap", + "homepage": "https://github.com/laminas/laminas-soap", "keywords": [ "soap", "zf2" @@ -14239,39 +14239,39 @@ "time": "2018-01-29T17:51:26+00:00" }, { - "name": "zendframework/zend-stdlib", + "name": "laminas/laminas-stdlib", "version": "2.7.7", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-stdlib.git", + "url": "https://github.com/laminas/laminas-stdlib.git", "reference": "0e44eb46788f65e09e077eb7f44d2659143bcc1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/0e44eb46788f65e09e077eb7f44d2659143bcc1f", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/0e44eb46788f65e09e077eb7f44d2659143bcc1f", "reference": "0e44eb46788f65e09e077eb7f44d2659143bcc1f", "shasum": "" }, "require": { "php": "^5.5 || ^7.0", - "zendframework/zend-hydrator": "~1.1" + "laminas/laminas-hydrator": "~1.1" }, "require-dev": { "athletic/athletic": "~0.1", "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", - "zendframework/zend-config": "~2.5", - "zendframework/zend-eventmanager": "~2.5", - "zendframework/zend-filter": "~2.5", - "zendframework/zend-inputfilter": "~2.5", - "zendframework/zend-serializer": "~2.5", - "zendframework/zend-servicemanager": "~2.5" + "laminas/laminas-config": "~2.5", + "laminas/laminas-eventmanager": "~2.5", + "laminas/laminas-filter": "~2.5", + "laminas/laminas-inputfilter": "~2.5", + "laminas/laminas-serializer": "~2.5", + "laminas/laminas-servicemanager": "~2.5" }, "suggest": { - "zendframework/zend-eventmanager": "To support aggregate hydrator usage", - "zendframework/zend-filter": "To support naming strategy hydrator usage", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + "laminas/laminas-eventmanager": "To support aggregate hydrator usage", + "laminas/laminas-filter": "To support naming strategy hydrator usage", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager": "To support hydrator plugin manager usage" }, "type": "library", "extra": { @@ -14283,14 +14283,14 @@ }, "autoload": { "psr-4": { - "Zend\\Stdlib\\": "src/" + "Laminas\\Stdlib\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-stdlib", + "homepage": "https://github.com/laminas/laminas-stdlib", "keywords": [ "stdlib", "zf2" @@ -14298,28 +14298,28 @@ "time": "2016-04-12T21:17:31+00:00" }, { - "name": "zendframework/zend-text", + "name": "laminas/laminas-text", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-text.git", + "url": "https://github.com/laminas/laminas-text.git", "reference": "ca987dd4594f5f9508771fccd82c89bc7fbb39ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-text/zipball/ca987dd4594f5f9508771fccd82c89bc7fbb39ac", + "url": "https://api.github.com/repos/laminas/laminas-text/zipball/ca987dd4594f5f9508771fccd82c89bc7fbb39ac", "reference": "ca987dd4594f5f9508771fccd82c89bc7fbb39ac", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6" }, "type": "library", "extra": { @@ -14330,7 +14330,7 @@ }, "autoload": { "psr-4": { - "Zend\\Text\\": "src/" + "Laminas\\Text\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14346,27 +14346,27 @@ "time": "2018-04-30T14:55:10+00:00" }, { - "name": "zendframework/zend-uri", + "name": "laminas/laminas-uri", "version": "2.7.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-uri.git", + "url": "https://github.com/laminas/laminas-uri.git", "reference": "b2785cd38fe379a784645449db86f21b7739b1ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/b2785cd38fe379a784645449db86f21b7739b1ee", + "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/b2785cd38fe379a784645449db86f21b7739b1ee", "reference": "b2785cd38fe379a784645449db86f21b7739b1ee", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-validator": "^2.10" + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-validator": "^2.10" }, "require-dev": { "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "laminas/laminas-coding-standard": "~1.0.0" }, "type": "library", "extra": { @@ -14377,7 +14377,7 @@ }, "autoload": { "psr-4": { - "Zend\\Uri\\": "src/" + "Laminas\\Uri\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14393,49 +14393,49 @@ "time": "2019-02-27T21:39:04+00:00" }, { - "name": "zendframework/zend-validator", + "name": "laminas/laminas-validator", "version": "2.11.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-validator.git", + "url": "https://github.com/laminas/laminas-validator.git", "reference": "3c28dfe4e5951ba38059cea895244d9d206190b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/3c28dfe4e5951ba38059cea895244d9d206190b3", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/3c28dfe4e5951ba38059cea895244d9d206190b3", "reference": "3c28dfe4e5951ba38059cea895244d9d206190b3", "shasum": "" }, "require": { "container-interop/container-interop": "^1.1", "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.6 || ^3.1" + "laminas/laminas-stdlib": "^2.7.6 || ^3.1" }, "require-dev": { "phpunit/phpunit": "^6.0.8 || ^5.7.15", "psr/http-message": "^1.0", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-db": "^2.7", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-math": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8", - "zendframework/zend-uri": "^2.5" + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.5" }, "suggest": { "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", - "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", - "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", - "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", - "zendframework/zend-i18n-resources": "Translations of validator messages", - "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", - "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", - "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators" }, "type": "library", "extra": { @@ -14444,13 +14444,13 @@ "dev-develop": "2.12.x-dev" }, "zf": { - "component": "Zend\\Validator", - "config-provider": "Zend\\Validator\\ConfigProvider" + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" } }, "autoload": { "psr-4": { - "Zend\\Validator\\": "src/" + "Laminas\\Validator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14458,7 +14458,7 @@ "BSD-3-Clause" ], "description": "provides a set of commonly needed validators", - "homepage": "https://github.com/zendframework/zend-validator", + "homepage": "https://github.com/laminas/laminas-validator", "keywords": [ "validator", "zf2" @@ -14466,64 +14466,64 @@ "time": "2019-01-29T22:26:39+00:00" }, { - "name": "zendframework/zend-view", + "name": "laminas/laminas-view", "version": "2.10.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-view.git", + "url": "https://github.com/laminas/laminas-view.git", "reference": "c1a3f2043fb75b5983ab9adfc369ae396601be7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/c1a3f2043fb75b5983ab9adfc369ae396601be7e", + "url": "https://api.github.com/repos/laminas/laminas-view/zipball/c1a3f2043fb75b5983ab9adfc369ae396601be7e", "reference": "c1a3f2043fb75b5983ab9adfc369ae396601be7e", "shasum": "" }, "require": { "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0" }, "require-dev": { "phpunit/phpunit": "^5.7.15 || ^6.0.8", - "zendframework/zend-authentication": "^2.5", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-console": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-feed": "^2.7", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-log": "^2.7", - "zendframework/zend-modulemanager": "^2.7.1", - "zendframework/zend-mvc": "^2.7.14 || ^3.0", - "zendframework/zend-navigation": "^2.5", - "zendframework/zend-paginator": "^2.5", - "zendframework/zend-permissions-acl": "^2.6", - "zendframework/zend-router": "^3.0.1", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-uri": "^2.5" + "laminas/laminas-authentication": "^2.5", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-console": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-feed": "^2.7", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-log": "^2.7", + "laminas/laminas-modulemanager": "^2.7.1", + "laminas/laminas-mvc": "^2.7.14 || ^3.0", + "laminas/laminas-navigation": "^2.5", + "laminas/laminas-paginator": "^2.5", + "laminas/laminas-permissions-acl": "^2.6", + "laminas/laminas-router": "^3.0.1", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-uri": "^2.5" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component", - "zendframework/zend-escaper": "Zend\\Escaper component", - "zendframework/zend-feed": "Zend\\Feed component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", - "zendframework/zend-navigation": "Zend\\Navigation component", - "zendframework/zend-paginator": "Zend\\Paginator component", - "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component" + "laminas/laminas-authentication": "Laminas\\Authentication component", + "laminas/laminas-escaper": "Laminas\\Escaper component", + "laminas/laminas-feed": "Laminas\\Feed component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", + "laminas/laminas-navigation": "Laminas\\Navigation component", + "laminas/laminas-paginator": "Laminas\\Paginator component", + "laminas/laminas-permissions-acl": "Laminas\\Permissions\\Acl component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-uri": "Laminas\\Uri component" }, "bin": [ "bin/templatemap_generator.php" @@ -14537,7 +14537,7 @@ }, "autoload": { "psr-4": { - "Zend\\View\\": "src/" + "Laminas\\View\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -14545,7 +14545,7 @@ "BSD-3-Clause" ], "description": "provides a system of helpers, output filters, and variable escaping", - "homepage": "https://github.com/zendframework/zend-view", + "homepage": "https://github.com/laminas/laminas-view", "keywords": [ "view", "zf2" diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index 2ffce44b32cfe..1e72485a9859f 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -183,7 +183,7 @@ enumValues(includeDeprecated: true) { $request->setPathInfo('/graphql'); $request->setMethod('POST'); $request->setContent(json_encode($postData)); - $headers = $this->objectManager->create(\Zend\Http\Headers::class) + $headers = $this->objectManager->create(\Laminas\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); $request->setHeaders($headers); diff --git a/dev/tests/integration/testsuite/Magento/Framework/HTTP/HeaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/HTTP/HeaderTest.php index 6c6b0b4aafba9..1bd182c20b290 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/HTTP/HeaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/HTTP/HeaderTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\HTTP; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; class HeaderTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Framework/Stdlib/Cookie/CookieScopeTest.php b/dev/tests/integration/testsuite/Magento/Framework/Stdlib/Cookie/CookieScopeTest.php index 9dee98fcb9121..2b5cb27e84dcb 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Stdlib/Cookie/CookieScopeTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Stdlib/Cookie/CookieScopeTest.php @@ -9,7 +9,7 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; /** * Test CookieScope diff --git a/dev/tests/integration/testsuite/Magento/Framework/UrlTest.php b/dev/tests/integration/testsuite/Magento/Framework/UrlTest.php index db830d228201c..773c833627572 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/UrlTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/UrlTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; use Magento\TestFramework\Helper\Bootstrap; /** diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php index 2fd388d9db3f5..527e471ff3e39 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php @@ -98,7 +98,7 @@ public function testDispatch() : void $this->request->setPathInfo('/graphql'); $this->request->setMethod('POST'); $this->request->setContent(json_encode($postData)); - $headers = $this->objectManager->create(\Zend\Http\Headers::class) + $headers = $this->objectManager->create(\Laminas\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); $this->request->setHeaders($headers); $response = $this->graphql->dispatch($this->request); @@ -241,7 +241,7 @@ public function testError() : void $this->request->setPathInfo('/graphql'); $this->request->setMethod('POST'); $this->request->setContent(json_encode($postData)); - $headers = $this->objectManager->create(\Zend\Http\Headers::class) + $headers = $this->objectManager->create(\Laminas\Http\Headers::class) ->addHeaders(['Content-Type' => 'application/json']); $this->request->setHeaders($headers); $response = $this->graphql->dispatch($this->request); diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterTemplateTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterTemplateTest.php index ae57703f9e8e2..06450d2a1d187 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterTemplateTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Adminhtml/NewsletterTemplateTest.php @@ -36,7 +36,7 @@ protected function setUp() 'text' => 'Template Content', 'form_key' => $this->formKey, ]; - $this->getRequest()->setPostValue($post)->setMethod(\Zend\Http\Request::METHOD_POST); + $this->getRequest()->setPostValue($post)->setMethod(\Laminas\Http\Request::METHOD_POST); $this->model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Newsletter\Model\Template::class ); @@ -141,7 +141,7 @@ public function testSaveActionTemplateWithGetAndVerifyRedirect() // Loading by code, since ID will vary. template_code is not actually used to load anywhere else. $this->model->load('some_unique_code', 'template_code'); - $this->getRequest()->setMethod(\Zend\Http\Request::METHOD_GET)->setParam('id', $this->model->getId()); + $this->getRequest()->setMethod(\Laminas\Http\Request::METHOD_GET)->setParam('id', $this->model->getId()); $this->dispatch('backend/newsletter/template/save'); $this->assertEquals(404, $this->getResponse()->getStatusCode()); diff --git a/dev/tests/integration/testsuite/Magento/Phpserver/PhpserverTest.php b/dev/tests/integration/testsuite/Magento/Phpserver/PhpserverTest.php index b11991fc03255..237bb0aa118bc 100644 --- a/dev/tests/integration/testsuite/Magento/Phpserver/PhpserverTest.php +++ b/dev/tests/integration/testsuite/Magento/Phpserver/PhpserverTest.php @@ -22,7 +22,7 @@ class PhpserverTest extends \PHPUnit\Framework\TestCase private static $serverPid; /** - * @var \Zend\Http\Client + * @var \Laminas\Http\Client */ private $httpClient; @@ -53,7 +53,7 @@ private function getUrl($url) public function setUp() { - $this->httpClient = new \Zend\Http\Client(null, ['timeout' => 10]); + $this->httpClient = new \Laminas\Http\Client(null, ['timeout' => 10]); } public function testServerHasPid() diff --git a/dev/tests/integration/testsuite/Magento/Review/Controller/Adminhtml/Product/MassUpdateTest.php b/dev/tests/integration/testsuite/Magento/Review/Controller/Adminhtml/Product/MassUpdateTest.php index d6664d1fbb7ac..60d5d8cd5c4f4 100644 --- a/dev/tests/integration/testsuite/Magento/Review/Controller/Adminhtml/Product/MassUpdateTest.php +++ b/dev/tests/integration/testsuite/Magento/Review/Controller/Adminhtml/Product/MassUpdateTest.php @@ -13,7 +13,7 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\UrlInterface; use Magento\Review\Model\ResourceModel\Review\CollectionFactory; -use Zend\Http\Request; +use Laminas\Http\Request; /** * Test Mass Update action. diff --git a/dev/tests/integration/testsuite/Magento/Setup/Controller/UrlCheckTest.php b/dev/tests/integration/testsuite/Magento/Setup/Controller/UrlCheckTest.php index c5c54b8c1dd3b..80796c0294958 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Controller/UrlCheckTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Controller/UrlCheckTest.php @@ -6,8 +6,8 @@ namespace Magento\Setup\Controller; use Magento\TestFramework\Helper\Bootstrap; -use Zend\Stdlib\RequestInterface as Request; -use Zend\View\Model\JsonModel; +use Laminas\Stdlib\RequestInterface as Request; +use Laminas\View\Model\JsonModel; class UrlCheckTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/Setup/Model/ConfigOptionsListCollectorTest.php b/dev/tests/integration/testsuite/Magento/Setup/Model/ConfigOptionsListCollectorTest.php index 455d6436f724d..c7868d90af7a4 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Model/ConfigOptionsListCollectorTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Model/ConfigOptionsListCollectorTest.php @@ -39,7 +39,7 @@ public function testCollectOptionsLists() ] ); - $serviceLocator = $this->getMockForAbstractClass(\Zend\ServiceManager\ServiceLocatorInterface::class); + $serviceLocator = $this->getMockForAbstractClass(\Laminas\ServiceManager\ServiceLocatorInterface::class); $serviceLocator->expects($this->once()) ->method('get') diff --git a/dev/tests/integration/testsuite/Magento/Setup/Model/ObjectManagerProviderTest.php b/dev/tests/integration/testsuite/Magento/Setup/Model/ObjectManagerProviderTest.php index a80da16be67eb..d66329761336b 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Model/ObjectManagerProviderTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Model/ObjectManagerProviderTest.php @@ -11,7 +11,7 @@ use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject; use Symfony\Component\Console\Application; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; /** * Tests ObjectManagerProvider diff --git a/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.lock b/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.lock index 48fa6d0d0cd34..ddd1ed9684ed3 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.lock +++ b/dev/tests/integration/testsuite/Magento/Setup/Model/_files/testSkeleton/composer.lock @@ -4057,26 +4057,26 @@ "oyejorge/less.php": "1.7.0.3", "php": "~5.5.0|~5.6.0|~7.0.0", "tubalmartin/cssmin": "2.4.8-p4", - "zendframework/zend-code": "2.3.1", - "zendframework/zend-config": "2.3.1", - "zendframework/zend-console": "2.3.1", - "zendframework/zend-di": "2.3.1", - "zendframework/zend-eventmanager": "2.3.1", - "zendframework/zend-form": "2.3.1", - "zendframework/zend-http": "2.3.1", - "zendframework/zend-json": "2.3.1", - "zendframework/zend-log": "2.3.1", - "zendframework/zend-modulemanager": "2.3.1", - "zendframework/zend-mvc": "2.3.1", - "zendframework/zend-serializer": "2.3.1", - "zendframework/zend-server": "2.3.1", - "zendframework/zend-servicemanager": "2.3.1", - "zendframework/zend-soap": "2.3.1", - "zendframework/zend-stdlib": "2.3.1", - "zendframework/zend-text": "2.3.1", - "zendframework/zend-uri": "2.3.1", - "zendframework/zend-validator": "2.3.1", - "zendframework/zend-view": "2.3.1" + "laminas/laminas-code": "2.3.1", + "laminas/laminas-config": "2.3.1", + "laminas/laminas-console": "2.3.1", + "laminas/laminas-di": "2.3.1", + "laminas/laminas-eventmanager": "2.3.1", + "laminas/laminas-form": "2.3.1", + "laminas/laminas-http": "2.3.1", + "laminas/laminas-json": "2.3.1", + "laminas/laminas-log": "2.3.1", + "laminas/laminas-modulemanager": "2.3.1", + "laminas/laminas-mvc": "2.3.1", + "laminas/laminas-serializer": "2.3.1", + "laminas/laminas-server": "2.3.1", + "laminas/laminas-servicemanager": "2.3.1", + "laminas/laminas-soap": "2.3.1", + "laminas/laminas-stdlib": "2.3.1", + "laminas/laminas-text": "2.3.1", + "laminas/laminas-uri": "2.3.1", + "laminas/laminas-validator": "2.3.1", + "laminas/laminas-view": "2.3.1" }, "require-dev": { "ext-ctype": "*", @@ -4696,33 +4696,33 @@ "time": "2014-09-22 08:08:50" }, { - "name": "zendframework/zend-code", + "name": "laminas/laminas-code", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-code.git", + "url": "https://github.com/laminas/laminas-code.git", "reference": "a64e90c9ee8c939335ee7e21e39e3342c0e54526" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/a64e90c9ee8c939335ee7e21e39e3342c0e54526", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/a64e90c9ee8c939335ee7e21e39e3342c0e54526", "reference": "a64e90c9ee8c939335ee7e21e39e3342c0e54526", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-eventmanager": "self.version" + "laminas/laminas-eventmanager": "self.version" }, "require-dev": { "doctrine/common": ">=2.1", "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-stdlib": "self.version" }, "suggest": { "doctrine/common": "Doctrine\\Common >=2.1 for annotation features", - "zendframework/zend-stdlib": "Zend\\Stdlib component" + "laminas/laminas-stdlib": "Laminas\\Stdlib component" }, "type": "library", "extra": { @@ -4733,7 +4733,7 @@ }, "autoload": { "psr-4": { - "Zend\\Code\\": "src/" + "Laminas\\Code\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4741,7 +4741,7 @@ "BSD-3-Clause" ], "description": "provides facilities to generate arbitrary code using an object oriented interface", - "homepage": "https://github.com/zendframework/zend-code", + "homepage": "https://github.com/laminas/laminas-code", "keywords": [ "code", "zf2" @@ -4749,37 +4749,37 @@ "time": "2014-04-15 14:47:18" }, { - "name": "zendframework/zend-config", + "name": "laminas/laminas-config", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-config.git", + "url": "https://github.com/laminas/laminas-config.git", "reference": "698c139707380b29fd09791ec1c21b837e9a3f15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/698c139707380b29fd09791ec1c21b837e9a3f15", + "url": "https://api.github.com/repos/laminas/laminas-config/zipball/698c139707380b29fd09791ec1c21b837e9a3f15", "reference": "698c139707380b29fd09791ec1c21b837e9a3f15", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-filter": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-json": "self.version", - "zendframework/zend-servicemanager": "self.version" + "laminas/laminas-filter": "self.version", + "laminas/laminas-i18n": "self.version", + "laminas/laminas-json": "self.version", + "laminas/laminas-servicemanager": "self.version" }, "suggest": { - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", - "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-json": "Laminas\\Json to use the Json reader or writer classes", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" }, "type": "library", "extra": { @@ -4790,7 +4790,7 @@ }, "autoload": { "psr-4": { - "Zend\\Config\\": "src/" + "Laminas\\Config\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4798,7 +4798,7 @@ "BSD-3-Clause" ], "description": "provides a nested object property based user interface for accessing this configuration data within application code", - "homepage": "https://github.com/zendframework/zend-config", + "homepage": "https://github.com/laminas/laminas-config", "keywords": [ "config", "zf2" @@ -4806,22 +4806,22 @@ "time": "2014-04-15 13:59:53" }, { - "name": "zendframework/zend-console", + "name": "laminas/laminas-console", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-console.git", + "url": "https://github.com/laminas/laminas-console.git", "reference": "4253efd75a022d97ef326eac38a06c8eebb48a37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-console/zipball/4253efd75a022d97ef326eac38a06c8eebb48a37", + "url": "https://api.github.com/repos/laminas/laminas-console/zipball/4253efd75a022d97ef326eac38a06c8eebb48a37", "reference": "4253efd75a022d97ef326eac38a06c8eebb48a37", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -4829,8 +4829,8 @@ "satooshi/php-coveralls": "dev-master" }, "suggest": { - "zendframework/zend-filter": "To support DefaultRouteMatcher usage", - "zendframework/zend-validator": "To support DefaultRouteMatcher usage" + "laminas/laminas-filter": "To support DefaultRouteMatcher usage", + "laminas/laminas-validator": "To support DefaultRouteMatcher usage" }, "type": "library", "extra": { @@ -4841,14 +4841,14 @@ }, "autoload": { "psr-4": { - "Zend\\Console\\": "src/" + "Laminas\\Console\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-console", + "homepage": "https://github.com/laminas/laminas-console", "keywords": [ "console", "zf2" @@ -4856,32 +4856,32 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-di", + "name": "laminas/laminas-di", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-di.git", + "url": "https://github.com/laminas/laminas-di.git", "reference": "7502db10f8023bfd4e860ce83c9cdeda0db42c31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-di/zipball/7502db10f8023bfd4e860ce83c9cdeda0db42c31", + "url": "https://api.github.com/repos/laminas/laminas-di/zipball/7502db10f8023bfd4e860ce83c9cdeda0db42c31", "reference": "7502db10f8023bfd4e860ce83c9cdeda0db42c31", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-code": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-code": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-servicemanager": "self.version" + "laminas/laminas-servicemanager": "self.version" }, "suggest": { - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { @@ -4892,14 +4892,14 @@ }, "autoload": { "psr-4": { - "Zend\\Di\\": "src/" + "Laminas\\Di\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-di", + "homepage": "https://github.com/laminas/laminas-di", "keywords": [ "di", "zf2" @@ -4907,16 +4907,16 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-escaper", + "name": "laminas/laminas-escaper", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-escaper.git", + "url": "https://github.com/laminas/laminas-escaper.git", "reference": "2b2fcb6141cc2a2c6cc0c596e82771c72ef4ddc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/2b2fcb6141cc2a2c6cc0c596e82771c72ef4ddc1", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/2b2fcb6141cc2a2c6cc0c596e82771c72ef4ddc1", "reference": "2b2fcb6141cc2a2c6cc0c596e82771c72ef4ddc1", "shasum": "" }, @@ -4937,14 +4937,14 @@ }, "autoload": { "psr-4": { - "Zend\\Escaper\\": "src/" + "Laminas\\Escaper\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-escaper", + "homepage": "https://github.com/laminas/laminas-escaper", "keywords": [ "escaper", "zf2" @@ -4952,22 +4952,22 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-eventmanager", + "name": "laminas/laminas-eventmanager", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-eventmanager.git", + "url": "https://github.com/laminas/laminas-eventmanager.git", "reference": "ec158e89d0290f988a29ccd6bf179b561efbb702" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/ec158e89d0290f988a29ccd6bf179b561efbb702", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/ec158e89d0290f988a29ccd6bf179b561efbb702", "reference": "ec158e89d0290f988a29ccd6bf179b561efbb702", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -4983,7 +4983,7 @@ }, "autoload": { "psr-4": { - "Zend\\EventManager\\": "src/" + "Laminas\\EventManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4998,36 +4998,36 @@ "time": "2014-04-15 13:59:53" }, { - "name": "zendframework/zend-filter", + "name": "laminas/laminas-filter", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-filter.git", + "url": "https://github.com/laminas/laminas-filter.git", "reference": "307fe694659e08ffd710c70e4db8bc60187bcc84" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/307fe694659e08ffd710c70e4db8bc60187bcc84", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/307fe694659e08ffd710c70e4db8bc60187bcc84", "reference": "307fe694659e08ffd710c70e4db8bc60187bcc84", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-crypt": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-uri": "self.version" + "laminas/laminas-crypt": "self.version", + "laminas/laminas-servicemanager": "self.version", + "laminas/laminas-uri": "self.version" }, "suggest": { - "zendframework/zend-crypt": "Zend\\Crypt component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component for UriNormalize filter" + "laminas/laminas-crypt": "Laminas\\Crypt component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-uri": "Laminas\\Uri component for UriNormalize filter" }, "type": "library", "extra": { @@ -5038,7 +5038,7 @@ }, "autoload": { "psr-4": { - "Zend\\Filter\\": "src/" + "Laminas\\Filter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5046,7 +5046,7 @@ "BSD-3-Clause" ], "description": "provides a set of commonly needed data filters", - "homepage": "https://github.com/zendframework/zend-filter", + "homepage": "https://github.com/laminas/laminas-filter", "keywords": [ "filter", "zf2" @@ -5054,48 +5054,48 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-form", + "name": "laminas/laminas-form", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-form.git", + "url": "https://github.com/laminas/laminas-form.git", "reference": "d7a1f5bc4626b1df990391502a868b28c37ba65d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/d7a1f5bc4626b1df990391502a868b28c37ba65d", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/d7a1f5bc4626b1df990391502a868b28c37ba65d", "reference": "d7a1f5bc4626b1df990391502a868b28c37ba65d", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-inputfilter": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-inputfilter": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-captcha": "self.version", - "zendframework/zend-code": "self.version", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-validator": "self.version", - "zendframework/zend-view": "self.version", - "zendframework/zendservice-recaptcha": "*" + "laminas/laminas-captcha": "self.version", + "laminas/laminas-code": "self.version", + "laminas/laminas-eventmanager": "self.version", + "laminas/laminas-filter": "self.version", + "laminas/laminas-i18n": "self.version", + "laminas/laminas-servicemanager": "self.version", + "laminas/laminas-validator": "self.version", + "laminas/laminas-view": "self.version", + "laminas/laminas-recaptcha": "*" }, "suggest": { - "zendframework/zend-captcha": "Zend\\Captcha component", - "zendframework/zend-code": "Zend\\Code component", - "zendframework/zend-eventmanager": "Zend\\EventManager component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zend-view": "Zend\\View component", - "zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha component" + "laminas/laminas-captcha": "Laminas\\Captcha component", + "laminas/laminas-code": "Laminas\\Code component", + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-view": "Laminas\\View component", + "laminas/laminas-recaptcha": "Laminas\\ReCaptcha component" }, "type": "library", "extra": { @@ -5106,14 +5106,14 @@ }, "autoload": { "psr-4": { - "Zend\\Form\\": "src/" + "Laminas\\Form\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-form", + "homepage": "https://github.com/laminas/laminas-form", "keywords": [ "form", "zf2" @@ -5121,25 +5121,25 @@ "time": "2014-04-15 14:36:41" }, { - "name": "zendframework/zend-http", + "name": "laminas/laminas-http", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-http.git", + "url": "https://github.com/laminas/laminas-http.git", "reference": "869ce7bdf60727e14d85c305d2948fbe831c3534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/869ce7bdf60727e14d85c305d2948fbe831c3534", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/869ce7bdf60727e14d85c305d2948fbe831c3534", "reference": "869ce7bdf60727e14d85c305d2948fbe831c3534", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-loader": "self.version", - "zendframework/zend-stdlib": "self.version", - "zendframework/zend-uri": "self.version", - "zendframework/zend-validator": "self.version" + "laminas/laminas-loader": "self.version", + "laminas/laminas-stdlib": "self.version", + "laminas/laminas-uri": "self.version", + "laminas/laminas-validator": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -5155,7 +5155,7 @@ }, "autoload": { "psr-4": { - "Zend\\Http\\": "src/" + "Laminas\\Http\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5163,7 +5163,7 @@ "BSD-3-Clause" ], "description": "provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", - "homepage": "https://github.com/zendframework/zend-http", + "homepage": "https://github.com/laminas/laminas-http", "keywords": [ "http", "zf2" @@ -5171,33 +5171,33 @@ "time": "2014-04-15 13:59:53" }, { - "name": "zendframework/zend-inputfilter", + "name": "laminas/laminas-inputfilter", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-inputfilter.git", + "url": "https://github.com/laminas/laminas-inputfilter.git", "reference": "abca740015a856d03542f5b6c535b8874f84b622" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/abca740015a856d03542f5b6c535b8874f84b622", + "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/abca740015a856d03542f5b6c535b8874f84b622", "reference": "abca740015a856d03542f5b6c535b8874f84b622", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-filter": "self.version", - "zendframework/zend-stdlib": "self.version", - "zendframework/zend-validator": "self.version" + "laminas/laminas-filter": "self.version", + "laminas/laminas-stdlib": "self.version", + "laminas/laminas-validator": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-servicemanager": "self.version" + "laminas/laminas-servicemanager": "self.version" }, "suggest": { - "zendframework/zend-servicemanager": "To support plugin manager support" + "laminas/laminas-servicemanager": "To support plugin manager support" }, "type": "library", "extra": { @@ -5208,7 +5208,7 @@ }, "autoload": { "psr-4": { - "Zend\\InputFilter\\": "src/" + "Laminas\\InputFilter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5223,34 +5223,34 @@ "time": "2014-04-15 13:59:53" }, { - "name": "zendframework/zend-json", + "name": "laminas/laminas-json", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-json.git", + "url": "https://github.com/laminas/laminas-json.git", "reference": "5284687fc3aeab27961d2e17ada08973ae6daafe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/5284687fc3aeab27961d2e17ada08973ae6daafe", + "url": "https://api.github.com/repos/laminas/laminas-json/zipball/5284687fc3aeab27961d2e17ada08973ae6daafe", "reference": "5284687fc3aeab27961d2e17ada08973ae6daafe", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-http": "self.version", - "zendframework/zend-server": "self.version" + "laminas/laminas-http": "self.version", + "laminas/laminas-server": "self.version" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-server": "Zend\\Server component", - "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-server": "Laminas\\Server component", + "zendframework/zendxml": "To support Laminas\\Json\\Json::fromXml() usage" }, "type": "library", "extra": { @@ -5261,7 +5261,7 @@ }, "autoload": { "psr-4": { - "Zend\\Json\\": "src/" + "Laminas\\Json\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5269,7 +5269,7 @@ "BSD-3-Clause" ], "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", - "homepage": "https://github.com/zendframework/zend-json", + "homepage": "https://github.com/laminas/laminas-json", "keywords": [ "json", "zf2" @@ -5277,16 +5277,16 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-loader", + "name": "laminas/laminas-loader", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-loader.git", + "url": "https://github.com/laminas/laminas-loader.git", "reference": "5e0bd7e28c644078685f525cf8ae03d9a01ae292" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/5e0bd7e28c644078685f525cf8ae03d9a01ae292", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/5e0bd7e28c644078685f525cf8ae03d9a01ae292", "reference": "5e0bd7e28c644078685f525cf8ae03d9a01ae292", "shasum": "" }, @@ -5307,14 +5307,14 @@ }, "autoload": { "psr-4": { - "Zend\\Loader\\": "src/" + "Laminas\\Loader\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-loader", + "homepage": "https://github.com/laminas/laminas-loader", "keywords": [ "loader", "zf2" @@ -5322,41 +5322,41 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-log", + "name": "laminas/laminas-log", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-log.git", + "url": "https://github.com/laminas/laminas-log.git", "reference": "217611433f5cb56d4420a1db8f164e5db85d815d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/217611433f5cb56d4420a1db8f164e5db85d815d", + "url": "https://api.github.com/repos/laminas/laminas-log/zipball/217611433f5cb56d4420a1db8f164e5db85d815d", "reference": "217611433f5cb56d4420a1db8f164e5db85d815d", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-servicemanager": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-console": "self.version", - "zendframework/zend-db": "self.version", - "zendframework/zend-escaper": "self.version", - "zendframework/zend-mail": "self.version", - "zendframework/zend-validator": "self.version" + "laminas/laminas-console": "self.version", + "laminas/laminas-db": "self.version", + "laminas/laminas-escaper": "self.version", + "laminas/laminas-mail": "self.version", + "laminas/laminas-validator": "self.version" }, "suggest": { "ext-mongo": "*", - "zendframework/zend-console": "Zend\\Console component", - "zendframework/zend-db": "Zend\\Db component", - "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML formatter", - "zendframework/zend-mail": "Zend\\Mail component", - "zendframework/zend-validator": "Zend\\Validator component" + "laminas/laminas-console": "Laminas\\Console component", + "laminas/laminas-db": "Laminas\\Db component", + "laminas/laminas-escaper": "Laminas\\Escaper component, for use in the XML formatter", + "laminas/laminas-mail": "Laminas\\Mail component", + "laminas/laminas-validator": "Laminas\\Validator component" }, "type": "library", "extra": { @@ -5367,7 +5367,7 @@ }, "autoload": { "psr-4": { - "Zend\\Log\\": "src/" + "Laminas\\Log\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5375,7 +5375,7 @@ "BSD-3-Clause" ], "description": "component for general purpose logging", - "homepage": "https://github.com/zendframework/zend-log", + "homepage": "https://github.com/laminas/laminas-log", "keywords": [ "log", "logging", @@ -5384,16 +5384,16 @@ "time": "2014-04-15 13:59:53" }, { - "name": "zendframework/zend-math", + "name": "laminas/laminas-math", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-math.git", + "url": "https://github.com/laminas/laminas-math.git", "reference": "63225fcebb196fc6e20094f5f01e9354779ec31e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/63225fcebb196fc6e20094f5f01e9354779ec31e", + "url": "https://api.github.com/repos/laminas/laminas-math/zipball/63225fcebb196fc6e20094f5f01e9354779ec31e", "reference": "63225fcebb196fc6e20094f5f01e9354779ec31e", "shasum": "" }, @@ -5408,8 +5408,8 @@ "suggest": { "ext-bcmath": "If using the bcmath functionality", "ext-gmp": "If using the gmp functionality", - "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable", - "zendframework/zend-servicemanager": ">= current version, if using the BigInteger::factory functionality" + "ircmaxell/random-lib": "Fallback random byte generator for Laminas\\Math\\Rand if OpenSSL/Mcrypt extensions are unavailable", + "laminas/laminas-servicemanager": ">= current version, if using the BigInteger::factory functionality" }, "type": "library", "extra": { @@ -5420,14 +5420,14 @@ }, "autoload": { "psr-4": { - "Zend\\Math\\": "src/" + "Laminas\\Math\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-math", + "homepage": "https://github.com/laminas/laminas-math", "keywords": [ "math", "zf2" @@ -5435,40 +5435,40 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-modulemanager", + "name": "laminas/laminas-modulemanager", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-modulemanager.git", + "url": "https://github.com/laminas/laminas-modulemanager.git", "reference": "d4591b958e40b8f5ae8110d9b203331437aa19f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/d4591b958e40b8f5ae8110d9b203331437aa19f2", + "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/d4591b958e40b8f5ae8110d9b203331437aa19f2", "reference": "d4591b958e40b8f5ae8110d9b203331437aa19f2", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-eventmanager": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-config": "self.version", - "zendframework/zend-console": "self.version", - "zendframework/zend-loader": "self.version", - "zendframework/zend-mvc": "self.version", - "zendframework/zend-servicemanager": "self.version" + "laminas/laminas-config": "self.version", + "laminas/laminas-console": "self.version", + "laminas/laminas-loader": "self.version", + "laminas/laminas-mvc": "self.version", + "laminas/laminas-servicemanager": "self.version" }, "suggest": { - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-console": "Zend\\Console component", - "zendframework/zend-loader": "Zend\\Loader component", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-console": "Laminas\\Console component", + "laminas/laminas-loader": "Laminas\\Loader component", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { @@ -5479,7 +5479,7 @@ }, "autoload": { "psr-4": { - "Zend\\ModuleManager\\": "src/" + "Laminas\\ModuleManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5494,70 +5494,70 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-mvc", + "name": "laminas/laminas-mvc", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mvc.git", + "url": "https://github.com/laminas/laminas-mvc.git", "reference": "ee76ddd009ecb0c507bb8ab396fbe719aea8f1ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/ee76ddd009ecb0c507bb8ab396fbe719aea8f1ff", + "url": "https://api.github.com/repos/laminas/laminas-mvc/zipball/ee76ddd009ecb0c507bb8ab396fbe719aea8f1ff", "reference": "ee76ddd009ecb0c507bb8ab396fbe719aea8f1ff", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-form": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-eventmanager": "self.version", + "laminas/laminas-form": "self.version", + "laminas/laminas-servicemanager": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-authentication": "self.version", - "zendframework/zend-console": "self.version", - "zendframework/zend-di": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-form": "self.version", - "zendframework/zend-http": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-inputfilter": "self.version", - "zendframework/zend-json": "self.version", - "zendframework/zend-log": "self.version", - "zendframework/zend-modulemanager": "self.version", - "zendframework/zend-serializer": "self.version", - "zendframework/zend-session": "self.version", - "zendframework/zend-text": "self.version", - "zendframework/zend-uri": "self.version", - "zendframework/zend-validator": "self.version", + "laminas/laminas-authentication": "self.version", + "laminas/laminas-console": "self.version", + "laminas/laminas-di": "self.version", + "laminas/laminas-filter": "self.version", + "laminas/laminas-form": "self.version", + "laminas/laminas-http": "self.version", + "laminas/laminas-i18n": "self.version", + "laminas/laminas-inputfilter": "self.version", + "laminas/laminas-json": "self.version", + "laminas/laminas-log": "self.version", + "laminas/laminas-modulemanager": "self.version", + "laminas/laminas-serializer": "self.version", + "laminas/laminas-session": "self.version", + "laminas/laminas-text": "self.version", + "laminas/laminas-uri": "self.version", + "laminas/laminas-validator": "self.version", "zendframework/zend-version": "self.version", - "zendframework/zend-view": "self.version" + "laminas/laminas-view": "self.version" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component for Identity plugin", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-console": "Zend\\Console component", - "zendframework/zend-di": "Zend\\Di component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-form": "Zend\\Form component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component for translatable segments", - "zendframework/zend-inputfilter": "Zend\\Inputfilter component", - "zendframework/zend-json": "Zend\\Json component", - "zendframework/zend-log": "Zend\\Log component", - "zendframework/zend-modulemanager": "Zend\\ModuleManager component", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-session": "Zend\\Session component for FlashMessenger, PRG, and FPRG plugins", - "zendframework/zend-stdlib": "Zend\\Stdlib component", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-uri": "Zend\\Uri component", - "zendframework/zend-validator": "Zend\\Validator component", + "laminas/laminas-authentication": "Laminas\\Authentication component for Identity plugin", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-console": "Laminas\\Console component", + "laminas/laminas-di": "Laminas\\Di component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-form": "Laminas\\Form component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component for translatable segments", + "laminas/laminas-inputfilter": "Zend\\Inputfilter component", + "laminas/laminas-json": "Laminas\\Json component", + "laminas/laminas-log": "Laminas\\Log component", + "laminas/laminas-modulemanager": "Laminas\\ModuleManager component", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-session": "Laminas\\Session component for FlashMessenger, PRG, and FPRG plugins", + "laminas/laminas-stdlib": "Laminas\\Stdlib component", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-uri": "Laminas\\Uri component", + "laminas/laminas-validator": "Laminas\\Validator component", "zendframework/zend-version": "Zend\\Version component", - "zendframework/zend-view": "Zend\\View component" + "laminas/laminas-view": "Laminas\\View component" }, "type": "library", "extra": { @@ -5568,14 +5568,14 @@ }, "autoload": { "psr-4": { - "Zend\\Mvc\\": "src/" + "Laminas\\Mvc\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-mvc", + "homepage": "https://github.com/laminas/laminas-mvc", "keywords": [ "mvc", "zf2" @@ -5583,33 +5583,33 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-serializer", + "name": "laminas/laminas-serializer", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-serializer.git", + "url": "https://github.com/laminas/laminas-serializer.git", "reference": "22f73b0d0ff1158216bd5bcacf6bd00f7be1a0f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/22f73b0d0ff1158216bd5bcacf6bd00f7be1a0f6", + "url": "https://api.github.com/repos/laminas/laminas-serializer/zipball/22f73b0d0ff1158216bd5bcacf6bd00f7be1a0f6", "reference": "22f73b0d0ff1158216bd5bcacf6bd00f7be1a0f6", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-json": "self.version", - "zendframework/zend-math": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-json": "self.version", + "laminas/laminas-math": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-servicemanager": "self.version" + "laminas/laminas-servicemanager": "self.version" }, "suggest": { - "zendframework/zend-servicemanager": "To support plugin manager support" + "laminas/laminas-servicemanager": "To support plugin manager support" }, "type": "library", "extra": { @@ -5620,7 +5620,7 @@ }, "autoload": { "psr-4": { - "Zend\\Serializer\\": "src/" + "Laminas\\Serializer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5628,7 +5628,7 @@ "BSD-3-Clause" ], "description": "provides an adapter based interface to simply generate storable representation of PHP types by different facilities, and recover", - "homepage": "https://github.com/zendframework/zend-serializer", + "homepage": "https://github.com/laminas/laminas-serializer", "keywords": [ "serializer", "zf2" @@ -5636,23 +5636,23 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-server", + "name": "laminas/laminas-server", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-server.git", + "url": "https://github.com/laminas/laminas-server.git", "reference": "bc5fb97f4ac48a5dc54bd18dded21a3e1eea552c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-server/zipball/bc5fb97f4ac48a5dc54bd18dded21a3e1eea552c", + "url": "https://api.github.com/repos/laminas/laminas-server/zipball/bc5fb97f4ac48a5dc54bd18dded21a3e1eea552c", "reference": "bc5fb97f4ac48a5dc54bd18dded21a3e1eea552c", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-code": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-code": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -5668,14 +5668,14 @@ }, "autoload": { "psr-4": { - "Zend\\Server\\": "src/" + "Laminas\\Server\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-server", + "homepage": "https://github.com/laminas/laminas-server", "keywords": [ "server", "zf2" @@ -5683,16 +5683,16 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-servicemanager", + "name": "laminas/laminas-servicemanager", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-servicemanager.git", + "url": "https://github.com/laminas/laminas-servicemanager.git", "reference": "7a428b595a1fcd4c2a8026ee5d5f89a56036f682" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/7a428b595a1fcd4c2a8026ee5d5f89a56036f682", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/7a428b595a1fcd4c2a8026ee5d5f89a56036f682", "reference": "7a428b595a1fcd4c2a8026ee5d5f89a56036f682", "shasum": "" }, @@ -5703,10 +5703,10 @@ "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-di": "self.version" + "laminas/laminas-di": "self.version" }, "suggest": { - "zendframework/zend-di": "Zend\\Di component" + "laminas/laminas-di": "Laminas\\Di component" }, "type": "library", "extra": { @@ -5717,7 +5717,7 @@ }, "autoload": { "psr-4": { - "Zend\\ServiceManager\\": "src/" + "Laminas\\ServiceManager\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5732,33 +5732,33 @@ "time": "2014-04-15 13:59:53" }, { - "name": "zendframework/zend-soap", + "name": "laminas/laminas-soap", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-soap.git", + "url": "https://github.com/laminas/laminas-soap.git", "reference": "fab9f5309be04cacf01af54f694aed5102592c5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/fab9f5309be04cacf01af54f694aed5102592c5c", + "url": "https://api.github.com/repos/laminas/laminas-soap/zipball/fab9f5309be04cacf01af54f694aed5102592c5c", "reference": "fab9f5309be04cacf01af54f694aed5102592c5c", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-server": "self.version", - "zendframework/zend-stdlib": "self.version", - "zendframework/zend-uri": "self.version" + "laminas/laminas-server": "self.version", + "laminas/laminas-stdlib": "self.version", + "laminas/laminas-uri": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-http": "self.version" + "laminas/laminas-http": "self.version" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component" + "laminas/laminas-http": "Laminas\\Http component" }, "type": "library", "extra": { @@ -5769,14 +5769,14 @@ }, "autoload": { "psr-4": { - "Zend\\Soap\\": "src/" + "Laminas\\Soap\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-soap", + "homepage": "https://github.com/laminas/laminas-soap", "keywords": [ "soap", "zf2" @@ -5784,16 +5784,16 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-stdlib", + "name": "laminas/laminas-stdlib", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-stdlib.git", + "url": "https://github.com/laminas/laminas-stdlib.git", "reference": "6079302963d57f36a9ba92ed3f38b992997aa78d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/6079302963d57f36a9ba92ed3f38b992997aa78d", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/6079302963d57f36a9ba92ed3f38b992997aa78d", "reference": "6079302963d57f36a9ba92ed3f38b992997aa78d", "shasum": "" }, @@ -5804,16 +5804,16 @@ "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-serializer": "self.version", - "zendframework/zend-servicemanager": "self.version" + "laminas/laminas-eventmanager": "self.version", + "laminas/laminas-filter": "self.version", + "laminas/laminas-serializer": "self.version", + "laminas/laminas-servicemanager": "self.version" }, "suggest": { - "zendframework/zend-eventmanager": "To support aggregate hydrator usage", - "zendframework/zend-filter": "To support naming strategy hydrator usage", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager": "To support hydrator plugin manager usage" + "laminas/laminas-eventmanager": "To support aggregate hydrator usage", + "laminas/laminas-filter": "To support naming strategy hydrator usage", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager": "To support hydrator plugin manager usage" }, "type": "library", "extra": { @@ -5824,14 +5824,14 @@ }, "autoload": { "psr-4": { - "Zend\\Stdlib\\": "src/" + "Laminas\\Stdlib\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-stdlib", + "homepage": "https://github.com/laminas/laminas-stdlib", "keywords": [ "stdlib", "zf2" @@ -5839,23 +5839,23 @@ "time": "2014-04-15 13:59:53" }, { - "name": "zendframework/zend-text", + "name": "laminas/laminas-text", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-text.git", + "url": "https://github.com/laminas/laminas-text.git", "reference": "e9b3fffcc6241f7cfdb33282ed10979cd8ba9b90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-text/zipball/e9b3fffcc6241f7cfdb33282ed10979cd8ba9b90", + "url": "https://api.github.com/repos/laminas/laminas-text/zipball/e9b3fffcc6241f7cfdb33282ed10979cd8ba9b90", "reference": "e9b3fffcc6241f7cfdb33282ed10979cd8ba9b90", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-servicemanager": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -5871,14 +5871,14 @@ }, "autoload": { "psr-4": { - "Zend\\Text\\": "src/" + "Laminas\\Text\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "homepage": "https://github.com/zendframework/zend-text", + "homepage": "https://github.com/laminas/laminas-text", "keywords": [ "text", "zf2" @@ -5886,23 +5886,23 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-uri", + "name": "laminas/laminas-uri", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-uri.git", + "url": "https://github.com/laminas/laminas-uri.git", "reference": "0de6ba8c166a235588783ff8e88a19b364630d89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/0de6ba8c166a235588783ff8e88a19b364630d89", + "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/0de6ba8c166a235588783ff8e88a19b364630d89", "reference": "0de6ba8c166a235588783ff8e88a19b364630d89", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-escaper": "self.version", - "zendframework/zend-validator": "self.version" + "laminas/laminas-escaper": "self.version", + "laminas/laminas-validator": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", @@ -5918,7 +5918,7 @@ }, "autoload": { "psr-4": { - "Zend\\Uri\\": "src/" + "Laminas\\Uri\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5926,7 +5926,7 @@ "BSD-3-Clause" ], "description": "a component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", - "homepage": "https://github.com/zendframework/zend-uri", + "homepage": "https://github.com/laminas/laminas-uri", "keywords": [ "uri", "zf2" @@ -5934,44 +5934,44 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-validator", + "name": "laminas/laminas-validator", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-validator.git", + "url": "https://github.com/laminas/laminas-validator.git", "reference": "a5f97f6c3a27a27b1235f724ad0048715459f013" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/a5f97f6c3a27a27b1235f724ad0048715459f013", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/a5f97f6c3a27a27b1235f724ad0048715459f013", "reference": "a5f97f6c3a27a27b1235f724ad0048715459f013", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-db": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-math": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-session": "self.version", - "zendframework/zend-uri": "self.version" + "laminas/laminas-db": "self.version", + "laminas/laminas-filter": "self.version", + "laminas/laminas-i18n": "self.version", + "laminas/laminas-math": "self.version", + "laminas/laminas-servicemanager": "self.version", + "laminas/laminas-session": "self.version", + "laminas/laminas-uri": "self.version" }, "suggest": { - "zendframework/zend-db": "Zend\\Db component", - "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", - "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages as well as to use the various Date validators", - "zendframework/zend-math": "Zend\\Math component", + "laminas/laminas-db": "Laminas\\Db component", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages as well as to use the various Date validators", + "laminas/laminas-math": "Laminas\\Math component", "zendframework/zend-resources": "Translations of validator messages", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", - "zendframework/zend-session": "Zend\\Session component", - "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators" }, "type": "library", "extra": { @@ -5982,7 +5982,7 @@ }, "autoload": { "psr-4": { - "Zend\\Validator\\": "src/" + "Laminas\\Validator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -5990,7 +5990,7 @@ "BSD-3-Clause" ], "description": "provides a set of commonly needed validators", - "homepage": "https://github.com/zendframework/zend-validator", + "homepage": "https://github.com/laminas/laminas-validator", "keywords": [ "validator", "zf2" @@ -5998,57 +5998,57 @@ "time": "2014-04-14 19:50:30" }, { - "name": "zendframework/zend-view", + "name": "laminas/laminas-view", "version": "2.3.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-view.git", + "url": "https://github.com/laminas/laminas-view.git", "reference": "e308d498fa7851f26bd639bfe3ebbfba397c47bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/e308d498fa7851f26bd639bfe3ebbfba397c47bc", + "url": "https://api.github.com/repos/laminas/laminas-view/zipball/e308d498fa7851f26bd639bfe3ebbfba397c47bc", "reference": "e308d498fa7851f26bd639bfe3ebbfba397c47bc", "shasum": "" }, "require": { "php": ">=5.3.23", - "zendframework/zend-eventmanager": "self.version", - "zendframework/zend-loader": "self.version", - "zendframework/zend-stdlib": "self.version" + "laminas/laminas-eventmanager": "self.version", + "laminas/laminas-loader": "self.version", + "laminas/laminas-stdlib": "self.version" }, "require-dev": { "fabpot/php-cs-fixer": "1.7.*", "phpunit/phpunit": "~4.0", "satooshi/php-coveralls": "dev-master", - "zendframework/zend-authentication": "self.version", - "zendframework/zend-escaper": "self.version", - "zendframework/zend-feed": "self.version", - "zendframework/zend-filter": "self.version", - "zendframework/zend-http": "self.version", - "zendframework/zend-i18n": "self.version", - "zendframework/zend-json": "self.version", - "zendframework/zend-mvc": "self.version", - "zendframework/zend-navigation": "self.version", - "zendframework/zend-paginator": "self.version", - "zendframework/zend-permissions-acl": "self.version", - "zendframework/zend-servicemanager": "self.version", - "zendframework/zend-uri": "self.version" + "laminas/laminas-authentication": "self.version", + "laminas/laminas-escaper": "self.version", + "laminas/laminas-feed": "self.version", + "laminas/laminas-filter": "self.version", + "laminas/laminas-http": "self.version", + "laminas/laminas-i18n": "self.version", + "laminas/laminas-json": "self.version", + "laminas/laminas-mvc": "self.version", + "laminas/laminas-navigation": "self.version", + "laminas/laminas-paginator": "self.version", + "laminas/laminas-permissions-acl": "self.version", + "laminas/laminas-servicemanager": "self.version", + "laminas/laminas-uri": "self.version" }, "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component", - "zendframework/zend-escaper": "Zend\\Escaper component", - "zendframework/zend-feed": "Zend\\Feed component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json component", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-navigation": "Zend\\Navigation component", - "zendframework/zend-paginator": "Zend\\Paginator component", - "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component" + "laminas/laminas-authentication": "Laminas\\Authentication component", + "laminas/laminas-escaper": "Laminas\\Escaper component", + "laminas/laminas-feed": "Laminas\\Feed component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-json": "Laminas\\Json component", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-navigation": "Laminas\\Navigation component", + "laminas/laminas-paginator": "Laminas\\Paginator component", + "laminas/laminas-permissions-acl": "Laminas\\Permissions\\Acl component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-uri": "Laminas\\Uri component" }, "type": "library", "extra": { @@ -6059,7 +6059,7 @@ }, "autoload": { "psr-4": { - "Zend\\View\\": "src/" + "Laminas\\View\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6067,7 +6067,7 @@ "BSD-3-Clause" ], "description": "provides a system of helpers, output filters, and variable escaping", - "homepage": "https://github.com/zendframework/zend-view", + "homepage": "https://github.com/laminas/laminas-view", "keywords": [ "view", "zf2" diff --git a/dev/tests/integration/testsuite/Magento/Sitemap/Model/SitemapTest.php b/dev/tests/integration/testsuite/Magento/Sitemap/Model/SitemapTest.php index 73863e0915c66..7f4a8df2f3c6f 100644 --- a/dev/tests/integration/testsuite/Magento/Sitemap/Model/SitemapTest.php +++ b/dev/tests/integration/testsuite/Magento/Sitemap/Model/SitemapTest.php @@ -14,7 +14,7 @@ use Magento\Sitemap\Model\Sitemap; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Request; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; use PHPUnit\Framework\TestCase; /** diff --git a/dev/tests/integration/testsuite/Magento/Store/App/FrontController/Plugin/RequestPreprocessorTest.php b/dev/tests/integration/testsuite/Magento/Store/App/FrontController/Plugin/RequestPreprocessorTest.php index a1a99ecd32b89..b4d1f94b74ffe 100644 --- a/dev/tests/integration/testsuite/Magento/Store/App/FrontController/Plugin/RequestPreprocessorTest.php +++ b/dev/tests/integration/testsuite/Magento/Store/App/FrontController/Plugin/RequestPreprocessorTest.php @@ -11,7 +11,7 @@ use Magento\Framework\App\Config\Value; use Magento\Framework\Data\Form\FormKey; use Magento\TestFramework\Response; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; /** * Tests \Magento\Store\App\FrontController\Plugin\RequestPreprocessor. diff --git a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php index 00de5544d8fb7..5477c8007be04 100644 --- a/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php +++ b/dev/tests/integration/testsuite/Magento/Store/Model/StoreTest.php @@ -11,7 +11,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\UrlInterface; use Magento\Store\Api\StoreRepositoryInterface; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) diff --git a/dev/tests/integration/testsuite/Magento/Ui/Controller/Index/RenderTest.php b/dev/tests/integration/testsuite/Magento/Ui/Controller/Index/RenderTest.php index c63a6fe75f5fc..5873c0e1da53e 100644 --- a/dev/tests/integration/testsuite/Magento/Ui/Controller/Index/RenderTest.php +++ b/dev/tests/integration/testsuite/Magento/Ui/Controller/Index/RenderTest.php @@ -8,7 +8,7 @@ namespace Magento\Ui\Controller\Index; use Magento\TestFramework\TestCase\AbstractController; -use Zend\Http\Headers; +use Laminas\Http\Headers; /** * Test component rendering on storefront. diff --git a/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php b/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php index f6ea0c393260a..c4b69cc5150ba 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php +++ b/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php @@ -5,9 +5,9 @@ */ namespace Magento\TestFramework\Integrity\Library; -use Zend\Code\Reflection\ClassReflection; -use Zend\Code\Reflection\FileReflection; -use Zend\Code\Reflection\ParameterReflection; +use Laminas\Code\Reflection\ClassReflection; +use Laminas\Code\Reflection\FileReflection; +use Laminas\Code\Reflection\ParameterReflection; /** */ @@ -28,7 +28,7 @@ public function getDependencies(FileReflection $fileReflection) foreach ($fileReflection->getClasses() as $class) { /** @var ClassReflection $class */ foreach ($class->getMethods() as $method) { - /** @var \Zend\Code\Reflection\MethodReflection $method */ + /** @var \Laminas\Code\Reflection\MethodReflection $method */ if ($method->getDeclaringClass()->getName() != $class->getName()) { continue; } diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php index 4e5bb8d9d0aa9..856ba1f80ac68 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php @@ -17,7 +17,7 @@ class InjectableTest extends \PHPUnit\Framework\TestCase protected $injectable; /** - * @var \Zend\Code\Reflection\FileReflection + * @var \Laminas\Code\Reflection\FileReflection */ protected $fileReflection; @@ -38,23 +38,23 @@ public function setUp() { $this->injectable = new Injectable(); $this->fileReflection = $this->getMockBuilder( - \Zend\Code\Reflection\FileReflection::class + \Laminas\Code\Reflection\FileReflection::class )->disableOriginalConstructor()->getMock(); $classReflection = $this->getMockBuilder( - \Zend\Code\Reflection\ClassReflection::class + \Laminas\Code\Reflection\ClassReflection::class )->disableOriginalConstructor()->getMock(); $methodReflection = $this->getMockBuilder( - \Zend\Code\Reflection\MethodReflection::class + \Laminas\Code\Reflection\MethodReflection::class )->disableOriginalConstructor()->getMock(); $this->parameterReflection = $this->getMockBuilder( - \Zend\Code\Reflection\ParameterReflection::class + \Laminas\Code\Reflection\ParameterReflection::class )->disableOriginalConstructor()->getMock(); $this->declaredClass = $this->getMockBuilder( - \Zend\Code\Reflection\ClassReflection::class + \Laminas\Code\Reflection\ClassReflection::class )->disableOriginalConstructor()->getMock(); $methodReflection->expects( @@ -98,7 +98,7 @@ public function setUp() public function testGetDependencies() { $classReflection = $this->getMockBuilder( - \Zend\Code\Reflection\ClassReflection::class + \Laminas\Code\Reflection\ClassReflection::class )->disableOriginalConstructor()->getMock(); $classReflection->expects( diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php index b5a4e41b63279..c5447ef716eb2 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php @@ -501,7 +501,7 @@ private function removeSpecialCases(array $badClasses, string $file, string $con } // Remove usage of classes that have been declared as "use" or "include" - // Also deals with case like: "use \Zend\Code\Scanner\FileScanner, Magento\Tools\Di\Compiler\Log\Log;" + // Also deals with case like: "use \Laminas\Code\Scanner\FileScanner, Magento\Tools\Di\Compiler\Log\Log;" // (continued) where there is a comma separating two different classes. if (preg_match('/use\s.*[\\n]?.*' . str_replace('\\', '\\\\', $badClass) . '[\,\;]/', $contents)) { unset($badClasses[array_search($badClass, $badClasses)]); diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php index a4baacbe4d169..b2c117960352a 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php @@ -11,7 +11,7 @@ use Magento\TestFramework\Integrity\Library\Injectable; use Magento\TestFramework\Integrity\Library\PhpParser\ParserFactory; use Magento\TestFramework\Integrity\Library\PhpParser\Tokens; -use Zend\Code\Reflection\FileReflection; +use Laminas\Code\Reflection\FileReflection; /** * Test check if Magento library components contain incorrect dependencies to application layer diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php index 571ed4afbb6ce..23c72593c9fc8 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php @@ -170,7 +170,7 @@ function ($filename) { if (preg_match('/' . $extensibleClassPattern . '/', $fileContent) && !preg_match('/' . $abstractExtensibleClassPattern . '/', $fileContent) ) { - $fileReflection = new \Zend\Code\Reflection\FileReflection($filename, true); + $fileReflection = new \Laminas\Code\Reflection\FileReflection($filename, true); foreach ($fileReflection->getClasses() as $classReflection) { if ($classReflection->isSubclassOf(self::EXTENSIBLE_DATA_INTERFACE)) { $methodsToCheck = ['setExtensionAttributes', 'getExtensionAttributes']; diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php index 6dd9d2f2bddfd..849a57911a77e 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php @@ -167,7 +167,7 @@ public function publicPHPTypesDataProvider() * Retrieve list of classes and interfaces declared in the file * * @param string $file - * @return \Zend\Code\Scanner\ClassScanner[] + * @return \Laminas\Code\Scanner\ClassScanner[] */ private function getDeclaredClassesAndInterfaces($file) { diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index af785c28db414..f386aab71a57c 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -4238,8 +4238,8 @@ 'Magento\Elasticsearch\Test\Unit\Model\SearchAdapter\ConnectionManagerTest', 'Magento\Elasticsearch\Test\Unit\SearchAdapter\ConnectionManagerTest' ], - ['Zend_Feed', 'Zend\Feed'], - ['Zend_Uri', 'Zend\Uri\Uri'], + ['Zend_Feed', 'Laminas\Feed'], + ['Zend_Uri', 'Laminas\Uri\Uri'], ['Zend_Mime', 'Magento\Framework\HTTP\Mime'], ['Magento\Framework\Encryption\Crypt', 'Magento\Framework\Encryption\EncryptionAdapterInterface'], ['Magento\Wishlist\Setup\Patch\Schema\AddProductIdConstraint'], diff --git a/dev/tools/UpgradeScripts/pre_composer_update_2.3.php b/dev/tools/UpgradeScripts/pre_composer_update_2.3.php index 665af637443be..e6f1ddb31c4a3 100644 --- a/dev/tools/UpgradeScripts/pre_composer_update_2.3.php +++ b/dev/tools/UpgradeScripts/pre_composer_update_2.3.php @@ -17,7 +17,7 @@ Steps included: - Require new version of the metapackage - Update "require-dev" section - - Add "Zend\\Mvc\\Controller\\": "setup/src/Zend/Mvc/Controller/" to composer.json "autoload":"psr-4" section + - Add "Laminas\\Mvc\\Controller\\": "setup/src/Zend/Mvc/Controller/" to composer.json "autoload":"psr-4" section - Update Magento/Updater if it's installed - Update name, version, and description fields in the root composer.json @@ -248,7 +248,7 @@ runComposer('remove --dev sjparkinson/static-review fabpot/php-cs-fixer --no-update'); output('\nAdding "Zend\\\\Mvc\\\\Controller\\\\": "setup/src/Zend/Mvc/Controller/" to "autoload": "psr-4"'); - $composerData['autoload']['psr-4']['Zend\\Mvc\\Controller\\'] = 'setup/src/Zend/Mvc/Controller/'; + $composerData['autoload']['psr-4']['Laminas\\Mvc\\Controller\\'] = 'setup/src/Zend/Mvc/Controller/'; if (preg_match('/^magento\/project\-(community|enterprise)\-edition$/', $composerData['name'])) { output('\nUpdating project name, version, and description'); diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index 1154749a75b94..c504f463f68dc 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -7,7 +7,7 @@ namespace Magento\Framework\App; -use Zend\Feed\Writer\FeedFactory; +use Laminas\Feed\Writer\FeedFactory; /** * Default XML feed class diff --git a/lib/internal/Magento/Framework/App/Request/Http.php b/lib/internal/Magento/Framework/App/Request/Http.php index 4e709ed276954..fbcf20b9f4f99 100644 --- a/lib/internal/Magento/Framework/App/Request/Http.php +++ b/lib/internal/Magento/Framework/App/Request/Http.php @@ -106,7 +106,7 @@ class Http extends Request implements RequestContentInterface, RequestSafetyInte * @param ConfigInterface $routeConfig * @param PathInfoProcessorInterface $pathInfoProcessor * @param ObjectManagerInterface $objectManager - * @param \Zend\Uri\UriInterface|string|null $uri + * @param \Laminas\Uri\UriInterface|string|null $uri * @param array $directFrontNames * @param PathInfo|null $pathInfoService */ diff --git a/lib/internal/Magento/Framework/App/Request/HttpMethodMap.php b/lib/internal/Magento/Framework/App/Request/HttpMethodMap.php index 4e7216954b0d4..ddc3a03416c68 100644 --- a/lib/internal/Magento/Framework/App/Request/HttpMethodMap.php +++ b/lib/internal/Magento/Framework/App/Request/HttpMethodMap.php @@ -59,7 +59,7 @@ private function processMap(array $map): array * * @return string[] * - * @see \Zend\Http\Request Has list of methods as METHOD_* constants. + * @see \Laminas\Http\Request Has list of methods as METHOD_* constants. */ public function getMap(): array { diff --git a/lib/internal/Magento/Framework/App/Response/HttpInterface.php b/lib/internal/Magento/Framework/App/Response/HttpInterface.php index 08b1257f73abe..17825aeb88d65 100644 --- a/lib/internal/Magento/Framework/App/Response/HttpInterface.php +++ b/lib/internal/Magento/Framework/App/Response/HttpInterface.php @@ -48,7 +48,7 @@ public function setHeader($name, $value, $replace = false); * If header with specified name was not found returns false. * * @param string $name - * @return \Zend\Http\Header\HeaderInterface|bool + * @return \Laminas\Http\Header\HeaderInterface|bool * @since 100.2.0 */ public function getHeader($name); diff --git a/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php b/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php index db200f962f5b5..81df376a5d42f 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/PageCache/KernelTest.php @@ -52,7 +52,7 @@ class KernelTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $headersMock = $this->createMock(\Zend\Http\Headers::class); + $headersMock = $this->createMock(\Laminas\Http\Headers::class); $this->cacheMock = $this->createMock(\Magento\Framework\App\PageCache\Cache::class); $this->fullPageCacheMock = $this->createMock(\Magento\PageCache\Model\Cache\Type::class); $this->contextMock = $this->createMock(\Magento\Framework\App\Http\Context::class); @@ -204,7 +204,7 @@ function ($value) { } ); - $cacheControlHeader = \Zend\Http\Header\CacheControl::fromString( + $cacheControlHeader = \Laminas\Http\Header\CacheControl::fromString( 'Cache-Control: public, max-age=100, s-maxage=100' ); @@ -261,7 +261,7 @@ public function testProcessSaveCacheDataProvider() */ public function testProcessNotSaveCache($cacheControlHeader, $httpCode, $isGet, $overrideHeaders) { - $header = \Zend\Http\Header\CacheControl::fromString("Cache-Control: $cacheControlHeader"); + $header = \Laminas\Http\Header\CacheControl::fromString("Cache-Control: $cacheControlHeader"); $this->responseMock->expects( $this->once() )->method( diff --git a/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php b/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php index 0a48945e55c2d..397d2c09fbf47 100644 --- a/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php +++ b/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php @@ -5,10 +5,10 @@ */ namespace Magento\Framework\Code\Generator; -use Zend\Code\Generator\MethodGenerator; -use Zend\Code\Generator\PropertyGenerator; +use Laminas\Code\Generator\MethodGenerator; +use Laminas\Code\Generator\PropertyGenerator; -class ClassGenerator extends \Zend\Code\Generator\ClassGenerator implements +class ClassGenerator extends \Laminas\Code\Generator\ClassGenerator implements \Magento\Framework\Code\Generator\CodeGeneratorInterface { /** @@ -86,7 +86,7 @@ protected function _setDataToObject($object, array $data, array $map) */ public function setClassDocBlock(array $docBlock) { - $docBlockObject = new \Zend\Code\Generator\DocBlockGenerator(); + $docBlockObject = new \Laminas\Code\Generator\DocBlockGenerator(); $docBlockObject->setWordWrap(false); $this->_setDataToObject($docBlockObject, $docBlock, $this->_docBlockOptions); @@ -115,7 +115,7 @@ public function addMethods(array $methods) ) { $parametersArray = []; foreach ($methodOptions['parameters'] as $parameterOptions) { - $parameterObject = new \Zend\Code\Generator\ParameterGenerator(); + $parameterObject = new \Laminas\Code\Generator\ParameterGenerator(); $this->_setDataToObject($parameterObject, $parameterOptions, $this->_parameterOptions); $parametersArray[] = $parameterObject; } @@ -124,7 +124,7 @@ public function addMethods(array $methods) } if (isset($methodOptions['docblock']) && is_array($methodOptions['docblock'])) { - $docBlockObject = new \Zend\Code\Generator\DocBlockGenerator(); + $docBlockObject = new \Laminas\Code\Generator\DocBlockGenerator(); $docBlockObject->setWordWrap(false); $this->_setDataToObject($docBlockObject, $methodOptions['docblock'], $this->_docBlockOptions); @@ -172,7 +172,7 @@ public function addProperties(array $properties) if (isset($propertyOptions['docblock'])) { $docBlock = $propertyOptions['docblock']; if (is_array($docBlock)) { - $docBlockObject = new \Zend\Code\Generator\DocBlockGenerator(); + $docBlockObject = new \Laminas\Code\Generator\DocBlockGenerator(); $docBlockObject->setWordWrap(false); $this->_setDataToObject($docBlockObject, $docBlock, $this->_docBlockOptions); $propertyObject->setDocBlock($docBlockObject); diff --git a/lib/internal/Magento/Framework/Code/Generator/CodeGeneratorInterface.php b/lib/internal/Magento/Framework/Code/Generator/CodeGeneratorInterface.php index 81ada6a1ee369..59fc5522a12e2 100644 --- a/lib/internal/Magento/Framework/Code/Generator/CodeGeneratorInterface.php +++ b/lib/internal/Magento/Framework/Code/Generator/CodeGeneratorInterface.php @@ -9,7 +9,7 @@ * Interface \Magento\Framework\Code\Generator\CodeGeneratorInterface * */ -interface CodeGeneratorInterface extends \Zend\Code\Generator\GeneratorInterface +interface CodeGeneratorInterface extends \Laminas\Code\Generator\GeneratorInterface { /** * Set class name. diff --git a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php index 3efe110ccf193..78360b9b7b1ad 100644 --- a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php +++ b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\Code\Generator; -use Zend\Code\Generator\ValueGenerator; +use Laminas\Code\Generator\ValueGenerator; abstract class EntityAbstract { diff --git a/lib/internal/Magento/Framework/Code/Generator/InterfaceMethodGenerator.php b/lib/internal/Magento/Framework/Code/Generator/InterfaceMethodGenerator.php index d73c94eb89e00..ddcabdcf85724 100644 --- a/lib/internal/Magento/Framework/Code/Generator/InterfaceMethodGenerator.php +++ b/lib/internal/Magento/Framework/Code/Generator/InterfaceMethodGenerator.php @@ -8,7 +8,7 @@ /** * Interface method generator. */ -class InterfaceMethodGenerator extends \Zend\Code\Generator\MethodGenerator +class InterfaceMethodGenerator extends \Laminas\Code\Generator\MethodGenerator { /** * {@inheritdoc} diff --git a/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php b/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php index d2e6c01997b30..08f93ec514b55 100644 --- a/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php +++ b/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php @@ -54,7 +54,7 @@ public function getConstructorArguments(\ReflectionClass $class, $groupByPositio return $output; } - $constructor = new \Zend\Code\Reflection\MethodReflection($class->getName(), '__construct'); + $constructor = new \Laminas\Code\Reflection\MethodReflection($class->getName(), '__construct'); foreach ($constructor->getParameters() as $parameter) { $name = $parameter->getName(); $position = $parameter->getPosition(); @@ -90,10 +90,10 @@ public function getConstructorArguments(\ReflectionClass $class, $groupByPositio * Process argument type. * * @param \ReflectionClass $class - * @param \Zend\Code\Reflection\ParameterReflection $parameter + * @param \Laminas\Code\Reflection\ParameterReflection $parameter * @return string */ - private function processType(\ReflectionClass $class, \Zend\Code\Reflection\ParameterReflection $parameter) + private function processType(\ReflectionClass $class, \Laminas\Code\Reflection\ParameterReflection $parameter) { if ($parameter->getClass()) { return NamespaceResolver::NS_SEPARATOR . $parameter->getClass()->getName(); diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php index 79d94b4d25a3d..bdcd834527c91 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php @@ -138,11 +138,11 @@ public function testSetClassDocBlock() /** * @param array $expectedDocBlock - * @param \Zend\Code\Generator\DocBlockGenerator $actualDocBlock + * @param \Laminas\Code\Generator\DocBlockGenerator $actualDocBlock */ protected function _assertDocBlockData( array $expectedDocBlock, - \Zend\Code\Generator\DocBlockGenerator $actualDocBlock + \Laminas\Code\Generator\DocBlockGenerator $actualDocBlock ) { // assert plain string data foreach ($expectedDocBlock as $propertyName => $propertyData) { @@ -156,7 +156,7 @@ protected function _assertDocBlockData( $expectedTagsData = $expectedDocBlock['tags']; $actualTags = $actualDocBlock->getTags(); $this->assertSameSize($expectedTagsData, $actualTags); - /** @var $actualTag \Zend\Code\Generator\DocBlock\Tag */ + /** @var $actualTag \Laminas\Code\Generator\DocBlock\Tag */ foreach ($actualTags as $actualTag) { $tagName = $actualTag->getName(); $this->assertArrayHasKey($tagName, $expectedTagsData); @@ -173,7 +173,7 @@ public function testAddMethods() $this->assertSameSize($this->_methodData, $actualMethods); - /** @var $method \Zend\Code\Generator\MethodGenerator */ + /** @var $method \Laminas\Code\Generator\MethodGenerator */ foreach ($actualMethods as $methodName => $method) { $this->assertArrayHasKey($methodName, $this->_methodData); $expectedMethodData = $this->_methodData[$methodName]; @@ -196,7 +196,7 @@ public function testAddMethods() foreach ($expectedMethodData['parameters'] as $parameterData) { $parameterName = $parameterData['name']; $this->assertArrayHasKey($parameterName, $actualParameters); - /** @var $actualParameter \Zend\Code\Generator\ParameterGenerator */ + /** @var $actualParameter \Laminas\Code\Generator\ParameterGenerator */ $actualParameter = $actualParameters[$parameterName]; $this->assertEquals($parameterName, $actualParameter->getName()); @@ -210,7 +210,7 @@ public function testAddMethods() // assert default value if (isset($parameterData['defaultValue'])) { - /** @var $actualDefaultValue \Zend\Code\Generator\ValueGenerator */ + /** @var $actualDefaultValue \Laminas\Code\Generator\ValueGenerator */ $actualDefaultValue = $actualParameter->getDefaultValue(); $this->assertEquals($parameterData['defaultValue'], $actualDefaultValue->getValue()); } @@ -242,11 +242,11 @@ protected function _assertFlag($flagType, array $expectedData, $actualObject) /** * @param array $expectedData - * @param \Zend\Code\Generator\AbstractMemberGenerator $actualObject + * @param \Laminas\Code\Generator\AbstractMemberGenerator $actualObject */ protected function _assertVisibility( array $expectedData, - \Zend\Code\Generator\AbstractMemberGenerator $actualObject + \Laminas\Code\Generator\AbstractMemberGenerator $actualObject ) { $expectedVisibility = isset($expectedData['visibility']) ? $expectedData['visibility'] : 'public'; $this->assertEquals($expectedVisibility, $actualObject->getVisibility()); @@ -260,7 +260,7 @@ protected function _assertVisibility( */ public function testAddMethodFromGenerator() { - $invalidMethod = new \Zend\Code\Generator\MethodGenerator(); + $invalidMethod = new \Laminas\Code\Generator\MethodGenerator(); $this->_model->addMethodFromGenerator($invalidMethod); } @@ -271,7 +271,7 @@ public function testAddProperties() $this->assertSameSize($this->_propertyData, $actualProperties); - /** @var $property \Zend\Code\Generator\PropertyGenerator */ + /** @var $property \Laminas\Code\Generator\PropertyGenerator */ foreach ($actualProperties as $propertyName => $property) { $this->assertArrayHasKey($propertyName, $this->_propertyData); $expectedPropertyData = $this->_propertyData[$propertyName]; @@ -287,7 +287,7 @@ public function testAddProperties() // assert default value if (isset($expectedPropertyData['defaultValue'])) { - /** @var $actualDefaultValue \Zend\Code\Generator\ValueGenerator */ + /** @var $actualDefaultValue \Laminas\Code\Generator\ValueGenerator */ $actualDefaultValue = $property->getDefaultValue(); $this->assertEquals($expectedPropertyData['defaultValue'], $actualDefaultValue->getValue()); } @@ -308,7 +308,7 @@ public function testAddProperties() */ public function testAddPropertyFromGenerator() { - $invalidProperty = new \Zend\Code\Generator\PropertyGenerator(); + $invalidProperty = new \Laminas\Code\Generator\PropertyGenerator(); $this->_model->addPropertyFromGenerator($invalidProperty); } diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/TestAsset/ParentClass.php b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/TestAsset/ParentClass.php index 4565620a7557e..c5a7cd84686c9 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/TestAsset/ParentClass.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/TestAsset/ParentClass.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\Code\Test\Unit\Generator\TestAsset; -use Zend\Code\Generator\DocBlockGenerator; +use Laminas\Code\Generator\DocBlockGenerator; /** * phpcs:ignoreFile @@ -15,7 +15,7 @@ class ParentClass /** * Public parent method * - * @param \Zend\Code\Generator\DocBlockGenerator $docBlockGenerator + * @param \Laminas\Code\Generator\DocBlockGenerator $docBlockGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -35,7 +35,7 @@ public function publicParentMethod( /** * Protected parent method * - * @param \Zend\Code\Generator\DocBlockGenerator $docBlockGenerator + * @param \Laminas\Code\Generator\DocBlockGenerator $docBlockGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -55,7 +55,7 @@ protected function _protectedParentMethod( /** * Private parent method * - * @param \Zend\Code\Generator\DocBlockGenerator $docBlockGenerator + * @param \Laminas\Code\Generator\DocBlockGenerator $docBlockGenerator * @param string $param1 * @param string $param2 * @param string $param3 diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/TestAsset/SourceClass.php b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/TestAsset/SourceClass.php index 5ba3031a2ae4d..08f781c01756a 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/TestAsset/SourceClass.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/TestAsset/SourceClass.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\Code\Test\Unit\Generator\TestAsset; -use Zend\Code\Generator\ClassGenerator; +use Laminas\Code\Generator\ClassGenerator; /** * phpcs:ignoreFile @@ -15,7 +15,7 @@ class SourceClass extends ParentClass /** * Public child constructor * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -35,7 +35,7 @@ public function __construct( /** * Public child method * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -58,7 +58,7 @@ public function publicChildMethod( /** * Public child method with reference * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param array $array * * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -70,7 +70,7 @@ public function publicMethodWithReference(ClassGenerator &$classGenerator, array /** * Protected child method * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param string $param1 * @param string $param2 * @param string $param3 @@ -88,7 +88,7 @@ protected function _protectedChildMethod( /** * Private child method * - * @param \Zend\Code\Generator\ClassGenerator $classGenerator + * @param \Laminas\Code\Generator\ClassGenerator $classGenerator * @param string $param1 * @param string $param2 * @param string $param3 diff --git a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php index b8978c9ef7181..9ea37b0d0f6bd 100644 --- a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php +++ b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php @@ -9,7 +9,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem\Directory\WriteFactory; use Magento\Framework\Filesystem\DriverPool; -use Zend\ServiceManager\ServiceManager; +use Laminas\ServiceManager\ServiceManager; use Magento\Setup\Mvc\Bootstrap\InitParamListener; /** diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index 058bf18b16d3c..d42715fee9923 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -1957,7 +1957,7 @@ public function insertOnDuplicate($table, array $data, array $fields = []) $field = $this->quoteIdentifier($k); if ($v instanceof \Zend_Db_Expr) { $value = $v->__toString(); - } elseif ($v instanceof \Zend\Db\Sql\Expression) { + } elseif ($v instanceof \Laminas\Db\Sql\Expression) { $value = $v->getExpression(); } elseif (is_string($v)) { $value = sprintf('VALUES(%s)', $this->quoteIdentifier($v)); diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php index eb642278694c0..ab1b1e9cc65f5 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php @@ -26,7 +26,7 @@ class FormKeyTest extends \PHPUnit\Framework\TestCase protected $sessionMock; /** - * @var \Zend\Escaper\Escaper|\PHPUnit_Framework_MockObject_MockObject + * @var \Laminas\Escaper\Escaper|\PHPUnit_Framework_MockObject_MockObject */ protected $escaperMock; diff --git a/lib/internal/Magento/Framework/Encryption/Helper/Security.php b/lib/internal/Magento/Framework/Encryption/Helper/Security.php index 63884b5c7fb3e..0320468b35f02 100644 --- a/lib/internal/Magento/Framework/Encryption/Helper/Security.php +++ b/lib/internal/Magento/Framework/Encryption/Helper/Security.php @@ -6,10 +6,10 @@ namespace Magento\Framework\Encryption\Helper; -use Zend\Crypt\Utils; +use Laminas\Crypt\Utils; /** - * Class implements compareString from Zend\Crypt + * Class implements compareString from Laminas\Crypt * * @api */ diff --git a/lib/internal/Magento/Framework/File/Test/Unit/Transfer/Adapter/HttpTest.php b/lib/internal/Magento/Framework/File/Test/Unit/Transfer/Adapter/HttpTest.php index 023c4cc4ddba6..65fde6f27fd8a 100644 --- a/lib/internal/Magento/Framework/File/Test/Unit/Transfer/Adapter/HttpTest.php +++ b/lib/internal/Magento/Framework/File/Test/Unit/Transfer/Adapter/HttpTest.php @@ -90,7 +90,7 @@ public function testSendWithOptions(): void $file = __DIR__ . '/../../_files/javascript.js'; $contentType = 'content/type'; - $headers = $this->getMockBuilder(\Zend\Http\Headers::class)->getMock(); + $headers = $this->getMockBuilder(\Laminas\Http\Headers::class)->getMock(); $this->response->expects($this->atLeastOnce()) ->method('setHeader') ->withConsecutive(['Content-length', filesize($file)], ['Content-Type', $contentType]); diff --git a/lib/internal/Magento/Framework/File/Transfer/Adapter/Http.php b/lib/internal/Magento/Framework/File/Transfer/Adapter/Http.php index cd42c8d04b477..07fc73eacf282 100644 --- a/lib/internal/Magento/Framework/File/Transfer/Adapter/Http.php +++ b/lib/internal/Magento/Framework/File/Transfer/Adapter/Http.php @@ -10,7 +10,7 @@ use Magento\Framework\File\Mime; use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\App\ObjectManager; -use Zend\Http\Headers; +use Laminas\Http\Headers; /** * File adapter to send the file to the client. diff --git a/lib/internal/Magento/Framework/Filesystem/Glob.php b/lib/internal/Magento/Framework/Filesystem/Glob.php index 415bb5494c2e8..a73fa1a371da6 100644 --- a/lib/internal/Magento/Framework/Filesystem/Glob.php +++ b/lib/internal/Magento/Framework/Filesystem/Glob.php @@ -5,13 +5,13 @@ */ namespace Magento\Framework\Filesystem; -use Zend\Stdlib\Glob as ZendGlob; -use Zend\Stdlib\Exception\RuntimeException as ZendRuntimeException; +use Laminas\Stdlib\Glob as LaminasGlob; +use Laminas\Stdlib\Exception\RuntimeException as ZendRuntimeException; /** - * Wrapper for Zend\Stdlib\Glob + * Wrapper for Laminas\Stdlib\Glob */ -class Glob extends ZendGlob +class Glob extends LaminasGlob { /** * Find pathnames matching a pattern. @@ -24,7 +24,7 @@ class Glob extends ZendGlob public static function glob($pattern, $flags = 0, $forceFallback = false) { try { - $result = ZendGlob::glob($pattern, $flags, $forceFallback); + $result = LaminasGlob::glob($pattern, $flags, $forceFallback); } catch (ZendRuntimeException $e) { $result = []; } diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Request.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Request.php index 3ecf360f36894..13d6e7b72d89f 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Request.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Request.php @@ -7,11 +7,11 @@ use Magento\Framework\Stdlib\Cookie\CookieReaderInterface; use Magento\Framework\Stdlib\StringUtils; -use Zend\Http\Header\HeaderInterface; -use Zend\Stdlib\Parameters; -use Zend\Stdlib\ParametersInterface; -use Zend\Uri\UriFactory; -use Zend\Uri\UriInterface; +use Laminas\Http\Header\HeaderInterface; +use Laminas\Stdlib\Parameters; +use Laminas\Stdlib\ParametersInterface; +use Laminas\Uri\UriFactory; +use Laminas\Uri\UriInterface; /** * HTTP Request for current PHP environment. @@ -19,7 +19,7 @@ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ -class Request extends \Zend\Http\PhpEnvironment\Request +class Request extends \Laminas\Http\PhpEnvironment\Request { /**#@+ * Protocols diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php index dc3e63fcc7df8..27ca4ae58948b 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php @@ -10,7 +10,7 @@ /** * Base HTTP response object */ -class Response extends \Zend\Http\PhpEnvironment\Response implements \Magento\Framework\App\Response\HttpInterface +class Response extends \Laminas\Http\PhpEnvironment\Response implements \Magento\Framework\App\Response\HttpInterface { /** * Flag; is this response a redirect? @@ -198,7 +198,7 @@ public function sendHeaders() $status = $this->renderStatusLine(); header($status); - /** @var \Zend\Http\Header\HeaderInterface $header */ + /** @var \Laminas\Http\Header\HeaderInterface $header */ foreach ($this->getHeaders() as $header) { header($header->toString(), false); } diff --git a/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/RequestTest.php b/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/RequestTest.php index 6bd8a977f2a2c..addd4ba93576e 100644 --- a/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/RequestTest.php +++ b/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/RequestTest.php @@ -7,7 +7,7 @@ use \Magento\Framework\HTTP\PhpEnvironment\Request; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; class RequestTest extends \PHPUnit\Framework\TestCase { diff --git a/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/ResponseTest.php b/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/ResponseTest.php index ba6746e75ce34..7ba897ee21cbe 100644 --- a/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/ResponseTest.php +++ b/lib/internal/Magento/Framework/HTTP/Test/Unit/PhpEnvironment/ResponseTest.php @@ -12,7 +12,7 @@ class ResponseTest extends \PHPUnit\Framework\TestCase /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\HTTP\PhpEnvironment\Response */ protected $response; - /** @var \PHPUnit_Framework_MockObject_MockObject|\Zend\Http\Headers */ + /** @var \PHPUnit_Framework_MockObject_MockObject|\Laminas\Http\Headers */ protected $headers; protected function setUp() @@ -22,7 +22,7 @@ protected function setUp() ['getHeaders', 'send', 'clearHeader'] ); $this->headers = $this->createPartialMock( - \Zend\Http\Headers::class, + \Laminas\Http\Headers::class, ['has', 'get', 'current', 'removeHeader'] ); } @@ -117,7 +117,7 @@ public function testClearHeaderIfHeaderExistsAndWasFound() $this->headers->addHeaderLine('Header-name: header-value'); - $header = \Zend\Http\Header\GenericHeader::fromString('Header-name: header-value'); + $header = \Laminas\Http\Header\GenericHeader::fromString('Header-name: header-value'); $this->headers ->expects($this->once()) @@ -152,7 +152,7 @@ public function testClearHeaderAndHeaderNotExists() $this->headers->addHeaderLine('Header-name: header-value'); - $header = \Zend\Http\Header\GenericHeader::fromString('Header-name: header-value'); + $header = \Laminas\Http\Header\GenericHeader::fromString('Header-name: header-value'); $this->headers ->expects($this->once()) diff --git a/lib/internal/Magento/Framework/Mail/EmailMessage.php b/lib/internal/Magento/Framework/Mail/EmailMessage.php index 02c75977cd093..0b8a1da8a4b85 100644 --- a/lib/internal/Magento/Framework/Mail/EmailMessage.php +++ b/lib/internal/Magento/Framework/Mail/EmailMessage.php @@ -8,9 +8,9 @@ namespace Magento\Framework\Mail; use Magento\Framework\Mail\Exception\InvalidArgumentException; -use Zend\Mail\Address as ZendAddress; -use Zend\Mail\AddressList; -use Zend\Mime\Message as ZendMimeMessage; +use Laminas\Mail\Address as ZendAddress; +use Laminas\Mail\AddressList; +use Laminas\Mime\Message as ZendMimeMessage; /** * Email message diff --git a/lib/internal/Magento/Framework/Mail/Message.php b/lib/internal/Magento/Framework/Mail/Message.php index 0e4d79aac9331..1d572e29fecc1 100644 --- a/lib/internal/Magento/Framework/Mail/Message.php +++ b/lib/internal/Magento/Framework/Mail/Message.php @@ -5,8 +5,8 @@ */ namespace Magento\Framework\Mail; -use Zend\Mime\Mime; -use Zend\Mime\Part; +use Laminas\Mime\Mime; +use Laminas\Mime\Part; /** * Class Message for email transportation @@ -17,7 +17,7 @@ class Message implements MailMessageInterface { /** - * @var \Zend\Mail\Message + * @var \Laminas\Mail\Message */ protected $zendMessage; @@ -35,7 +35,7 @@ class Message implements MailMessageInterface */ public function __construct($charset = 'utf-8') { - $this->zendMessage = new \Zend\Mail\Message(); + $this->zendMessage = new \Laminas\Mail\Message(); $this->zendMessage->setEncoding($charset); } @@ -164,7 +164,7 @@ public function getRawMessage() * * @param string $body * @param string $messageType - * @return \Zend\Mime\Message + * @return \Laminas\Mime\Message */ private function createMimeFromString($body, $messageType) { @@ -172,7 +172,7 @@ private function createMimeFromString($body, $messageType) $part->setCharset($this->zendMessage->getEncoding()); $part->setEncoding(Mime::ENCODING_QUOTEDPRINTABLE); $part->setType($messageType); - $mimeMessage = new \Zend\Mime\Message(); + $mimeMessage = new \Laminas\Mime\Message(); $mimeMessage->addPart($part); return $mimeMessage; } diff --git a/lib/internal/Magento/Framework/Mail/MimeInterface.php b/lib/internal/Magento/Framework/Mail/MimeInterface.php index 026dd188d1685..a7910e195a160 100644 --- a/lib/internal/Magento/Framework/Mail/MimeInterface.php +++ b/lib/internal/Magento/Framework/Mail/MimeInterface.php @@ -9,7 +9,7 @@ /** * Interface MimeInterface used providing constants * - * @see \Zend\Mime\Mime + * @see \Laminas\Mime\Mime */ interface MimeInterface { diff --git a/lib/internal/Magento/Framework/Mail/MimeMessage.php b/lib/internal/Magento/Framework/Mail/MimeMessage.php index 4d783dafd1d7a..6c293fd957e35 100644 --- a/lib/internal/Magento/Framework/Mail/MimeMessage.php +++ b/lib/internal/Magento/Framework/Mail/MimeMessage.php @@ -7,7 +7,7 @@ namespace Magento\Framework\Mail; -use Zend\Mime\Message as ZendMimeMessage; +use Laminas\Mime\Message as ZendMimeMessage; /** * Class MimeMessage diff --git a/lib/internal/Magento/Framework/Mail/MimePart.php b/lib/internal/Magento/Framework/Mail/MimePart.php index a43ed4b36e072..7cb0e9fbf0097 100644 --- a/lib/internal/Magento/Framework/Mail/MimePart.php +++ b/lib/internal/Magento/Framework/Mail/MimePart.php @@ -8,7 +8,7 @@ namespace Magento\Framework\Mail; use Magento\Framework\Mail\Exception\InvalidArgumentException; -use Zend\Mime\Part as ZendMimePart; +use Laminas\Mime\Part as ZendMimePart; /** * @inheritDoc diff --git a/lib/internal/Magento/Framework/Mail/Transport.php b/lib/internal/Magento/Framework/Mail/Transport.php index 8a7ace17cc9a0..44e8242c965a1 100644 --- a/lib/internal/Magento/Framework/Mail/Transport.php +++ b/lib/internal/Magento/Framework/Mail/Transport.php @@ -7,8 +7,8 @@ use Magento\Framework\Exception\MailException; use Magento\Framework\Phrase; -use Zend\Mail\Message as ZendMessage; -use Zend\Mail\Transport\Sendmail; +use Laminas\Mail\Message as ZendMessage; +use Laminas\Mail\Transport\Sendmail; class Transport implements \Magento\Framework\Mail\TransportInterface { diff --git a/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php b/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php index 0cd62963c547c..74053d58f0503 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php @@ -11,7 +11,7 @@ use Magento\Framework\Communication\ConfigInterface as CommunicationConfig; use Magento\Framework\MessageQueue\Code\Generator\Config\RemoteServiceReader\Communication as RemoteServiceReader; use Magento\Framework\Reflection\MethodsMap as ServiceMethodsMap; -use Zend\Code\Reflection\MethodReflection; +use Laminas\Code\Reflection\MethodReflection; /** * Code generator for remote services. @@ -131,7 +131,7 @@ protected function _getClassMethods() $sourceMethodParameters = $methodReflection->getParameters(); $methodParameters = []; $topicParameters = []; - /** @var \Zend\Code\Reflection\ParameterReflection $methodParameter */ + /** @var \Laminas\Code\Reflection\ParameterReflection $methodParameter */ foreach ($sourceMethodParameters as $methodParameter) { $parameterName = $methodParameter->getName(); $parameter = [ diff --git a/lib/internal/Magento/Framework/Oauth/Helper/Request.php b/lib/internal/Magento/Framework/Oauth/Helper/Request.php index 548c4a5990efe..d5f2fa45e6843 100644 --- a/lib/internal/Magento/Framework/Oauth/Helper/Request.php +++ b/lib/internal/Magento/Framework/Oauth/Helper/Request.php @@ -6,7 +6,7 @@ namespace Magento\Framework\Oauth\Helper; use Magento\Framework\App\RequestInterface; -use Zend\Uri\UriFactory; +use Laminas\Uri\UriFactory; class Request { diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php index be484f074342d..aeca85a6e9ae0 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php @@ -11,8 +11,8 @@ use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\InputException; -use Zend\Code\Reflection\MethodReflection; -use Zend\Code\Reflection\ParameterReflection; +use Laminas\Code\Reflection\MethodReflection; +use Laminas\Code\Reflection\ParameterReflection; /** * Class Repository diff --git a/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php b/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php index aa978e7f337cc..fb22cf6872b9e 100644 --- a/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php @@ -11,7 +11,7 @@ use Magento\Framework\AuthorizationInterface; use Magento\Framework\Phrase; use Magento\Framework\Api\ExtensionAttributesInterface; -use Zend\Code\Reflection\MethodReflection; +use Laminas\Code\Reflection\MethodReflection; /** * Processes extension attributes and produces an array for the data. diff --git a/lib/internal/Magento/Framework/Reflection/MethodsMap.php b/lib/internal/Magento/Framework/Reflection/MethodsMap.php index 6b0ddfbfc2127..57347c62e4244 100644 --- a/lib/internal/Magento/Framework/Reflection/MethodsMap.php +++ b/lib/internal/Magento/Framework/Reflection/MethodsMap.php @@ -7,9 +7,9 @@ namespace Magento\Framework\Reflection; use Magento\Framework\Serialize\SerializerInterface; -use Zend\Code\Reflection\ClassReflection; -use Zend\Code\Reflection\MethodReflection; -use Zend\Code\Reflection\ParameterReflection; +use Laminas\Code\Reflection\ClassReflection; +use Laminas\Code\Reflection\MethodReflection; +use Laminas\Code\Reflection\ParameterReflection; use Magento\Framework\App\Cache\Type\Reflection as ReflectionCache; /** diff --git a/lib/internal/Magento/Framework/Reflection/NameFinder.php b/lib/internal/Magento/Framework/Reflection/NameFinder.php index 5cd4ab744bb1b..81eb4782c4c98 100644 --- a/lib/internal/Magento/Framework/Reflection/NameFinder.php +++ b/lib/internal/Magento/Framework/Reflection/NameFinder.php @@ -6,7 +6,7 @@ namespace Magento\Framework\Reflection; -use Zend\Code\Reflection\ClassReflection; +use Laminas\Code\Reflection\ClassReflection; class NameFinder { diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php index e4c0294c0cfb5..a0ae3b4841d56 100644 --- a/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/NameFinderTest.php @@ -6,7 +6,7 @@ // @codingStandardsIgnoreStart namespace Magento\Framework\Reflection\Test\Unit; -use Zend\Code\Reflection\ClassReflection; +use Laminas\Code\Reflection\ClassReflection; /** * NameFinder Unit Test @@ -64,7 +64,7 @@ public function testGetSetterMethodNameWrongCamelCasedAttribute() */ public function testFindAccessorMethodName() { - $reflectionClass = $this->createMock(\Zend\Code\Reflection\ClassReflection::class); + $reflectionClass = $this->createMock(\Laminas\Code\Reflection\ClassReflection::class); $reflectionClass->expects($this->atLeastOnce())->method('hasMethod')->willReturn(false); $reflectionClass->expects($this->atLeastOnce())->method('getName')->willReturn('className'); diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php index 1a8702c0e1c5b..0a1473e7d3d8e 100644 --- a/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php @@ -15,7 +15,7 @@ use Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleTwo\SampleFour; use Magento\Framework\Reflection\Test\Unit\Fixture\UseSample; use Magento\Framework\Reflection\TypeProcessor; -use Zend\Code\Reflection\ClassReflection; +use Laminas\Code\Reflection\ClassReflection; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) diff --git a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php index 9571fa53547ab..65b485f79aff5 100644 --- a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php @@ -8,12 +8,12 @@ use Magento\Framework\Exception\SerializationException; use Magento\Framework\Phrase; -use Zend\Code\Reflection\ClassReflection; -use Zend\Code\Reflection\DocBlock\Tag\ParamTag; -use Zend\Code\Reflection\DocBlock\Tag\ReturnTag; -use Zend\Code\Reflection\DocBlockReflection; -use Zend\Code\Reflection\MethodReflection; -use Zend\Code\Reflection\ParameterReflection; +use Laminas\Code\Reflection\ClassReflection; +use Laminas\Code\Reflection\DocBlock\Tag\ParamTag; +use Laminas\Code\Reflection\DocBlock\Tag\ReturnTag; +use Laminas\Code\Reflection\DocBlockReflection; +use Laminas\Code\Reflection\MethodReflection; +use Laminas\Code\Reflection\ParameterReflection; /** * Type processor of config reader properties @@ -310,7 +310,7 @@ public function getExceptions($methodReflection) if ($methodDocBlock->hasTag('throws')) { $throwsTypes = $methodDocBlock->getTags('throws'); if (is_array($throwsTypes)) { - /** @var $throwsType \Zend\Code\Reflection\DocBlock\Tag\ThrowsTag */ + /** @var $throwsType \Laminas\Code\Reflection\DocBlock\Tag\ThrowsTag */ foreach ($throwsTypes as $throwsType) { $exceptions = array_merge($exceptions, $throwsType->getTypes()); } diff --git a/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php b/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php index 7d286eca4ed3f..f4092622e0b01 100644 --- a/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php +++ b/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php @@ -19,7 +19,7 @@ public function isValid($value) return false; } - $validator = new \Zend\Validator\Hostname(\Zend\Validator\Hostname::ALLOW_ALL); + $validator = new \Laminas\Validator\Hostname(\Laminas\Validator\Hostname::ALLOW_ALL); if (!empty($value) && !$validator->isValid($value)) { $this->_addMessages($validator->getMessages()); diff --git a/lib/internal/Magento/Framework/Stdlib/Parameters.php b/lib/internal/Magento/Framework/Stdlib/Parameters.php index dcefaf85d390e..e5743355527c8 100644 --- a/lib/internal/Magento/Framework/Stdlib/Parameters.php +++ b/lib/internal/Magento/Framework/Stdlib/Parameters.php @@ -7,7 +7,7 @@ namespace Magento\Framework\Stdlib; -use Zend\Stdlib\Parameters as ZendParameters; +use Laminas\Stdlib\Parameters as ZendParameters; /** * Class Parameters @@ -100,7 +100,7 @@ public function get($name, $default = null) * * @param string $name * @param mixed $value - * @return \Zend\Stdlib\Parameters + * @return \Laminas\Stdlib\Parameters */ public function set($name, $value) { diff --git a/lib/internal/Magento/Framework/Url/Test/Unit/ValidatorTest.php b/lib/internal/Magento/Framework/Url/Test/Unit/ValidatorTest.php index 843ee8b66e710..171430df906fe 100644 --- a/lib/internal/Magento/Framework/Url/Test/Unit/ValidatorTest.php +++ b/lib/internal/Magento/Framework/Url/Test/Unit/ValidatorTest.php @@ -12,7 +12,7 @@ class ValidatorTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\Url\Validator */ protected $object; - /** @var \Zend\Validator\Uri */ + /** @var \Laminas\Validator\Uri */ protected $zendValidator; /** @var string[] */ @@ -22,7 +22,7 @@ protected function setUp() { $objectManager = new ObjectManager($this); - $this->zendValidator = $this->createMock(\Zend\Validator\Uri::class); + $this->zendValidator = $this->createMock(\Laminas\Validator\Uri::class); $this->object = $objectManager->getObject( \Magento\Framework\Url\Validator::class, ['validator' => $this->zendValidator] diff --git a/lib/internal/Magento/Framework/Url/Validator.php b/lib/internal/Magento/Framework/Url/Validator.php index 489e8c502179d..c85853bf48fd2 100644 --- a/lib/internal/Magento/Framework/Url/Validator.php +++ b/lib/internal/Magento/Framework/Url/Validator.php @@ -20,14 +20,14 @@ class Validator extends \Zend_Validate_Abstract /**#@-*/ /** - * @var \Zend\Validator\Uri + * @var \Laminas\Validator\Uri */ private $validator; /** * Object constructor */ - public function __construct(\Zend\Validator\Uri $validator) + public function __construct(\Laminas\Validator\Uri $validator) { // set translated message template $this->setMessage((string)new \Magento\Framework\Phrase("Invalid URL '%value%'."), self::INVALID_URL); diff --git a/lib/internal/Magento/Framework/Validator/AllowedProtocols.php b/lib/internal/Magento/Framework/Validator/AllowedProtocols.php index a25346d673408..c1473097d7262 100644 --- a/lib/internal/Magento/Framework/Validator/AllowedProtocols.php +++ b/lib/internal/Magento/Framework/Validator/AllowedProtocols.php @@ -7,7 +7,7 @@ */ namespace Magento\Framework\Validator; -use \Zend\Uri\Uri; +use \Laminas\Uri\Uri; /** * Check is URI starts from allowed protocol diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index f93d7efda5c8a..902e67bf015b7 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -21,7 +21,7 @@ use Magento\Framework\Reflection\TypeProcessor; use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; -use Zend\Code\Reflection\ClassReflection; +use Laminas\Code\Reflection\ClassReflection; /** * Deserialize arguments from API requests. diff --git a/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php index 224421d6561c8..25eacb00c23ae 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php @@ -11,7 +11,7 @@ use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Reflection\MethodsMap; use Magento\Framework\Reflection\TypeProcessor; -use Zend\Code\Reflection\ClassReflection; +use Laminas\Code\Reflection\ClassReflection; /** * Data object converter diff --git a/lib/internal/Magento/Framework/ZendEscaper.php b/lib/internal/Magento/Framework/ZendEscaper.php index 37182ab97d49d..6edd84d7c87f4 100644 --- a/lib/internal/Magento/Framework/ZendEscaper.php +++ b/lib/internal/Magento/Framework/ZendEscaper.php @@ -8,6 +8,6 @@ /** * Magento wrapper for Zend's Escaper class */ -class ZendEscaper extends \Zend\Escaper\Escaper +class ZendEscaper extends \Laminas\Escaper\Escaper { } diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index dfbfb5a25debe..3689cdccd3e2a 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -30,15 +30,15 @@ "symfony/console": "~4.1.0", "symfony/process": "~4.1.0", "tedivm/jshrink": "~1.3.0", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-stdlib": "^3.2.1", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-mail": "^2.9.0", - "zendframework/zend-mime": "^2.5.0", + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0", + "laminas/laminas-mail": "^2.9.0", + "laminas/laminas-mime": "^2.5.0", "guzzlehttp/guzzle": "^6.3.3" }, "archive": { diff --git a/setup/config/application.config.php b/setup/config/application.config.php index e7efd12023df4..a293e20219b27 100644 --- a/setup/config/application.config.php +++ b/setup/config/application.config.php @@ -5,8 +5,8 @@ */ use Magento\Setup\Mvc\Bootstrap\InitParamListener; -use Zend\Mvc\Service\DiAbstractServiceFactoryFactory; -use Zend\ServiceManager\Di\DiAbstractServiceFactory; +use Laminas\Mvc\Service\DiAbstractServiceFactoryFactory; +use Laminas\ServiceManager\Di\DiAbstractServiceFactory; return [ 'modules' => [ diff --git a/setup/config/di.config.php b/setup/config/di.config.php index fbdebe7a96e18..b9f2ebe2fa4e0 100644 --- a/setup/config/di.config.php +++ b/setup/config/di.config.php @@ -8,8 +8,8 @@ 'di' => [ 'instance' => [ 'preference' => [ - \Zend\EventManager\EventManagerInterface::class => 'EventManager', - \Zend\ServiceManager\ServiceLocatorInterface::class => \Zend\ServiceManager\ServiceManager::class, + \Laminas\EventManager\EventManagerInterface::class => 'EventManager', + \Laminas\ServiceManager\ServiceLocatorInterface::class => \Laminas\ServiceManager\ServiceManager::class, \Magento\Framework\DB\LoggerInterface::class => \Magento\Framework\DB\Logger\Quiet::class, \Magento\Framework\Locale\ConfigInterface::class => \Magento\Framework\Locale\Config::class, \Magento\Framework\Filesystem\DriverInterface::class => diff --git a/setup/config/module.config.php b/setup/config/module.config.php index 7816c8e3599f3..4e6b89ee70d91 100644 --- a/setup/config/module.config.php +++ b/setup/config/module.config.php @@ -37,7 +37,7 @@ ], 'controllers' => [ 'abstract_factories' => [ - \Zend\Mvc\Controller\LazyControllerAbstractFactory::class, + \Laminas\Mvc\Controller\LazyControllerAbstractFactory::class, ], ], ]; diff --git a/setup/src/Magento/Setup/Application.php b/setup/src/Magento/Setup/Application.php index 4366727e080c8..2c34cf00aa66c 100644 --- a/setup/src/Magento/Setup/Application.php +++ b/setup/src/Magento/Setup/Application.php @@ -5,19 +5,19 @@ */ namespace Magento\Setup; -use Zend\Mvc\Application as ZendApplication; -use Zend\Mvc\Service\ServiceManagerConfig; -use Zend\ServiceManager\ServiceManager; +use Laminas\Mvc\Application as ZendApplication; +use Laminas\Mvc\Service\ServiceManagerConfig; +use Laminas\ServiceManager\ServiceManager; /** - * This class is wrapper on \Zend\Mvc\Application and allows to do more customization like services loading, which + * This class is wrapper on \Laminas\Mvc\Application and allows to do more customization like services loading, which * cannot be loaded via configuration. */ class Application { /** - * Creates \Zend\Mvc\Application and bootstrap it. - * This method is similar to \Zend\Mvc\Application::init but allows to load + * Creates \Laminas\Mvc\Application and bootstrap it. + * This method is similar to \Laminas\Mvc\Application::init but allows to load * Magento specific services. * * @param array $configuration @@ -52,8 +52,8 @@ public function bootstrap(array $configuration) } /** - * Uses \Zend\ServiceManager\ServiceManager::get method to load different kind of services. - * Some services cannot be loaded via configuration like \Zend\ServiceManager\Di\DiAbstractServiceFactory and + * Uses \Laminas\ServiceManager\ServiceManager::get method to load different kind of services. + * Some services cannot be loaded via configuration like \Laminas\ServiceManager\Di\DiAbstractServiceFactory and * should be initialized via corresponding factory. * * @param ServiceManager $serviceManager diff --git a/setup/src/Magento/Setup/Console/CommandList.php b/setup/src/Magento/Setup/Console/CommandList.php index f0dad6f4a7452..338330ef91599 100644 --- a/setup/src/Magento/Setup/Console/CommandList.php +++ b/setup/src/Magento/Setup/Console/CommandList.php @@ -7,7 +7,7 @@ namespace Magento\Setup\Console; use Magento\Setup\Console\Command\TablesWhitelistGenerateCommand; -use Zend\ServiceManager\ServiceManager; +use Laminas\ServiceManager\ServiceManager; /** * Class CommandList contains predefined list of commands for Setup. diff --git a/setup/src/Magento/Setup/Console/CompilerPreparation.php b/setup/src/Magento/Setup/Console/CompilerPreparation.php index c39c721b61716..c83aa48636393 100644 --- a/setup/src/Magento/Setup/Console/CompilerPreparation.php +++ b/setup/src/Magento/Setup/Console/CompilerPreparation.php @@ -15,7 +15,7 @@ use Magento\Setup\Console\Command\DiCompileCommand; use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Symfony\Component\Console\Input\ArgvInput; -use Zend\ServiceManager\ServiceManager; +use Laminas\ServiceManager\ServiceManager; /** * Class prepares folders for code generation diff --git a/setup/src/Magento/Setup/Controller/AddDatabase.php b/setup/src/Magento/Setup/Controller/AddDatabase.php index 8d36eece58e22..7002f8e7f64a4 100644 --- a/setup/src/Magento/Setup/Controller/AddDatabase.php +++ b/setup/src/Magento/Setup/Controller/AddDatabase.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class AddDatabase extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/BackupActionItems.php b/setup/src/Magento/Setup/Controller/BackupActionItems.php index a00492e001f8c..a79d9a566dab0 100644 --- a/setup/src/Magento/Setup/Controller/BackupActionItems.php +++ b/setup/src/Magento/Setup/Controller/BackupActionItems.php @@ -9,9 +9,9 @@ use Magento\Framework\Backup\Factory; use Magento\Framework\Backup\Filesystem; use Magento\Framework\Setup\BackupRollback; -use Zend\Json\Json; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; +use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; class BackupActionItems extends AbstractActionController { @@ -63,20 +63,20 @@ public function __construct( /** * No index action, return 404 error page * - * @return \Zend\View\Model\ViewModel + * @return \Laminas\View\Model\ViewModel */ public function indexAction() { - $view = new \Zend\View\Model\ViewModel; + $view = new \Laminas\View\Model\ViewModel; $view->setTemplate('/error/404.phtml'); - $this->getResponse()->setStatusCode(\Zend\Http\Response::STATUS_CODE_404); + $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_404); return $view; } /** * Checks disk space availability * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function checkAction() { @@ -114,7 +114,7 @@ public function checkAction() /** * Takes backup for code, media or DB * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function createAction() { diff --git a/setup/src/Magento/Setup/Controller/CompleteBackup.php b/setup/src/Magento/Setup/Controller/CompleteBackup.php index e0e45a208cdf5..158e0b724fd8f 100644 --- a/setup/src/Magento/Setup/Controller/CompleteBackup.php +++ b/setup/src/Magento/Setup/Controller/CompleteBackup.php @@ -6,9 +6,9 @@ namespace Magento\Setup\Controller; use Magento\Framework\App\MaintenanceMode; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; class CompleteBackup extends AbstractActionController { @@ -19,7 +19,7 @@ public function indexAction() { $view = new ViewModel; $view->setTemplate('/error/404.phtml'); - $this->getResponse()->setStatusCode(\Zend\Http\Response::STATUS_CODE_404); + $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_404); return $view; } diff --git a/setup/src/Magento/Setup/Controller/CreateAdminAccount.php b/setup/src/Magento/Setup/Controller/CreateAdminAccount.php index 9c20312b23dca..d06407796ff9b 100644 --- a/setup/src/Magento/Setup/Controller/CreateAdminAccount.php +++ b/setup/src/Magento/Setup/Controller/CreateAdminAccount.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class CreateAdminAccount extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/CreateBackup.php b/setup/src/Magento/Setup/Controller/CreateBackup.php index 9987cfd13bcf4..97c6f0deef188 100644 --- a/setup/src/Magento/Setup/Controller/CreateBackup.php +++ b/setup/src/Magento/Setup/Controller/CreateBackup.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class CreateBackup extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/CustomizeYourStore.php b/setup/src/Magento/Setup/Controller/CustomizeYourStore.php index 83c96ed9d43ef..cc987e8339008 100644 --- a/setup/src/Magento/Setup/Controller/CustomizeYourStore.php +++ b/setup/src/Magento/Setup/Controller/CustomizeYourStore.php @@ -9,9 +9,9 @@ use Magento\Framework\Module\FullModuleList; use Magento\Framework\Setup\Lists; use Magento\Setup\Model\ObjectManagerProvider; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; -use Zend\View\Model\JsonModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; +use Laminas\View\Model\JsonModel; class CustomizeYourStore extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/DataOption.php b/setup/src/Magento/Setup/Controller/DataOption.php index 88ebf291016d9..549358f6e3cf2 100644 --- a/setup/src/Magento/Setup/Controller/DataOption.php +++ b/setup/src/Magento/Setup/Controller/DataOption.php @@ -7,10 +7,10 @@ namespace Magento\Setup\Controller; use Magento\Setup\Model\UninstallCollector; -use Zend\Json\Json; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; /** * Controller of data option selection @@ -35,7 +35,7 @@ public function __construct(UninstallCollector $uninstallCollector) /** * Shows data option page * - * @return ViewModel|\Zend\Http\Response + * @return ViewModel|\Laminas\Http\Response */ public function indexAction() { diff --git a/setup/src/Magento/Setup/Controller/DatabaseCheck.php b/setup/src/Magento/Setup/Controller/DatabaseCheck.php index 4b88a8732d2c7..cf6c6ae1b4409 100644 --- a/setup/src/Magento/Setup/Controller/DatabaseCheck.php +++ b/setup/src/Magento/Setup/Controller/DatabaseCheck.php @@ -7,9 +7,9 @@ use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Setup\Validator\DbValidator; -use Zend\Json\Json; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; +use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; /** * Class DatabaseCheck diff --git a/setup/src/Magento/Setup/Controller/DependencyCheck.php b/setup/src/Magento/Setup/Controller/DependencyCheck.php index e0e1050fa628d..44f205ace3925 100644 --- a/setup/src/Magento/Setup/Controller/DependencyCheck.php +++ b/setup/src/Magento/Setup/Controller/DependencyCheck.php @@ -10,9 +10,9 @@ use Magento\Setup\Model\DependencyReadinessCheck; use Magento\Setup\Model\ModuleStatusFactory; use Magento\Setup\Model\UninstallDependencyCheck; -use Zend\Json\Json; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; +use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; /** * Class DependencyCheck diff --git a/setup/src/Magento/Setup/Controller/Environment.php b/setup/src/Magento/Setup/Controller/Environment.php index c6c7073e02951..2a330bd1453a8 100644 --- a/setup/src/Magento/Setup/Controller/Environment.php +++ b/setup/src/Magento/Setup/Controller/Environment.php @@ -8,7 +8,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; use Magento\Setup\Model\Cron\ReadinessCheck; -use Zend\Mvc\Controller\AbstractActionController; +use Laminas\Mvc\Controller\AbstractActionController; /** * Class Environment @@ -66,20 +66,20 @@ public function __construct( /** * No index action, return 404 error page * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function indexAction() { - $view = new \Zend\View\Model\JsonModel([]); + $view = new \Laminas\View\Model\JsonModel([]); $view->setTemplate('/error/404.phtml'); - $this->getResponse()->setStatusCode(\Zend\Http\Response::STATUS_CODE_404); + $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_404); return $view; } /** * Verifies php version * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function phpVersionAction() { @@ -91,13 +91,13 @@ public function phpVersionAction() } elseif ($type == ReadinessCheckUpdater::UPDATER) { $data = $this->getPhpChecksInfo(ReadinessCheck::KEY_PHP_VERSION_VERIFIED); } - return new \Zend\View\Model\JsonModel($data); + return new \Laminas\View\Model\JsonModel($data); } /** * Checks PHP settings * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function phpSettingsAction() { @@ -109,13 +109,13 @@ public function phpSettingsAction() } elseif ($type == ReadinessCheckUpdater::UPDATER) { $data = $this->getPhpChecksInfo(ReadinessCheck::KEY_PHP_SETTINGS_VERIFIED); } - return new \Zend\View\Model\JsonModel($data); + return new \Laminas\View\Model\JsonModel($data); } /** * Verifies php verifications * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function phpExtensionsAction() { @@ -127,7 +127,7 @@ public function phpExtensionsAction() } elseif ($type == ReadinessCheckUpdater::UPDATER) { $data = $this->getPhpChecksInfo(ReadinessCheck::KEY_PHP_EXTENSIONS_VERIFIED); } - return new \Zend\View\Model\JsonModel($data); + return new \Laminas\View\Model\JsonModel($data); } /** @@ -155,7 +155,7 @@ private function getPhpChecksInfo($type) /** * Verifies file permissions * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function filePermissionsAction() { @@ -183,13 +183,13 @@ public function filePermissionsAction() ], ]; - return new \Zend\View\Model\JsonModel($data); + return new \Laminas\View\Model\JsonModel($data); } /** * Verifies updater application exists * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function updaterApplicationAction() { @@ -201,13 +201,13 @@ public function updaterApplicationAction() $data = [ 'responseType' => $responseType ]; - return new \Zend\View\Model\JsonModel($data); + return new \Laminas\View\Model\JsonModel($data); } /** * Verifies Setup and Updater Cron status * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel */ public function cronScriptAction() { @@ -232,6 +232,6 @@ public function cronScriptAction() $updaterCheck['notice']; } $data['responseType'] = $responseType; - return new \Zend\View\Model\JsonModel($data); + return new \Laminas\View\Model\JsonModel($data); } } diff --git a/setup/src/Magento/Setup/Controller/ExtensionGrid.php b/setup/src/Magento/Setup/Controller/ExtensionGrid.php index 48c63eafcf140..cbe9340ffd8f8 100644 --- a/setup/src/Magento/Setup/Controller/ExtensionGrid.php +++ b/setup/src/Magento/Setup/Controller/ExtensionGrid.php @@ -7,9 +7,9 @@ use Magento\Setup\Model\PackagesAuth; use Magento\Setup\Model\PackagesData; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; use Magento\Setup\Model\Grid; /** @@ -50,7 +50,7 @@ public function __construct( /** * Index page action * - * @return \Zend\View\Model\ViewModel + * @return \Laminas\View\Model\ViewModel */ public function indexAction() { diff --git a/setup/src/Magento/Setup/Controller/Home.php b/setup/src/Magento/Setup/Controller/Home.php index 4b0f4ef7917bd..a9b45af731b81 100644 --- a/setup/src/Magento/Setup/Controller/Home.php +++ b/setup/src/Magento/Setup/Controller/Home.php @@ -6,8 +6,8 @@ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; /** * Controller of homepage of setup @@ -15,7 +15,7 @@ class Home extends AbstractActionController { /** - * @return ViewModel|\Zend\Http\Response + * @return ViewModel|\Laminas\Http\Response */ public function indexAction() { diff --git a/setup/src/Magento/Setup/Controller/Index.php b/setup/src/Magento/Setup/Controller/Index.php index ea2fadd94f65e..347ef5738add3 100644 --- a/setup/src/Magento/Setup/Controller/Index.php +++ b/setup/src/Magento/Setup/Controller/Index.php @@ -6,8 +6,8 @@ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; /** * Main controller of the Setup Wizard @@ -15,7 +15,7 @@ class Index extends AbstractActionController { /** - * @return ViewModel|\Zend\Http\Response + * @return ViewModel|\Laminas\Http\Response */ public function indexAction() { diff --git a/setup/src/Magento/Setup/Controller/Install.php b/setup/src/Magento/Setup/Controller/Install.php index ceb37e7d7b6e0..a47c0e375500f 100644 --- a/setup/src/Magento/Setup/Controller/Install.php +++ b/setup/src/Magento/Setup/Controller/Install.php @@ -14,10 +14,10 @@ use Magento\Setup\Model\InstallerFactory; use Magento\Setup\Model\RequestDataConverter; use Magento\Setup\Model\WebLogger; -use Zend\Json\Json; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; /** * Install controller diff --git a/setup/src/Magento/Setup/Controller/InstallExtensionGrid.php b/setup/src/Magento/Setup/Controller/InstallExtensionGrid.php index 5589bb963aa3b..b6bed4f3db1f1 100644 --- a/setup/src/Magento/Setup/Controller/InstallExtensionGrid.php +++ b/setup/src/Magento/Setup/Controller/InstallExtensionGrid.php @@ -6,9 +6,9 @@ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; use Magento\Setup\Model\PackagesData; /** diff --git a/setup/src/Magento/Setup/Controller/LandingInstaller.php b/setup/src/Magento/Setup/Controller/LandingInstaller.php index 22eea503721be..8aee28df15137 100644 --- a/setup/src/Magento/Setup/Controller/LandingInstaller.php +++ b/setup/src/Magento/Setup/Controller/LandingInstaller.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; /** * Controller for Setup Landing page diff --git a/setup/src/Magento/Setup/Controller/LandingUpdater.php b/setup/src/Magento/Setup/Controller/LandingUpdater.php index 9c6ef98dc6dd1..b3728e404f8c1 100644 --- a/setup/src/Magento/Setup/Controller/LandingUpdater.php +++ b/setup/src/Magento/Setup/Controller/LandingUpdater.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; /** * Controller for Updater Landing page diff --git a/setup/src/Magento/Setup/Controller/License.php b/setup/src/Magento/Setup/Controller/License.php index 84caaebe6c5eb..69778a4bca908 100644 --- a/setup/src/Magento/Setup/Controller/License.php +++ b/setup/src/Magento/Setup/Controller/License.php @@ -6,8 +6,8 @@ namespace Magento\Setup\Controller; use Magento\Setup\Model\License as LicenseModel; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; /** * Class LicenseController diff --git a/setup/src/Magento/Setup/Controller/Maintenance.php b/setup/src/Magento/Setup/Controller/Maintenance.php index c3038c1171766..d95b23453e2c9 100644 --- a/setup/src/Magento/Setup/Controller/Maintenance.php +++ b/setup/src/Magento/Setup/Controller/Maintenance.php @@ -6,9 +6,9 @@ namespace Magento\Setup\Controller; use Magento\Framework\App\MaintenanceMode; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\Json\Json; class Maintenance extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/Marketplace.php b/setup/src/Magento/Setup/Controller/Marketplace.php index 8b3f19167a8da..99e935baa9169 100644 --- a/setup/src/Magento/Setup/Controller/Marketplace.php +++ b/setup/src/Magento/Setup/Controller/Marketplace.php @@ -5,10 +5,10 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; -use Zend\Json\Json; -use Zend\View\Model\JsonModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; +use Laminas\Json\Json; +use Laminas\View\Model\JsonModel; use Magento\Setup\Model\PackagesData; use Magento\Setup\Model\PackagesAuth; @@ -43,7 +43,7 @@ public function indexAction() { $view = new ViewModel; $view->setTemplate('/error/404.phtml'); - $this->getResponse()->setStatusCode(\Zend\Http\Response::STATUS_CODE_404); + $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_404); return $view; } diff --git a/setup/src/Magento/Setup/Controller/MarketplaceCredentials.php b/setup/src/Magento/Setup/Controller/MarketplaceCredentials.php index c2329d09d6021..189acebc2a5f8 100644 --- a/setup/src/Magento/Setup/Controller/MarketplaceCredentials.php +++ b/setup/src/Magento/Setup/Controller/MarketplaceCredentials.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class MarketplaceCredentials extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/ModuleGrid.php b/setup/src/Magento/Setup/Controller/ModuleGrid.php index 1c1da63ea0017..f01e7bfbac11f 100644 --- a/setup/src/Magento/Setup/Controller/ModuleGrid.php +++ b/setup/src/Magento/Setup/Controller/ModuleGrid.php @@ -11,7 +11,7 @@ /** * Controller for module grid tasks */ -class ModuleGrid extends \Zend\Mvc\Controller\AbstractActionController +class ModuleGrid extends \Laminas\Mvc\Controller\AbstractActionController { /** * Module grid @@ -32,11 +32,11 @@ public function __construct( /** * Index page action * - * @return \Zend\View\Model\ViewModel + * @return \Laminas\View\Model\ViewModel */ public function indexAction() { - $view = new \Zend\View\Model\ViewModel(); + $view = new \Laminas\View\Model\ViewModel(); $view->setTerminal(true); return $view; } @@ -44,14 +44,14 @@ public function indexAction() /** * Get Components info action * - * @return \Zend\View\Model\JsonModel + * @return \Laminas\View\Model\JsonModel * @throws \RuntimeException */ public function modulesAction() { $moduleList = $this->gridModule->getList(); - return new \Zend\View\Model\JsonModel( + return new \Laminas\View\Model\JsonModel( [ 'success' => true, 'modules' => $moduleList, diff --git a/setup/src/Magento/Setup/Controller/Modules.php b/setup/src/Magento/Setup/Controller/Modules.php index 2d67e0dc65814..4264e8f986641 100644 --- a/setup/src/Magento/Setup/Controller/Modules.php +++ b/setup/src/Magento/Setup/Controller/Modules.php @@ -7,9 +7,9 @@ use Magento\Setup\Model\ModuleStatus; use Magento\Setup\Model\ObjectManagerProvider; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\Json\Json; class Modules extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/Navigation.php b/setup/src/Magento/Setup/Controller/Navigation.php index 5ac0bbfe38c45..8d7145ccb9751 100644 --- a/setup/src/Magento/Setup/Controller/Navigation.php +++ b/setup/src/Magento/Setup/Controller/Navigation.php @@ -6,9 +6,9 @@ namespace Magento\Setup\Controller; use Magento\Setup\Model\Navigation as NavModel; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; use Magento\Setup\Model\Cron\Status; /** diff --git a/setup/src/Magento/Setup/Controller/OtherComponentsGrid.php b/setup/src/Magento/Setup/Controller/OtherComponentsGrid.php index 284f0d2aee296..edae5c5903090 100644 --- a/setup/src/Magento/Setup/Controller/OtherComponentsGrid.php +++ b/setup/src/Magento/Setup/Controller/OtherComponentsGrid.php @@ -7,8 +7,8 @@ namespace Magento\Setup\Controller; use Magento\Composer\InfoCommand; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; /** * Controller for other components grid on select version page @@ -40,13 +40,13 @@ public function __construct( /** * No index action, return 404 error page * - * @return \Zend\View\Model\ViewModel + * @return \Laminas\View\Model\ViewModel */ public function indexAction() { - $view = new \Zend\View\Model\ViewModel; + $view = new \Laminas\View\Model\ViewModel; $view->setTemplate('/error/404.phtml'); - $this->getResponse()->setStatusCode(\Zend\Http\Response::STATUS_CODE_404); + $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_404); return $view; } diff --git a/setup/src/Magento/Setup/Controller/ReadinessCheckInstaller.php b/setup/src/Magento/Setup/Controller/ReadinessCheckInstaller.php index 6c29ebda3bae5..26bcb8dd2f34d 100644 --- a/setup/src/Magento/Setup/Controller/ReadinessCheckInstaller.php +++ b/setup/src/Magento/Setup/Controller/ReadinessCheckInstaller.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class ReadinessCheckInstaller extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/ReadinessCheckUpdater.php b/setup/src/Magento/Setup/Controller/ReadinessCheckUpdater.php index 91e30ef06e703..c272e64a4ef62 100644 --- a/setup/src/Magento/Setup/Controller/ReadinessCheckUpdater.php +++ b/setup/src/Magento/Setup/Controller/ReadinessCheckUpdater.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class ReadinessCheckUpdater extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/SelectVersion.php b/setup/src/Magento/Setup/Controller/SelectVersion.php index cfac31432feba..613a1504d77e6 100644 --- a/setup/src/Magento/Setup/Controller/SelectVersion.php +++ b/setup/src/Magento/Setup/Controller/SelectVersion.php @@ -8,9 +8,9 @@ use Magento\Composer\InfoCommand; use Magento\Setup\Model\SystemPackage; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; /** * Controller for selecting version @@ -32,7 +32,7 @@ public function __construct( } /** - * @return ViewModel|\Zend\Http\Response + * @return ViewModel|\Laminas\Http\Response */ public function indexAction() { diff --git a/setup/src/Magento/Setup/Controller/Session.php b/setup/src/Magento/Setup/Controller/Session.php index c9caa5a8de792..76f6f2e859abc 100644 --- a/setup/src/Magento/Setup/Controller/Session.php +++ b/setup/src/Magento/Setup/Controller/Session.php @@ -8,10 +8,10 @@ /** * Sets up session for setup/index.php/session/prolong or redirects to error page */ -class Session extends \Zend\Mvc\Controller\AbstractActionController +class Session extends \Laminas\Mvc\Controller\AbstractActionController { /** - * @var \Zend\ServiceManager\ServiceManager + * @var \Laminas\ServiceManager\ServiceManager */ private $serviceManager; @@ -21,11 +21,11 @@ class Session extends \Zend\Mvc\Controller\AbstractActionController private $objectManagerProvider; /** - * @param \Zend\ServiceManager\ServiceManager $serviceManager + * @param \Laminas\ServiceManager\ServiceManager $serviceManager * @param \Magento\Setup\Model\ObjectManagerProvider $objectManagerProvider */ public function __construct( - \Zend\ServiceManager\ServiceManager $serviceManager, + \Laminas\ServiceManager\ServiceManager $serviceManager, \Magento\Setup\Model\ObjectManagerProvider $objectManagerProvider ) { $this->serviceManager = $serviceManager; @@ -35,13 +35,13 @@ public function __construct( /** * No index action, return 404 error page * - * @return \Zend\View\Model\ViewModel|\Zend\Http\Response + * @return \Laminas\View\Model\ViewModel|\Laminas\Http\Response */ public function indexAction() { - $view = new \Zend\View\Model\ViewModel(); + $view = new \Laminas\View\Model\ViewModel(); $view->setTemplate('/error/404.phtml'); - $this->getResponse()->setStatusCode(\Zend\Http\Response::STATUS_CODE_404); + $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_404); return $view; } @@ -78,23 +78,23 @@ public function prolongAction() ); } $session->prolong(); - return new \Zend\View\Model\JsonModel(['success' => true]); + return new \Laminas\View\Model\JsonModel(['success' => true]); } } catch (\Exception $e) { } - return new \Zend\View\Model\JsonModel(['success' => false]); + return new \Laminas\View\Model\JsonModel(['success' => false]); } /** * Unlogin action, return 401 error page * - * @return \Zend\View\Model\ViewModel|\Zend\Http\Response + * @return \Laminas\View\Model\ViewModel|\Laminas\Http\Response */ public function unloginAction() { - $view = new \Zend\View\Model\ViewModel(); + $view = new \Laminas\View\Model\ViewModel(); $view->setTemplate('/error/401.phtml'); - $this->getResponse()->setStatusCode(\Zend\Http\Response::STATUS_CODE_401); + $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_401); return $view; } } diff --git a/setup/src/Magento/Setup/Controller/StartUpdater.php b/setup/src/Magento/Setup/Controller/StartUpdater.php index ff19f8b4c0734..fb4d8ae03b9ef 100644 --- a/setup/src/Magento/Setup/Controller/StartUpdater.php +++ b/setup/src/Magento/Setup/Controller/StartUpdater.php @@ -7,10 +7,10 @@ namespace Magento\Setup\Controller; use Magento\Setup\Model\UpdaterTaskCreator; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; -use Zend\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; +use Laminas\Json\Json; /** * Controller for updater tasks diff --git a/setup/src/Magento/Setup/Controller/Success.php b/setup/src/Magento/Setup/Controller/Success.php index c792aadefe4f0..4df88c5891071 100644 --- a/setup/src/Magento/Setup/Controller/Success.php +++ b/setup/src/Magento/Setup/Controller/Success.php @@ -7,8 +7,8 @@ use Magento\Framework\Module\ModuleList; use Magento\Setup\Model\ObjectManagerProvider; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class Success extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/SystemConfig.php b/setup/src/Magento/Setup/Controller/SystemConfig.php index e089adaddf40f..f189de9fee95a 100644 --- a/setup/src/Magento/Setup/Controller/SystemConfig.php +++ b/setup/src/Magento/Setup/Controller/SystemConfig.php @@ -5,8 +5,8 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class SystemConfig extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/UpdateExtensionGrid.php b/setup/src/Magento/Setup/Controller/UpdateExtensionGrid.php index b5d342cf69745..55ff91a04cdf4 100644 --- a/setup/src/Magento/Setup/Controller/UpdateExtensionGrid.php +++ b/setup/src/Magento/Setup/Controller/UpdateExtensionGrid.php @@ -5,9 +5,9 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; use Magento\Setup\Model\Grid; /** diff --git a/setup/src/Magento/Setup/Controller/UpdaterSuccess.php b/setup/src/Magento/Setup/Controller/UpdaterSuccess.php index 37a662b959faa..4a4a5ce7f665f 100644 --- a/setup/src/Magento/Setup/Controller/UpdaterSuccess.php +++ b/setup/src/Magento/Setup/Controller/UpdaterSuccess.php @@ -6,8 +6,8 @@ namespace Magento\Setup\Controller; use Magento\Framework\App\MaintenanceMode; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class UpdaterSuccess extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/UrlCheck.php b/setup/src/Magento/Setup/Controller/UrlCheck.php index 6a209e0c18304..af7d3738be1e2 100644 --- a/setup/src/Magento/Setup/Controller/UrlCheck.php +++ b/setup/src/Magento/Setup/Controller/UrlCheck.php @@ -5,9 +5,9 @@ */ namespace Magento\Setup\Controller; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; -use Zend\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\Json\Json; use Magento\Framework\Validator\Url as UrlValidator; class UrlCheck extends AbstractActionController diff --git a/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php b/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php index ce5da002a372d..03faef928cdaf 100644 --- a/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php +++ b/setup/src/Magento/Setup/Controller/ValidateAdminCredentials.php @@ -8,9 +8,9 @@ use Magento\Setup\Model\Installer; use Magento\Setup\Model\RequestDataConverter; use Magento\Setup\Validator\AdminCredentialsValidator; -use Zend\Json\Json; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\JsonModel; +use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; /** * Controller for admin credentials validation diff --git a/setup/src/Magento/Setup/Controller/WebConfiguration.php b/setup/src/Magento/Setup/Controller/WebConfiguration.php index 6dded9f4071ce..68800f0f7404c 100644 --- a/setup/src/Magento/Setup/Controller/WebConfiguration.php +++ b/setup/src/Magento/Setup/Controller/WebConfiguration.php @@ -7,8 +7,8 @@ use Magento\Framework\App\SetupInfo; use Magento\Framework\Config\ConfigOptionsListConstants; -use Zend\Mvc\Controller\AbstractActionController; -use Zend\View\Model\ViewModel; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\ViewModel; class WebConfiguration extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Model/AdminAccountFactory.php b/setup/src/Magento/Setup/Model/AdminAccountFactory.php index 5f31bae30d3a9..86687fd28c23e 100644 --- a/setup/src/Magento/Setup/Model/AdminAccountFactory.php +++ b/setup/src/Magento/Setup/Model/AdminAccountFactory.php @@ -7,7 +7,7 @@ namespace Magento\Setup\Model; use Magento\Setup\Module\Setup; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\DB\Adapter\AdapterInterface; diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsListCollector.php b/setup/src/Magento/Setup/Model/ConfigOptionsListCollector.php index cd2442c215bb3..191d37f00a132 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsListCollector.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsListCollector.php @@ -9,7 +9,7 @@ use Magento\Framework\Filesystem; use Magento\Framework\Module\FullModuleList; use Magento\Framework\Setup\ConfigOptionsListInterface; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; /** * Collects all ConfigOptionsList class in modules and setup diff --git a/setup/src/Magento/Setup/Model/Cron/JobFactory.php b/setup/src/Magento/Setup/Model/Cron/JobFactory.php index a29bf389254f7..26cdc7aa3de80 100644 --- a/setup/src/Magento/Setup/Model/Cron/JobFactory.php +++ b/setup/src/Magento/Setup/Model/Cron/JobFactory.php @@ -11,7 +11,7 @@ use Magento\Setup\Console\Command\ModuleDisableCommand; use Magento\Setup\Console\Command\ModuleEnableCommand; use Magento\Setup\Console\Command\UpgradeCommand; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; use Magento\Setup\Console\Command\MaintenanceDisableCommand; use Magento\Setup\Console\Command\MaintenanceEnableCommand; diff --git a/setup/src/Magento/Setup/Model/InstallerFactory.php b/setup/src/Magento/Setup/Model/InstallerFactory.php index 0fb933dd46cb4..aaa33bd1a5ae4 100644 --- a/setup/src/Magento/Setup/Model/InstallerFactory.php +++ b/setup/src/Magento/Setup/Model/InstallerFactory.php @@ -6,7 +6,7 @@ namespace Magento\Setup\Model; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; use Magento\Setup\Module\ResourceFactory; use Magento\Framework\App\ErrorHandler; use Magento\Framework\Setup\LoggerInterface; diff --git a/setup/src/Magento/Setup/Model/Navigation.php b/setup/src/Magento/Setup/Model/Navigation.php index 9882059be0f7e..1c5cee2303c2a 100644 --- a/setup/src/Magento/Setup/Model/Navigation.php +++ b/setup/src/Magento/Setup/Model/Navigation.php @@ -6,7 +6,7 @@ namespace Magento\Setup\Model; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; use Magento\Framework\App\DeploymentConfig; class Navigation diff --git a/setup/src/Magento/Setup/Model/ObjectManagerProvider.php b/setup/src/Magento/Setup/Model/ObjectManagerProvider.php index 79216c8ec89b5..d89976552ab33 100644 --- a/setup/src/Magento/Setup/Model/ObjectManagerProvider.php +++ b/setup/src/Magento/Setup/Model/ObjectManagerProvider.php @@ -9,7 +9,7 @@ use Symfony\Component\Console\Application; use Magento\Framework\Console\CommandListInterface; use Magento\Framework\ObjectManagerInterface; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; use Magento\Setup\Mvc\Bootstrap\InitParamListener; /** diff --git a/setup/src/Magento/Setup/Model/PackagesAuth.php b/setup/src/Magento/Setup/Model/PackagesAuth.php index 5a29f9953d51b..502952db1aa16 100644 --- a/setup/src/Magento/Setup/Model/PackagesAuth.php +++ b/setup/src/Magento/Setup/Model/PackagesAuth.php @@ -7,7 +7,7 @@ namespace Magento\Setup\Model; use Magento\Framework\App\Filesystem\DirectoryList; -use Zend\View\Model\JsonModel; +use Laminas\View\Model\JsonModel; /** * Class PackagesAuth, checks, saves and removes auth details related to packages. @@ -30,7 +30,7 @@ class PackagesAuth /**#@-*/ /** - * @var \Zend\ServiceManager\ServiceLocatorInterface + * @var \Laminas\ServiceManager\ServiceLocatorInterface */ protected $serviceLocator; @@ -55,14 +55,14 @@ class PackagesAuth private $serializer; /** - * @param \Zend\ServiceManager\ServiceLocatorInterface $serviceLocator + * @param \Laminas\ServiceManager\ServiceLocatorInterface $serviceLocator * @param \Magento\Framework\HTTP\Client\Curl $curl * @param \Magento\Framework\Filesystem $filesystem * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer * @throws \RuntimeException */ public function __construct( - \Zend\ServiceManager\ServiceLocatorInterface $serviceLocator, + \Laminas\ServiceManager\ServiceLocatorInterface $serviceLocator, \Magento\Framework\HTTP\Client\Curl $curl, \Magento\Framework\Filesystem $filesystem, \Magento\Framework\Serialize\Serializer\Json $serializer = null @@ -198,7 +198,7 @@ public function saveAuthJson($username, $password) ] ] ]; - $json = new \Zend\View\Model\JsonModel($authContent); + $json = new \Laminas\View\Model\JsonModel($authContent); $json->setOption('prettyPrint', true); $jsonContent = $json->serialize(); diff --git a/setup/src/Magento/Setup/Model/UpdaterTaskCreator.php b/setup/src/Magento/Setup/Model/UpdaterTaskCreator.php index e3c598a4aa57e..c80717fe7c857 100644 --- a/setup/src/Magento/Setup/Model/UpdaterTaskCreator.php +++ b/setup/src/Magento/Setup/Model/UpdaterTaskCreator.php @@ -8,7 +8,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Setup\Model\Cron\JobComponentUninstall; -use Zend\Json\Json; +use Laminas\Json\Json; /** * Validates payloads for updater tasks diff --git a/setup/src/Magento/Setup/Module.php b/setup/src/Magento/Setup/Module.php index a2c4b1b774b9b..635d954dcda84 100644 --- a/setup/src/Magento/Setup/Module.php +++ b/setup/src/Magento/Setup/Module.php @@ -8,11 +8,11 @@ use Magento\Framework\App\Response\HeaderProvider\XssProtection; use Magento\Setup\Mvc\View\Http\InjectTemplateListener; -use Zend\EventManager\EventInterface; -use Zend\ModuleManager\Feature\BootstrapListenerInterface; -use Zend\ModuleManager\Feature\ConfigProviderInterface; -use Zend\Mvc\ModuleRouteListener; -use Zend\Mvc\MvcEvent; +use Laminas\EventManager\EventInterface; +use Laminas\ModuleManager\Feature\BootstrapListenerInterface; +use Laminas\ModuleManager\Feature\ConfigProviderInterface; +use Laminas\Mvc\ModuleRouteListener; +use Laminas\Mvc\MvcEvent; class Module implements BootstrapListenerInterface, @@ -23,28 +23,28 @@ class Module implements */ public function onBootstrap(EventInterface $e) { - /** @var \Zend\Mvc\MvcEvent $e */ - /** @var \Zend\Mvc\Application $application */ + /** @var \Laminas\Mvc\MvcEvent $e */ + /** @var \Laminas\Mvc\Application $application */ $application = $e->getApplication(); - /** @var \Zend\EventManager\EventManager $events */ + /** @var \Laminas\EventManager\EventManager $events */ $events = $application->getEventManager(); - /** @var \Zend\EventManager\SharedEventManager $sharedEvents */ + /** @var \Laminas\EventManager\SharedEventManager $sharedEvents */ $sharedEvents = $events->getSharedManager(); $moduleRouteListener = new ModuleRouteListener(); $moduleRouteListener->attach($events); - // Override Zend\Mvc\View\Http\InjectTemplateListener + // Override Laminas\Mvc\View\Http\InjectTemplateListener // to process templates by Vendor/Module $injectTemplateListener = new InjectTemplateListener(); $sharedEvents->attach( - \Zend\Stdlib\DispatchableInterface::class, + \Laminas\Stdlib\DispatchableInterface::class, MvcEvent::EVENT_DISPATCH, [$injectTemplateListener, 'injectTemplate'], -89 ); $response = $e->getResponse(); - if ($response instanceof \Zend\Http\Response) { + if ($response instanceof \Laminas\Http\Response) { $headers = $response->getHeaders(); if ($headers) { $headers->addHeaderLine('Cache-Control', 'no-cache, no-store, must-revalidate'); @@ -52,7 +52,7 @@ public function onBootstrap(EventInterface $e) $headers->addHeaderLine('Expires', '1970-01-01'); $headers->addHeaderLine('X-Frame-Options: SAMEORIGIN'); $headers->addHeaderLine('X-Content-Type-Options: nosniff'); - /** @var \Zend\Http\Header\UserAgent $userAgentHeader */ + /** @var \Laminas\Http\Header\UserAgent $userAgentHeader */ $userAgentHeader = $e->getRequest()->getHeader('User-Agent'); $xssHeaderValue = $userAgentHeader && $userAgentHeader->getFieldValue() && strpos($userAgentHeader->getFieldValue(), XssProtection::IE_8_USER_AGENT) === false diff --git a/setup/src/Magento/Setup/Module/ConnectionFactory.php b/setup/src/Magento/Setup/Module/ConnectionFactory.php index ccb1f819bb0a9..5f50d5efdf381 100644 --- a/setup/src/Magento/Setup/Module/ConnectionFactory.php +++ b/setup/src/Magento/Setup/Module/ConnectionFactory.php @@ -6,7 +6,7 @@ namespace Magento\Setup\Module; use Magento\Framework\Model\ResourceModel\Type\Db\Pdo\Mysql; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; /** * Connection adapter factory diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php index be52fc3d24dfd..8c80f339a3a70 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php @@ -9,7 +9,7 @@ /** * @SuppressWarnings(PHPMD) */ -class FileScanner extends \Zend\Code\Scanner\FileScanner +class FileScanner extends \Laminas\Code\Scanner\FileScanner { /** * @var int @@ -26,7 +26,7 @@ protected function scan() } if (!$this->tokens) { - throw new \Zend\Code\Exception\RuntimeException('No tokens were provided'); + throw new \Laminas\Code\Exception\RuntimeException('No tokens were provided'); } /** diff --git a/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php index 1cd242acbe50b..6f8976b552f41 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Scanner/PhpScanner.php @@ -145,7 +145,7 @@ protected function _fetchMissingExtensionAttributesClasses($reflectionClass, $fi $entityType = ucfirst(\Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceGenerator::ENTITY_TYPE); if ($reflectionClass->hasMethod($methodName) && $reflectionClass->isInterface()) { $returnType = $this->typeProcessor->getGetterReturnType( - (new \Zend\Code\Reflection\ClassReflection($reflectionClass->getName()))->getMethod($methodName) + (new \Laminas\Code\Reflection\ClassReflection($reflectionClass->getName()))->getMethod($methodName) ); $missingClassName = $returnType['type']; if ($this->shouldGenerateClass($missingClassName, $entityType, $file)) { diff --git a/setup/src/Magento/Setup/Module/ResourceFactory.php b/setup/src/Magento/Setup/Module/ResourceFactory.php index 7d26226cd6929..e29e6a8726c4b 100644 --- a/setup/src/Magento/Setup/Module/ResourceFactory.php +++ b/setup/src/Magento/Setup/Module/ResourceFactory.php @@ -7,7 +7,7 @@ use Magento\Framework\App\ResourceConnection; use Magento\Setup\Module\Setup\ResourceConfig; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; class ResourceFactory { diff --git a/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php b/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php index 709be46eac42b..f57fdd4c66962 100644 --- a/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php +++ b/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php @@ -12,16 +12,16 @@ use Magento\Framework\App\State; use Magento\Framework\Filesystem; use Magento\Framework\Shell\ComplexParameter; -use Zend\Console\Request; -use Zend\EventManager\EventManagerInterface; -use Zend\EventManager\ListenerAggregateInterface; -use Zend\Mvc\Application; -use Zend\Mvc\MvcEvent; -use Zend\Router\Http\RouteMatch; -use Zend\ServiceManager\FactoryInterface; -use Zend\ServiceManager\ServiceLocatorInterface; -use Zend\Stdlib\RequestInterface; -use Zend\Uri\UriInterface; +use Laminas\Console\Request; +use Laminas\EventManager\EventManagerInterface; +use Laminas\EventManager\ListenerAggregateInterface; +use Laminas\Mvc\Application; +use Laminas\Mvc\MvcEvent; +use Laminas\Router\Http\RouteMatch; +use Laminas\ServiceManager\FactoryInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; +use Laminas\Stdlib\RequestInterface; +use Laminas\Uri\UriInterface; /** * A listener that injects relevant Magento initialization parameters and initializes filesystem @@ -37,7 +37,7 @@ class InitParamListener implements ListenerAggregateInterface, FactoryInterface const BOOTSTRAP_PARAM = 'magento-init-params'; /** - * @var \Zend\Stdlib\CallbackHandler[] + * @var \Laminas\Stdlib\CallbackHandler[] */ private $listeners = []; @@ -114,8 +114,8 @@ public function onBootstrap(MvcEvent $e) /** * Check if user logged-in and has permissions * - * @param \Zend\Mvc\MvcEvent $event - * @return false|\Zend\Http\Response + * @param \Laminas\Mvc\MvcEvent $event + * @return false|\Laminas\Http\Response * * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Setup\Exception @@ -162,7 +162,7 @@ public function authPreDispatch($event) !$adminSession->isAllowed('Magento_Backend::setup_wizard') ) { $adminSession->destroy(); - /** @var \Zend\Http\Response $response */ + /** @var \Laminas\Http\Response $response */ $response = $event->getResponse(); $baseUrl = Http::getDistroBaseUrlPath($_SERVER); $response->getHeaders()->addHeaderLine('Location', $baseUrl . 'index.php/session/unlogin'); diff --git a/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php b/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php index 5d8d903c558d6..2b4b2af1bc84f 100644 --- a/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php +++ b/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php @@ -6,8 +6,8 @@ namespace Magento\Setup\Mvc\View\Http; -use Zend\Mvc\MvcEvent; -use Zend\Mvc\View\Http\InjectTemplateListener as ZendInjectTemplateListener; +use Laminas\Mvc\MvcEvent; +use Laminas\Mvc\View\Http\InjectTemplateListener as ZendInjectTemplateListener; class InjectTemplateListener extends ZendInjectTemplateListener { diff --git a/setup/src/Magento/Setup/Test/Unit/Console/CommandListTest.php b/setup/src/Magento/Setup/Test/Unit/Console/CommandListTest.php index 3f3e06ac9d732..d63461975f21e 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/CommandListTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/CommandListTest.php @@ -16,13 +16,13 @@ class CommandListTest extends \PHPUnit\Framework\TestCase private $commandList; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Zend\ServiceManager\ServiceManager + * @var \PHPUnit_Framework_MockObject_MockObject|\Laminas\ServiceManager\ServiceManager */ private $serviceManager; public function setUp() { - $this->serviceManager = $this->createMock(\Zend\ServiceManager\ServiceManager::class); + $this->serviceManager = $this->createMock(\Laminas\ServiceManager\ServiceManager::class); $this->commandList = new CommandList($this->serviceManager); } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php b/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php index 7bd11c64f93ac..212759374847d 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php @@ -11,7 +11,7 @@ use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Magento\Framework\Filesystem\Driver\File; use Symfony\Component\Console\Input\ArgvInput; -use Zend\ServiceManager\ServiceManager; +use Laminas\ServiceManager\ServiceManager; use Magento\Setup\Console\CompilerPreparation; use PHPUnit_Framework_MockObject_MockObject as Mock; diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/AddDatabaseTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/AddDatabaseTest.php index 6e8306965c6ec..ec346fa6353d9 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/AddDatabaseTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/AddDatabaseTest.php @@ -15,7 +15,7 @@ public function testIndexAction() /** @var $controller AddDatabase */ $controller = new AddDatabase(); $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/BackupActionItemsTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/BackupActionItemsTest.php index f6004d808ec6f..93912c3062097 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/BackupActionItemsTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/BackupActionItemsTest.php @@ -67,11 +67,11 @@ public function setUp() $this->filesystem ); - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); - $mvcEvent = $this->createMock(\Zend\Mvc\MvcEvent::class); + $mvcEvent = $this->createMock(\Laminas\Mvc\MvcEvent::class); $mvcEvent->expects($this->any())->method('setRequest')->with($request)->willReturn($mvcEvent); $mvcEvent->expects($this->any())->method('setResponse')->with($response)->willReturn($mvcEvent); $mvcEvent->expects($this->any())->method('setTarget')->with($this->controller)->willReturn($mvcEvent); @@ -91,7 +91,7 @@ public function testCheckAction() $this->directoryList->expects($this->once())->method('getPath')->willReturn(__DIR__); $this->filesystem->expects($this->once())->method('validateAvailableDiscSpace'); $jsonModel = $this->controller->checkAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_SUCCESS, $variables['responseType']); @@ -106,7 +106,7 @@ public function testCheckActionWithError() $this->throwException(new \Exception("Test error message")) ); $jsonModel = $this->controller->checkAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_ERROR, $variables['responseType']); @@ -118,7 +118,7 @@ public function testCreateAction() { $this->backupRollback->expects($this->once())->method('dbBackup')->willReturn('backup/path/'); $jsonModel = $this->controller->createAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_SUCCESS, $variables['responseType']); @@ -129,6 +129,6 @@ public function testCreateAction() public function testIndexAction() { $model = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $model); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $model); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/CompleteBackupTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/CompleteBackupTest.php index ef3290785875f..755552e86de79 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/CompleteBackupTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/CompleteBackupTest.php @@ -25,10 +25,10 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertSame('/error/404.phtml', $viewModel->getTemplate()); $this->assertSame( - \Zend\Http\Response::STATUS_CODE_404, + \Laminas\Http\Response::STATUS_CODE_404, $this->controller->getResponse()->getStatusCode() ); } @@ -36,7 +36,7 @@ public function testIndexAction() public function testProgressAction() { $viewModel = $this->controller->progressAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $this->assertSame('/magento/setup/complete-backup/progress.phtml', $viewModel->getTemplate()); } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/CreateAdminAccountTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/CreateAdminAccountTest.php index b8df922fcc93c..c33e56333cf8d 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/CreateAdminAccountTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/CreateAdminAccountTest.php @@ -14,7 +14,7 @@ public function testIndexAction() { $controller = new CreateAdminAccount(); $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/CreateBackupTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/CreateBackupTest.php index fd3b36b25525c..eb7d7cee9a2c1 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/CreateBackupTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/CreateBackupTest.php @@ -15,7 +15,7 @@ public function testIndexAction() /** @var $controller CreateBackup */ $controller = new CreateBackup(); $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/CustomizeYourStoreTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/CustomizeYourStoreTest.php index bb4e0c8b9291b..e7b5df737eee5 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/CustomizeYourStoreTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/CustomizeYourStoreTest.php @@ -71,7 +71,7 @@ public function testIndexAction($expected, $withSampleData) $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $variables = $viewModel->getVariables(); @@ -109,7 +109,7 @@ public function indexActionDataProvider() public function testDefaultTimeZoneAction() { $jsonModel = $this->controller->defaultTimeZoneAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $this->assertArrayHasKey('defaultTimeZone', $jsonModel->getVariables()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/DataOptionTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/DataOptionTest.php index 89831dd1471f5..b30a23a92c607 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/DataOptionTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/DataOptionTest.php @@ -16,17 +16,17 @@ class DataOptionTest extends \PHPUnit\Framework\TestCase private $uninstallCollector; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Zend\Http\PhpEnvironment\Request + * @var \PHPUnit_Framework_MockObject_MockObject|\Laminas\Http\PhpEnvironment\Request */ private $request; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Zend\Http\PhpEnvironment\Response + * @var \PHPUnit_Framework_MockObject_MockObject|\Laminas\Http\PhpEnvironment\Response */ private $response; /** - * @var \Zend\Mvc\MvcEvent|\PHPUnit_Framework_MockObject_MockObject + * @var \Laminas\Mvc\MvcEvent|\PHPUnit_Framework_MockObject_MockObject */ private $mvcEvent; @@ -37,14 +37,14 @@ class DataOptionTest extends \PHPUnit\Framework\TestCase public function setUp() { - $this->request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $this->response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $this->request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $this->response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); $this->uninstallCollector = $this->createMock(\Magento\Setup\Model\UninstallCollector::class); $this->controller = new DataOption($this->uninstallCollector); - $this->mvcEvent = $this->createMock(\Zend\Mvc\MvcEvent::class); + $this->mvcEvent = $this->createMock(\Laminas\Mvc\MvcEvent::class); $this->mvcEvent->expects($this->any()) ->method('setRequest') ->with($this->request) @@ -64,7 +64,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/EnvironmentTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/EnvironmentTest.php index 6050122d78521..825905aa5099b 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/EnvironmentTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/EnvironmentTest.php @@ -10,7 +10,7 @@ use Magento\Setup\Controller\ReadinessCheckUpdater; use Magento\Setup\Controller\ResponseTypeInterface; use PHPUnit\Framework\MockObject\MockObject; -use Zend\View\Model\JsonModel; +use Laminas\View\Model\JsonModel; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -58,9 +58,9 @@ public function setUp() public function testFilePermissionsInstaller() { - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); $mvcEvent = $this->getMvcEventMock($request, $response, $routeMatch); @@ -72,9 +72,9 @@ public function testFilePermissionsInstaller() public function testPhpVersionActionInstaller() { - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); $mvcEvent = $this->getMvcEventMock($request, $response, $routeMatch); @@ -87,9 +87,9 @@ public function testPhpVersionActionInstaller() public function testPhpVersionActionUpdater() { - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); $mvcEvent = $this->getMvcEventMock($request, $response, $routeMatch); @@ -108,9 +108,9 @@ public function testPhpVersionActionUpdater() public function testPhpSettingsActionInstaller() { - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); $mvcEvent = $this->getMvcEventMock($request, $response, $routeMatch); @@ -123,9 +123,9 @@ public function testPhpSettingsActionInstaller() public function testPhpSettingsActionUpdater() { - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); $mvcEvent = $this->getMvcEventMock($request, $response, $routeMatch); @@ -144,9 +144,9 @@ public function testPhpSettingsActionUpdater() public function testPhpExtensionsActionInstaller() { - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); $mvcEvent = $this->getMvcEventMock($request, $response, $routeMatch); @@ -159,9 +159,9 @@ public function testPhpExtensionsActionInstaller() public function testPhpExtensionsActionUpdater() { - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); $mvcEvent = $this->getMvcEventMock($request, $response, $routeMatch); @@ -291,7 +291,7 @@ public function testCronScriptActionBothNotice() public function testIndexAction() { $model = $this->environment->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $model); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $model); } /** @@ -306,7 +306,7 @@ protected function getMvcEventMock( MockObject $response, MockObject $routeMatch ) { - $mvcEvent = $this->createMock(\Zend\Mvc\MvcEvent::class); + $mvcEvent = $this->createMock(\Laminas\Mvc\MvcEvent::class); $mvcEvent->expects($this->once())->method('setRequest')->with($request)->willReturn($mvcEvent); $mvcEvent->expects($this->once())->method('setResponse')->with($response)->willReturn($mvcEvent); $mvcEvent->expects($this->once())->method('setTarget')->with($this->environment)->willReturn($mvcEvent); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/ExtensionGridTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/ExtensionGridTest.php index febcbd1f8dbd4..a97a8d96b34d2 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/ExtensionGridTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/ExtensionGridTest.php @@ -97,7 +97,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } @@ -119,7 +119,7 @@ public function testExtensionsAction() ); $jsonModel = $this->controller->extensionsAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertTrue($variables['success']); @@ -147,7 +147,7 @@ public function testSyncAction() ); $jsonModel = $this->controller->syncAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertTrue($variables['success']); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/IndexTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/IndexTest.php index f258d5d98d107..e39c535fe9980 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/IndexTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/IndexTest.php @@ -15,7 +15,7 @@ public function testIndexAction() /** @var $controller Index */ $controller = new Index(); $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertFalse($viewModel->terminate()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/InstallExtensionGridTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/InstallExtensionGridTest.php index 9eba4413d0cda..a92988f060724 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/InstallExtensionGridTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/InstallExtensionGridTest.php @@ -41,7 +41,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - static::assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + static::assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); } /** @@ -56,7 +56,7 @@ public function testExtensionsAction($extensions) ->willReturn($extensions); $jsonModel = $this->controller->extensionsAction(); - static::assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + static::assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); static::assertArrayHasKey('success', $variables); static::assertArrayHasKey('extensions', $variables); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/InstallTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/InstallTest.php index d0fd62adc42b4..e68ff822e4f1a 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/InstallTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/InstallTest.php @@ -75,7 +75,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } @@ -86,7 +86,7 @@ public function testStartAction() $this->installer->expects($this->exactly(2))->method('getInstallInfo'); $this->deploymentConfig->expects($this->once())->method('isAvailable')->willReturn(false); $jsonModel = $this->controller->startAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('key', $variables); $this->assertArrayHasKey('success', $variables); @@ -101,7 +101,7 @@ public function testStartActionPriorInstallException() $this->installer->expects($this->never())->method('getInstallInfo'); $this->deploymentConfig->expects($this->once())->method('isAvailable')->willReturn(true); $jsonModel = $this->controller->startAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertArrayHasKey('messages', $variables); @@ -126,7 +126,7 @@ public function testStartActionWithSampleDataError() $this->installer->method('install'); $this->sampleDataState->expects($this->once())->method('hasError')->willReturn(true); $jsonModel = $this->controller->startAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertTrue($variables['success']); @@ -145,7 +145,7 @@ public function testProgressAction() $progress->expects($this->once())->method('getRatio')->willReturn($numValue); $this->webLogger->expects($this->once())->method('get')->willReturn($consoleMessages); $jsonModel = $this->controller->progressAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('progress', $variables); $this->assertArrayHasKey('success', $variables); @@ -162,7 +162,7 @@ public function testProgressActionWithError() $this->progressFactory->expects($this->once())->method('createFromLog') ->will($this->throwException(new \LogicException($e))); $jsonModel = $this->controller->progressAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertArrayHasKey('console', $variables); @@ -180,7 +180,7 @@ public function testProgressActionWithSampleDataError() $this->progressFactory->expects($this->once())->method('createFromLog')->willReturn($progress); $this->sampleDataState->expects($this->once())->method('hasError')->willReturn(true); $jsonModel = $this->controller->progressAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertArrayHasKey('console', $variables); @@ -193,7 +193,7 @@ public function testProgressActionNoInstallLogFile() { $this->webLogger->expects($this->once())->method('logfileExists')->willReturn(false); $jsonModel = $this->controller->progressAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertArrayHasKey('console', $variables); @@ -204,11 +204,11 @@ public function testProgressActionNoInstallLogFile() public function testDispatch() { - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); - $mvcEvent = $this->createMock(\Zend\Mvc\MvcEvent::class); + $mvcEvent = $this->createMock(\Laminas\Mvc\MvcEvent::class); $mvcEvent->expects($this->once())->method('setRequest')->with($request)->willReturn($mvcEvent); $mvcEvent->expects($this->once())->method('setResponse')->with($response)->willReturn($mvcEvent); $mvcEvent->expects($this->once())->method('setTarget')->with($this->controller)->willReturn($mvcEvent); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/LandingInstallerTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/LandingInstallerTest.php index d337270dd938b..7db7d30b8b6fc 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/LandingInstallerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/LandingInstallerTest.php @@ -32,7 +32,7 @@ public function testIndexAction() $controller = new LandingInstaller($productMetadataMock); $_SERVER['DOCUMENT_ROOT'] = 'some/doc/root/value'; $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $this->assertEquals('/magento/setup/landing.phtml', $viewModel->getTemplate()); $variables = $viewModel->getVariables(); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/LandingUpdaterTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/LandingUpdaterTest.php index 1e75b36334bb9..3ad28a24bb734 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/LandingUpdaterTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/LandingUpdaterTest.php @@ -32,7 +32,7 @@ public function testIndexAction() $controller = new LandingUpdater($productMetadataMock); $_SERVER['DOCUMENT_ROOT'] = 'some/doc/root/value'; $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $this->assertEquals('/magento/setup/landing.phtml', $viewModel->getTemplate()); $variables = $viewModel->getVariables(); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/LicenseTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/LicenseTest.php index b496051c947ca..367be5f7b8381 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/LicenseTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/LicenseTest.php @@ -30,7 +30,7 @@ public function testIndexActionWithLicense() { $this->licenseModel->expects($this->once())->method('getContents')->willReturn('some license string'); $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertArrayHasKey('license', $viewModel->getVariables()); } @@ -38,7 +38,7 @@ public function testIndexActionNoLicense() { $this->licenseModel->expects($this->once())->method('getContents')->willReturn(false); $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertArrayHasKey('message', $viewModel->getVariables()); $this->assertEquals('error/404', $viewModel->getTemplate()); } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/MaintenanceTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/MaintenanceTest.php index f4c27ee890785..b938b1e04a81b 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/MaintenanceTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/MaintenanceTest.php @@ -29,11 +29,11 @@ public function setUp() $this->maintenanceMode = $this->createMock(\Magento\Framework\App\MaintenanceMode::class); $this->controller = new Maintenance($this->maintenanceMode); - $request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); + $request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); - $mvcEvent = $this->createMock(\Zend\Mvc\MvcEvent::class); + $mvcEvent = $this->createMock(\Laminas\Mvc\MvcEvent::class); $mvcEvent->expects($this->any())->method('setRequest')->with($request)->willReturn($mvcEvent); $mvcEvent->expects($this->any())->method('setResponse')->with($response)->willReturn($mvcEvent); $mvcEvent->expects($this->any())->method('setTarget')->with($this->controller)->willReturn($mvcEvent); @@ -51,7 +51,7 @@ public function testIndexAction() { $this->maintenanceMode->expects($this->once())->method('set'); $jsonModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_SUCCESS, $variables['responseType']); @@ -63,7 +63,7 @@ public function testIndexActionWithExceptions() $this->throwException(new \Exception("Test error message")) ); $jsonModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_ERROR, $variables['responseType']); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/MarketplaceTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/MarketplaceTest.php index dbdd6003f69c9..91a9bfcfdc296 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/MarketplaceTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/MarketplaceTest.php @@ -45,7 +45,7 @@ public function testSaveAuthJsonAction() ->method('saveAuthJson') ->willReturn(true); $jsonModel = $this->controller->saveAuthJsonAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertTrue($variables['success']); @@ -59,7 +59,7 @@ public function testSaveAuthJsonActionWithError() ->will($this->throwException(new \Exception)); $this->packagesAuth->expects($this->never())->method('saveAuthJson'); $jsonModel = $this->controller->saveAuthJsonAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertArrayHasKey('message', $variables); @@ -77,7 +77,7 @@ public function testCheckAuthAction() ->method('checkCredentials') ->will($this->returnValue(json_encode(['success' => true]))); $jsonModel = $this->controller->checkAuthAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertTrue($variables['success']); @@ -90,7 +90,7 @@ public function testCheckAuthActionWithError() ->method('getAuthJsonData') ->will($this->throwException(new \Exception)); $jsonModel = $this->controller->checkAuthAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertArrayHasKey('message', $variables); @@ -105,7 +105,7 @@ public function testRemoveCredentialsAction() ->will($this->returnValue(true)); $jsonModel = $this->controller->removeCredentialsAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertTrue($variables['success']); @@ -118,7 +118,7 @@ public function testRemoveCredentialsWithError() ->method('removeCredentials') ->will($this->throwException(new \Exception)); $jsonModel = $this->controller->removeCredentialsAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertArrayHasKey('message', $variables); @@ -128,13 +128,13 @@ public function testRemoveCredentialsWithError() public function testPopupAuthAction() { $viewModel = $this->controller->popupAuthAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } public function testIndexAction() { $model = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $model); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $model); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/ModuleGridTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/ModuleGridTest.php index 2c25aac37d578..25bf754676c23 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/ModuleGridTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/ModuleGridTest.php @@ -40,7 +40,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } @@ -72,7 +72,7 @@ public function testModulesAction() ->willReturn($moduleList); $jsonModel = $this->controller->modulesAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertTrue($variables['success']); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/ModulesTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/ModulesTest.php index 35a463090a806..243feebbc2655 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/ModulesTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/ModulesTest.php @@ -56,7 +56,7 @@ public function testIndexAction(array $expected) $this->modules->expects($this->once())->method('getAllModules')->willReturn($expected['modules']); $this->status->expects($this->once())->method('checkConstraints')->willReturn([]); $jsonModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertTrue($variables['success']); @@ -74,7 +74,7 @@ public function testIndexActionWithError(array $expected) ->method('checkConstraints') ->willReturn(['ModuleA', 'ModuleB']); $jsonModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('success', $variables); $this->assertArrayHasKey('error', $variables); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/NavigationTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/NavigationTest.php index a8a3962793d51..595892a71ac98 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/NavigationTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/NavigationTest.php @@ -46,14 +46,14 @@ public function testIndexAction() $this->navigationModel->expects($this->once())->method('getData')->willReturn('some data'); $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $viewModel); $this->assertArrayHasKey('nav', $viewModel->getVariables()); } public function testMenuActionUpdater() { $viewModel = $this->controller->menuAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $variables = $viewModel->getVariables(); $this->assertArrayHasKey('menu', $variables); $this->assertArrayHasKey('main', $variables); @@ -64,7 +64,7 @@ public function testMenuActionUpdater() public function testMenuActionInstaller() { $viewModel = $this->controller->menuAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $variables = $viewModel->getVariables(); $this->assertArrayHasKey('menu', $variables); $this->assertArrayHasKey('main', $variables); @@ -76,7 +76,7 @@ public function testHeaderBarInstaller() { $this->navigationModel->expects($this->once())->method('getType')->willReturn(NavModel::NAV_INSTALLER); $viewModel = $this->controller->headerBarAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $variables = $viewModel->getVariables(); $this->assertArrayHasKey('menu', $variables); $this->assertArrayHasKey('main', $variables); @@ -88,7 +88,7 @@ public function testHeaderBarUpdater() { $this->navigationModel->expects($this->once())->method('getType')->willReturn(NavModel::NAV_UPDATER); $viewModel = $this->controller->headerBarAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $variables = $viewModel->getVariables(); $this->assertArrayHasKey('menu', $variables); $this->assertArrayHasKey('main', $variables); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/OtherComponentsGridTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/OtherComponentsGridTest.php index 095c8907bf43d..feb8d1abd2493 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/OtherComponentsGridTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/OtherComponentsGridTest.php @@ -69,7 +69,7 @@ public function testComponentsAction() ] ]); $jsonModel = $this->controller->componentsAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_SUCCESS, $variables['responseType']); @@ -109,7 +109,7 @@ public function testComponentsActionWithError() ->method('getInstalledMagentoPackages') ->will($this->throwException(new \Exception("Test error message"))); $jsonModel = $this->controller->componentsAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_ERROR, $variables['responseType']); @@ -118,6 +118,6 @@ public function testComponentsActionWithError() public function testIndexAction() { $model = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $model); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $model); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/ReadinessCheckInstallerTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/ReadinessCheckInstallerTest.php index 81e687564b857..71bdc74730bc3 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/ReadinessCheckInstallerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/ReadinessCheckInstallerTest.php @@ -23,7 +23,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $variables = $viewModel->getVariables(); $this->assertArrayHasKey('actionFrom', $variables); @@ -33,7 +33,7 @@ public function testIndexAction() public function testProgressAction() { $viewModel = $this->controller->progressAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $this->assertSame('/magento/setup/readiness-check/progress.phtml', $viewModel->getTemplate()); } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/ReadinessCheckUpdaterTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/ReadinessCheckUpdaterTest.php index a5f3d25e73421..c184cd2f52465 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/ReadinessCheckUpdaterTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/ReadinessCheckUpdaterTest.php @@ -23,7 +23,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $variables = $viewModel->getVariables(); $this->assertArrayHasKey('actionFrom', $variables); @@ -33,7 +33,7 @@ public function testIndexAction() public function testProgressAction() { $viewModel = $this->controller->progressAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $this->assertSame('/magento/setup/readiness-check/progress.phtml', $viewModel->getTemplate()); } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/SelectVersionTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/SelectVersionTest.php index 85e060f684d07..3d9dee65c9acc 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/SelectVersionTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/SelectVersionTest.php @@ -34,7 +34,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } @@ -50,7 +50,7 @@ public function testSystemPackageAction() ] ]); $jsonModel = $this->controller->systemPackageAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_SUCCESS, $variables['responseType']); @@ -62,7 +62,7 @@ public function testSystemPackageActionActionWithError() ->method('getPackageVersions') ->will($this->throwException(new \Exception("Test error message"))); $jsonModel = $this->controller->systemPackageAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_ERROR, $variables['responseType']); @@ -80,7 +80,7 @@ public function testInstalledSystemPackageAction() ] ]); $jsonModel = $this->controller->installedSystemPackageAction(); - $this->assertInstanceOf(\Zend\View\Model\JsonModel::class, $jsonModel); + $this->assertInstanceOf(\Laminas\View\Model\JsonModel::class, $jsonModel); $variables = $jsonModel->getVariables(); $this->assertArrayHasKey('responseType', $variables); $this->assertEquals(ResponseTypeInterface::RESPONSE_TYPE_SUCCESS, $variables['responseType']); diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/SessionTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/SessionTest.php index 216013ebfc8d9..ecef8409cb0a5 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/SessionTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/SessionTest.php @@ -21,7 +21,7 @@ class SessionTest extends \PHPUnit\Framework\TestCase private $objectManagerProvider; /** - * @var \Zend\ServiceManager\ServiceManager + * @var \Laminas\ServiceManager\ServiceManager */ private $serviceManager; @@ -33,7 +33,7 @@ public function setUp() $this->createPartialMock(\Magento\Setup\Model\ObjectManagerProvider::class, ['get']); $this->objectManager = $objectManager; $this->objectManagerProvider = $objectManagerProvider; - $this->serviceManager = $this->createPartialMock(\Zend\ServiceManager\ServiceManager::class, ['get']); + $this->serviceManager = $this->createPartialMock(\Laminas\ServiceManager\ServiceManager::class, ['get']); } /** @@ -91,7 +91,7 @@ public function testIndexAction() /** @var $controller Session */ $controller = new Session($this->serviceManager, $this->objectManagerProvider); $viewModel = $controller->unloginAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); } /** @@ -116,6 +116,6 @@ public function testProlongActionWithExistingSession() ->method('get') ->will($this->returnValue($sessionMock)); $controller = new Session($this->serviceManager, $this->objectManagerProvider); - $this->assertEquals(new \Zend\View\Model\JsonModel(['success' => true]), $controller->prolongAction()); + $this->assertEquals(new \Laminas\View\Model\JsonModel(['success' => true]), $controller->prolongAction()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/StartUpdaterTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/StartUpdaterTest.php index 7833cf113578a..7daa5fc052d5b 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/StartUpdaterTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/StartUpdaterTest.php @@ -21,17 +21,17 @@ class StartUpdaterTest extends \PHPUnit\Framework\TestCase private $controller; /** - * @var \Zend\Http\PhpEnvironment\Request|\PHPUnit_Framework_MockObject_MockObject + * @var \Laminas\Http\PhpEnvironment\Request|\PHPUnit_Framework_MockObject_MockObject */ private $request; /** - * @var \Zend\Http\PhpEnvironment\Response|\PHPUnit_Framework_MockObject_MockObject + * @var \Laminas\Http\PhpEnvironment\Response|\PHPUnit_Framework_MockObject_MockObject */ private $response; /** - * @var \Zend\Mvc\MvcEvent|\PHPUnit_Framework_MockObject_MockObject + * @var \Laminas\Mvc\MvcEvent|\PHPUnit_Framework_MockObject_MockObject */ private $mvcEvent; @@ -54,10 +54,10 @@ public function setUp() $this->updaterTaskCreator, $this->payloadValidator ); - $this->request = $this->createMock(\Zend\Http\PhpEnvironment\Request::class); - $this->response = $this->createMock(\Zend\Http\PhpEnvironment\Response::class); - $routeMatch = $this->createMock(\Zend\Mvc\Router\RouteMatch::class); - $this->mvcEvent = $this->createMock(\Zend\Mvc\MvcEvent::class); + $this->request = $this->createMock(\Laminas\Http\PhpEnvironment\Request::class); + $this->response = $this->createMock(\Laminas\Http\PhpEnvironment\Response::class); + $routeMatch = $this->createMock(\Laminas\Mvc\Router\RouteMatch::class); + $this->mvcEvent = $this->createMock(\Laminas\Mvc\MvcEvent::class); $this->mvcEvent->expects($this->any()) ->method('setRequest') ->with($this->request) @@ -77,7 +77,7 @@ public function setUp() public function testIndexAction() { $viewModel = $this->controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/SuccessTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/SuccessTest.php index 1e6b224dbe8d7..91027d76fe627 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/SuccessTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/SuccessTest.php @@ -24,7 +24,7 @@ public function testIndexAction() $controller = new Success($moduleList, $objectManagerProvider); $sampleDataState->expects($this->once())->method('hasError'); $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/SystemConfigTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/SystemConfigTest.php index 4c93ba0bfd838..29a6d1a370cb8 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/SystemConfigTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/SystemConfigTest.php @@ -18,7 +18,7 @@ public function testIndexAction() /** @var $controller SystemConfig */ $controller = new SystemConfig(); $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/UpdateExtensionGridTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/UpdateExtensionGridTest.php index 52f2c2d236541..8a5286af19a06 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/UpdateExtensionGridTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/UpdateExtensionGridTest.php @@ -8,8 +8,8 @@ use Magento\Setup\Controller\UpdateExtensionGrid; use Magento\Setup\Model\Grid\Extension; use PHPUnit_Framework_MockObject_MockObject as MockObject; -use Zend\View\Model\JsonModel; -use Zend\View\Model\ViewModel; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; /** * Class UpdateExtensionGridTest diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/UpdaterSuccessTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/UpdaterSuccessTest.php index 3c8997f96dc3d..80f8a54165001 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/UpdaterSuccessTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/UpdaterSuccessTest.php @@ -18,7 +18,7 @@ public function testIndexAction() /** @var $controller UpdaterSuccess */ $controller = new UpdaterSuccess($maintenanceMode); $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/UrlCheckTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/UrlCheckTest.php index d07e5fe53e8db..4aee55c4dbc55 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/UrlCheckTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/UrlCheckTest.php @@ -6,8 +6,8 @@ namespace Magento\Setup\Test\Unit\Controller; use Magento\Setup\Controller\UrlCheck; -use Zend\Stdlib\RequestInterface; -use Zend\View\Model\JsonModel; +use Laminas\Stdlib\RequestInterface; +use Laminas\View\Model\JsonModel; use Magento\Framework\Validator\Url as UrlValidator; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/WebConfigurationTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/WebConfigurationTest.php index 0222e86f958fe..9d921184e57e5 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/WebConfigurationTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/WebConfigurationTest.php @@ -16,7 +16,7 @@ public function testIndexAction() $controller = new WebConfiguration(); $_SERVER['DOCUMENT_ROOT'] = 'some/doc/root/value'; $viewModel = $controller->indexAction(); - $this->assertInstanceOf(\Zend\View\Model\ViewModel::class, $viewModel); + $this->assertInstanceOf(\Laminas\View\Model\ViewModel::class, $viewModel); $this->assertTrue($viewModel->terminate()); $this->assertArrayHasKey('autoBaseUrl', $viewModel->getVariables()); } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php index 1bea7b65c72ca..38eba7c4bde72 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/AdminAccountFactoryTest.php @@ -13,7 +13,7 @@ class AdminAccountFactoryTest extends \PHPUnit\Framework\TestCase public function testCreate() { $serviceLocatorMock = - $this->getMockForAbstractClass(\Zend\ServiceManager\ServiceLocatorInterface::class, ['get']); + $this->getMockForAbstractClass(\Laminas\ServiceManager\ServiceLocatorInterface::class, ['get']); $serviceLocatorMock ->expects($this->once()) ->method('get') diff --git a/setup/src/Magento/Setup/Test/Unit/Model/Cron/JobFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Model/Cron/JobFactoryTest.php index 1f8a3fea16da2..88ad666ded388 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/Cron/JobFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/Cron/JobFactoryTest.php @@ -29,7 +29,7 @@ class JobFactoryTest extends \PHPUnit\Framework\TestCase public function setUp() { $serviceManager = - $this->getMockForAbstractClass(\Zend\ServiceManager\ServiceLocatorInterface::class, [], '', false); + $this->getMockForAbstractClass(\Laminas\ServiceManager\ServiceLocatorInterface::class, [], '', false); $status = $this->createMock(\Magento\Setup\Model\Cron\Status::class); $status->expects($this->once())->method('getStatusFilePath')->willReturn('path_a'); $status->expects($this->once())->method('getLogFilePath')->willReturn('path_b'); diff --git a/setup/src/Magento/Setup/Test/Unit/Model/InstallerFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Model/InstallerFactoryTest.php index 0ef30b9ab4d6f..bf9f2072411b1 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/InstallerFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/InstallerFactoryTest.php @@ -12,7 +12,7 @@ use Magento\Setup\Model\DeclarationInstaller; use Magento\Setup\Model\InstallerFactory; use Magento\Setup\Module\ResourceFactory; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) diff --git a/setup/src/Magento/Setup/Test/Unit/Model/NavigationTest.php b/setup/src/Magento/Setup/Test/Unit/Model/NavigationTest.php index a0089e44067df..ded3ebd696a67 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/NavigationTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/NavigationTest.php @@ -11,7 +11,7 @@ class NavigationTest extends \PHPUnit\Framework\TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Zend\ServiceManager\ServiceLocatorInterface + * @var \PHPUnit_Framework_MockObject_MockObject|\Laminas\ServiceManager\ServiceLocatorInterface */ private $serviceLocatorMock; @@ -28,7 +28,7 @@ class NavigationTest extends \PHPUnit\Framework\TestCase public function setUp() { $this->serviceLocatorMock = - $this->getMockForAbstractClass(\Zend\ServiceManager\ServiceLocatorInterface::class, ['get']); + $this->getMockForAbstractClass(\Laminas\ServiceManager\ServiceLocatorInterface::class, ['get']); $this->serviceLocatorMock ->expects($this->exactly(2)) ->method('get') diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php index 552453c4a185c..1081ff3888eed 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php @@ -8,7 +8,7 @@ use Magento\Setup\Model\ObjectManagerProvider; use Magento\Setup\Model\Bootstrap; -use Zend\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceLocatorInterface; use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Magento\Framework\App\ObjectManagerFactory; use Magento\Framework\ObjectManagerInterface; diff --git a/setup/src/Magento/Setup/Test/Unit/Model/PackagesAuthTest.php b/setup/src/Magento/Setup/Test/Unit/Model/PackagesAuthTest.php index 4e7707c1dc636..222b7a6812ab8 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/PackagesAuthTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/PackagesAuthTest.php @@ -33,7 +33,7 @@ class PackagesAuthTest extends \PHPUnit\Framework\TestCase public function setUp() { - $zendServiceLocator = $this->createMock(\Zend\ServiceManager\ServiceLocatorInterface::class); + $zendServiceLocator = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); $zendServiceLocator ->expects($this->any()) ->method('get') diff --git a/setup/src/Magento/Setup/Test/Unit/Module/ConnectionFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Module/ConnectionFactoryTest.php index 63fad7d79a314..cbe84f81a0bfa 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/ConnectionFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/ConnectionFactoryTest.php @@ -18,7 +18,7 @@ class ConnectionFactoryTest extends \PHPUnit\Framework\TestCase protected function setUp() { $objectManager = new ObjectManager($this); - $serviceLocatorMock = $this->createMock(\Zend\ServiceManager\ServiceLocatorInterface::class); + $serviceLocatorMock = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); $objectManagerProviderMock = $this->createMock(\Magento\Setup\Model\ObjectManagerProvider::class); $serviceLocatorMock->expects($this->once()) ->method('get') diff --git a/setup/src/Magento/Setup/Test/Unit/Module/ResourceFactoryTest.php b/setup/src/Magento/Setup/Test/Unit/Module/ResourceFactoryTest.php index 870c929f3648c..19fdc172b5306 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/ResourceFactoryTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Module/ResourceFactoryTest.php @@ -19,7 +19,7 @@ class ResourceFactoryTest extends \PHPUnit\Framework\TestCase protected function setUp() { $serviceLocatorMock = $this->getMockForAbstractClass( - \Zend\ServiceManager\ServiceLocatorInterface::class, + \Laminas\ServiceManager\ServiceLocatorInterface::class, ['get'] ); $connectionFactory = new ConnectionFactory($serviceLocatorMock); diff --git a/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php b/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php index 79717ab555580..eb37e5452bac0 100644 --- a/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php @@ -9,7 +9,7 @@ use Magento\Framework\App\Bootstrap as AppBootstrap; use Magento\Framework\App\Filesystem\DirectoryList; -use Zend\Mvc\MvcEvent; +use Laminas\Mvc\MvcEvent; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -46,11 +46,11 @@ public function testDetach() public function testOnBootstrap() { - /** @var \Zend\Mvc\MvcEvent|\PHPUnit_Framework_MockObject_MockObject $mvcEvent */ - $mvcEvent = $this->createMock(\Zend\Mvc\MvcEvent::class); - $mvcApplication = $this->getMockBuilder(\Zend\Mvc\Application::class)->disableOriginalConstructor()->getMock(); + /** @var \Laminas\Mvc\MvcEvent|\PHPUnit_Framework_MockObject_MockObject $mvcEvent */ + $mvcEvent = $this->createMock(\Laminas\Mvc\MvcEvent::class); + $mvcApplication = $this->getMockBuilder(\Laminas\Mvc\Application::class)->disableOriginalConstructor()->getMock(); $mvcEvent->expects($this->once())->method('getApplication')->willReturn($mvcApplication); - $serviceManager = $this->createMock(\Zend\ServiceManager\ServiceManager::class); + $serviceManager = $this->createMock(\Laminas\ServiceManager\ServiceManager::class); $initParams[AppBootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS][DirectoryList::ROOT] = ['path' => '/test']; $serviceManager->expects($this->once())->method('get') ->willReturn($initParams); @@ -67,7 +67,7 @@ public function testOnBootstrap() ); $mvcApplication->expects($this->any())->method('getServiceManager')->willReturn($serviceManager); - $eventManager = $this->getMockForAbstractClass(\Zend\EventManager\EventManagerInterface::class); + $eventManager = $this->getMockForAbstractClass(\Laminas\EventManager\EventManagerInterface::class); $mvcApplication->expects($this->any())->method('getEventManager')->willReturn($eventManager); $eventManager->expects($this->any())->method('attach'); @@ -95,11 +95,11 @@ public function testCreateDirectoryListException() public function testCreateServiceNotConsole() { /** - * @var \Zend\ServiceManager\ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject $serviceLocator + * @var \Laminas\ServiceManager\ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject $serviceLocator */ - $serviceLocator = $this->createMock(\Zend\ServiceManager\ServiceLocatorInterface::class); - $mvcApplication = $this->getMockBuilder(\Zend\Mvc\Application::class)->disableOriginalConstructor()->getMock(); - $request = $this->createMock(\Zend\Stdlib\RequestInterface::class); + $serviceLocator = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); + $mvcApplication = $this->getMockBuilder(\Laminas\Mvc\Application::class)->disableOriginalConstructor()->getMock(); + $request = $this->createMock(\Laminas\Stdlib\RequestInterface::class); $mvcApplication->expects($this->any())->method('getRequest')->willReturn($request); $serviceLocator->expects($this->once())->method('get')->with('Application') ->willReturn($mvcApplication); @@ -121,11 +121,11 @@ public function testCreateService($zfAppConfig, $env, $cliParam, $expectedArray) } $listener = new InitParamListener(); /** - * @var \Zend\ServiceManager\ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject $serviceLocator + * @var \Laminas\ServiceManager\ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject $serviceLocator */ - $serviceLocator = $this->createMock(\Zend\ServiceManager\ServiceLocatorInterface::class); - $mvcApplication = $this->getMockBuilder(\Zend\Mvc\Application::class)->disableOriginalConstructor()->getMock(); - $request = $this->getMockBuilder(\Zend\Console\Request::class)->disableOriginalConstructor()->getMock(); + $serviceLocator = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); + $mvcApplication = $this->getMockBuilder(\Laminas\Mvc\Application::class)->disableOriginalConstructor()->getMock(); + $request = $this->getMockBuilder(\Laminas\Console\Request::class)->disableOriginalConstructor()->getMock(); $request->expects($this->any()) ->method('getContent') ->willReturn( @@ -230,12 +230,12 @@ private function prepareEventManager() { $this->callbacks[] = [$this->listener, 'onBootstrap']; - /** @var \Zend\EventManager\EventManagerInterface|\PHPUnit_Framework_MockObject_MockObject $events */ - $eventManager = $this->createMock(\Zend\EventManager\EventManagerInterface::class); + /** @var \Laminas\EventManager\EventManagerInterface|\PHPUnit_Framework_MockObject_MockObject $events */ + $eventManager = $this->createMock(\Laminas\EventManager\EventManagerInterface::class); - $sharedManager = $this->createMock(\Zend\EventManager\SharedEventManager::class); + $sharedManager = $this->createMock(\Laminas\EventManager\SharedEventManager::class); $sharedManager->expects($this->once())->method('attach')->with( - \Zend\Mvc\Application::class, + \Laminas\Mvc\Application::class, MvcEvent::EVENT_BOOTSTRAP, [$this->listener, 'onBootstrap'] ); @@ -252,16 +252,16 @@ private function prepareEventManager() public function testAuthPreDispatch() { $cookiePath = 'test'; - $eventMock = $this->getMockBuilder(\Zend\Mvc\MvcEvent::class) + $eventMock = $this->getMockBuilder(\Laminas\Mvc\MvcEvent::class) ->disableOriginalConstructor() ->getMock(); - $routeMatchMock = $this->getMockBuilder(\Zend\Mvc\Router\Http\RouteMatch::class) + $routeMatchMock = $this->getMockBuilder(\Laminas\Mvc\Router\Http\RouteMatch::class) ->disableOriginalConstructor() ->getMock(); - $applicationMock = $this->getMockBuilder(\Zend\Mvc\Application::class) + $applicationMock = $this->getMockBuilder(\Laminas\Mvc\Application::class) ->disableOriginalConstructor() ->getMock(); - $serviceManagerMock = $this->getMockBuilder(\Zend\ServiceManager\ServiceManager::class) + $serviceManagerMock = $this->getMockBuilder(\Laminas\ServiceManager\ServiceManager::class) ->disableOriginalConstructor() ->getMock(); $deploymentConfigMock = $this->getMockBuilder(\Magento\Framework\App\DeploymentConfig::class) @@ -295,10 +295,10 @@ public function testAuthPreDispatch() $adminSessionMock = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) ->disableOriginalConstructor() ->getMock(); - $responseMock = $this->getMockBuilder(\Zend\Http\Response::class) + $responseMock = $this->getMockBuilder(\Laminas\Http\Response::class) ->disableOriginalConstructor() ->getMock(); - $headersMock = $this->getMockBuilder(\Zend\Http\Headers::class) + $headersMock = $this->getMockBuilder(\Laminas\Http\Headers::class) ->disableOriginalConstructor() ->getMock(); @@ -433,10 +433,10 @@ public function testAuthPreDispatch() public function testAuthPreDispatchSkip() { - $eventMock = $this->getMockBuilder(\Zend\Mvc\MvcEvent::class) + $eventMock = $this->getMockBuilder(\Laminas\Mvc\MvcEvent::class) ->disableOriginalConstructor() ->getMock(); - $routeMatchMock = $this->getMockBuilder(\Zend\Mvc\Router\Http\RouteMatch::class) + $routeMatchMock = $this->getMockBuilder(\Laminas\Mvc\Router\Http\RouteMatch::class) ->disableOriginalConstructor() ->getMock(); $deploymentConfigMock = $this->getMockBuilder(\Magento\Framework\App\DeploymentConfig::class) diff --git a/setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php b/setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php index acd7e07fd13aa..b32f4970db1d8 100644 --- a/setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php +++ b/setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php @@ -1,32 +1,32 @@ Date: Fri, 17 Jan 2020 17:20:58 +0200 Subject: [PATCH 065/369] Migrate ZF2 components to Laminas components --- app/code/Magento/Email/Model/Transport.php | 16 +++++++-------- .../Test/Unit/Model/Oauth/ConsumerTest.php | 4 ++-- .../Magento/Framework/Filesystem/Glob.php | 4 ++-- .../Magento/Framework/Mail/EmailMessage.php | 20 +++++++++---------- .../Magento/Framework/Mail/MimeMessage.php | 6 +++--- .../Magento/Framework/Mail/MimePart.php | 6 +++--- .../Magento/Framework/Mail/Transport.php | 10 +++++----- .../Code/Generator/RemoteServiceGenerator.php | 2 +- .../Magento/Framework/Stdlib/Parameters.php | 11 +++++----- .../Framework/Url/Test/Unit/ValidatorTest.php | 10 +++++----- setup/src/Magento/Setup/Application.php | 6 +++--- .../Magento/Setup/Model/InstallerFactory.php | 2 +- .../Setup/Model/ObjectManagerProvider.php | 2 +- .../Magento/Setup/Module/ResourceFactory.php | 2 +- .../Setup/Mvc/Bootstrap/InitParamListener.php | 4 ++-- .../Mvc/View/Http/InjectTemplateListener.php | 4 ++-- .../Test/Unit/Model/PackagesAuthTest.php | 6 +++--- .../Mvc/Bootstrap/InitParamListenerTest.php | 2 +- 18 files changed, 58 insertions(+), 59 deletions(-) diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php index a1e25c41f3bb5..8811809207313 100644 --- a/app/code/Magento/Email/Model/Transport.php +++ b/app/code/Magento/Email/Model/Transport.php @@ -53,7 +53,7 @@ class Transport implements TransportInterface /** * @var Sendmail */ - private $zendTransport; + private $laminasTransport; /** * @var MessageInterface @@ -79,7 +79,7 @@ public function __construct( ScopeInterface::SCOPE_STORE ); - $this->zendTransport = new Sendmail($parameters); + $this->laminasTransport = new Sendmail($parameters); $this->message = $message; } @@ -89,16 +89,16 @@ public function __construct( public function sendMessage() { try { - $zendMessage = Message::fromString($this->message->getRawMessage())->setEncoding('utf-8'); + $laminasMessage = Message::fromString($this->message->getRawMessage())->setEncoding('utf-8'); if (2 === $this->isSetReturnPath && $this->returnPathValue) { - $zendMessage->setSender($this->returnPathValue); - } elseif (1 === $this->isSetReturnPath && $zendMessage->getFrom()->count()) { - $fromAddressList = $zendMessage->getFrom(); + $laminasMessage->setSender($this->returnPathValue); + } elseif (1 === $this->isSetReturnPath && $laminasMessage->getFrom()->count()) { + $fromAddressList = $laminasMessage->getFrom(); $fromAddressList->rewind(); - $zendMessage->setSender($fromAddressList->current()->getEmail()); + $laminasMessage->setSender($fromAddressList->current()->getEmail()); } - $this->zendTransport->send($zendMessage); + $this->laminasTransport->send($laminasMessage); } catch (\Exception $e) { throw new MailException(new Phrase($e->getMessage()), $e); } diff --git a/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php b/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php index 55c494e1ed2dd..13df65f05fcca 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php @@ -6,7 +6,7 @@ namespace Magento\Integration\Test\Unit\Model\Oauth; use Magento\Framework\Url\Validator as UrlValidator; -use Laminas\Validator\Uri as ZendUriValidator; +use Laminas\Validator\Uri as LaminasUriValidator; use Magento\Integration\Model\Oauth\Consumer\Validator\KeyLength; /** @@ -85,7 +85,7 @@ protected function setUp() $this->keyLengthValidator = new KeyLength(); - $this->urlValidator = new UrlValidator(new ZendUriValidator()); + $this->urlValidator = new UrlValidator(new LaminasUriValidator()); $this->oauthDataMock = $this->createPartialMock( \Magento\Integration\Helper\Oauth\Data::class, diff --git a/lib/internal/Magento/Framework/Filesystem/Glob.php b/lib/internal/Magento/Framework/Filesystem/Glob.php index a73fa1a371da6..e7f049dfdf80d 100644 --- a/lib/internal/Magento/Framework/Filesystem/Glob.php +++ b/lib/internal/Magento/Framework/Filesystem/Glob.php @@ -6,7 +6,7 @@ namespace Magento\Framework\Filesystem; use Laminas\Stdlib\Glob as LaminasGlob; -use Laminas\Stdlib\Exception\RuntimeException as ZendRuntimeException; +use Laminas\Stdlib\Exception\RuntimeException as LaminasRuntimeException; /** * Wrapper for Laminas\Stdlib\Glob @@ -25,7 +25,7 @@ public static function glob($pattern, $flags = 0, $forceFallback = false) { try { $result = LaminasGlob::glob($pattern, $flags, $forceFallback); - } catch (ZendRuntimeException $e) { + } catch (LaminasRuntimeException $e) { $result = []; } return $result; diff --git a/lib/internal/Magento/Framework/Mail/EmailMessage.php b/lib/internal/Magento/Framework/Mail/EmailMessage.php index 0b8a1da8a4b85..ccb8a77227a27 100644 --- a/lib/internal/Magento/Framework/Mail/EmailMessage.php +++ b/lib/internal/Magento/Framework/Mail/EmailMessage.php @@ -8,9 +8,9 @@ namespace Magento\Framework\Mail; use Magento\Framework\Mail\Exception\InvalidArgumentException; -use Laminas\Mail\Address as ZendAddress; +use Laminas\Mail\Address as LaminasAddress; use Laminas\Mail\AddressList; -use Laminas\Mime\Message as ZendMimeMessage; +use Laminas\Mime\Message as LaminasMimeMessage; /** * Email message @@ -61,7 +61,7 @@ public function __construct( ?string $encoding = 'utf-8' ) { parent::__construct($encoding); - $mimeMessage = new ZendMimeMessage(); + $mimeMessage = new LaminasMimeMessage(); $mimeMessage->setParts($body->getParts()); $this->zendMessage->setBody($mimeMessage); if ($subject) { @@ -153,15 +153,15 @@ public function getReplyTo(): ?array */ public function getSender(): ?Address { - /** @var ZendAddress $zendSender */ - if (!$zendSender = $this->zendMessage->getSender()) { + /** @var LaminasAddress $laminasSender */ + if (!$laminasSender = $this->zendMessage->getSender()) { return null; } return $this->addressFactory->create( [ - 'email' => $zendSender->getEmail(), - 'name' => $zendSender->getName() + 'email' => $laminasSender->getEmail(), + 'name' => $laminasSender->getName() ] ); } @@ -222,11 +222,11 @@ private function convertAddressListToAddressArray(AddressList $addressList): arr */ private function convertAddressArrayToAddressList(array $arrayList): AddressList { - $zendAddressList = new AddressList(); + $laminasAddressList = new AddressList(); foreach ($arrayList as $address) { - $zendAddressList->add($address->getEmail(), $address->getName()); + $laminasAddressList->add($address->getEmail(), $address->getName()); } - return $zendAddressList; + return $laminasAddressList; } } diff --git a/lib/internal/Magento/Framework/Mail/MimeMessage.php b/lib/internal/Magento/Framework/Mail/MimeMessage.php index 6c293fd957e35..78d2a42637ff2 100644 --- a/lib/internal/Magento/Framework/Mail/MimeMessage.php +++ b/lib/internal/Magento/Framework/Mail/MimeMessage.php @@ -7,7 +7,7 @@ namespace Magento\Framework\Mail; -use Laminas\Mime\Message as ZendMimeMessage; +use Laminas\Mime\Message as LaminasMimeMessage; /** * Class MimeMessage @@ -15,7 +15,7 @@ class MimeMessage implements MimeMessageInterface { /** - * @var ZendMimeMessage + * @var LaminasMimeMessage */ private $mimeMessage; @@ -26,7 +26,7 @@ class MimeMessage implements MimeMessageInterface */ public function __construct(array $parts) { - $this->mimeMessage = new ZendMimeMessage(); + $this->mimeMessage = new LaminasMimeMessage(); $this->mimeMessage->setParts($parts); } diff --git a/lib/internal/Magento/Framework/Mail/MimePart.php b/lib/internal/Magento/Framework/Mail/MimePart.php index 7cb0e9fbf0097..d02ebffd5dc7b 100644 --- a/lib/internal/Magento/Framework/Mail/MimePart.php +++ b/lib/internal/Magento/Framework/Mail/MimePart.php @@ -8,7 +8,7 @@ namespace Magento\Framework\Mail; use Magento\Framework\Mail\Exception\InvalidArgumentException; -use Laminas\Mime\Part as ZendMimePart; +use Laminas\Mime\Part as LaminasMimePart; /** * @inheritDoc @@ -21,7 +21,7 @@ class MimePart implements MimePartInterface public const CHARSET_UTF8 = 'utf-8'; /** - * @var ZendMimePart + * @var LaminasMimePart */ private $mimePart; @@ -61,7 +61,7 @@ public function __construct( ?bool $isStream = null ) { try { - $this->mimePart = new ZendMimePart($content); + $this->mimePart = new LaminasMimePart($content); } catch (\Exception $e) { throw new InvalidArgumentException($e->getMessage()); } diff --git a/lib/internal/Magento/Framework/Mail/Transport.php b/lib/internal/Magento/Framework/Mail/Transport.php index 44e8242c965a1..0be387f22ac08 100644 --- a/lib/internal/Magento/Framework/Mail/Transport.php +++ b/lib/internal/Magento/Framework/Mail/Transport.php @@ -7,7 +7,7 @@ use Magento\Framework\Exception\MailException; use Magento\Framework\Phrase; -use Laminas\Mail\Message as ZendMessage; +use Laminas\Mail\Message as LaminasMessage; use Laminas\Mail\Transport\Sendmail; class Transport implements \Magento\Framework\Mail\TransportInterface @@ -15,7 +15,7 @@ class Transport implements \Magento\Framework\Mail\TransportInterface /** * @var Sendmail */ - private $zendTransport; + private $laminasTransport; /** * @var MessageInterface @@ -28,7 +28,7 @@ class Transport implements \Magento\Framework\Mail\TransportInterface */ public function __construct(MessageInterface $message, $parameters = null) { - $this->zendTransport = new Sendmail($parameters); + $this->laminasTransport = new Sendmail($parameters); $this->message = $message; } @@ -38,8 +38,8 @@ public function __construct(MessageInterface $message, $parameters = null) public function sendMessage() { try { - $this->zendTransport->send( - ZendMessage::fromString($this->message->getRawMessage()) + $this->laminasTransport->send( + LaminasMessage::fromString($this->message->getRawMessage()) ); } catch (\Exception $e) { throw new MailException(new Phrase($e->getMessage()), $e); diff --git a/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php b/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php index 74053d58f0503..22d93b2cc7dcf 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php @@ -126,7 +126,7 @@ protected function _getClassMethods() $methods = [$this->_getDefaultConstructorDefinition()]; $interfaceMethodsMap = $this->serviceMethodsMap->getMethodsMap($this->getSourceClassName()); foreach (array_keys($interfaceMethodsMap) as $methodName) { - // Uses Zend Reflection instead MethodsMap service, because second does not support features of PHP 7.x + // Uses Laminas Reflection instead MethodsMap service, because second does not support features of PHP 7.x $methodReflection = new MethodReflection($this->getSourceClassName(), $methodName); $sourceMethodParameters = $methodReflection->getParameters(); $methodParameters = []; diff --git a/lib/internal/Magento/Framework/Stdlib/Parameters.php b/lib/internal/Magento/Framework/Stdlib/Parameters.php index e5743355527c8..98bb7aa945226 100644 --- a/lib/internal/Magento/Framework/Stdlib/Parameters.php +++ b/lib/internal/Magento/Framework/Stdlib/Parameters.php @@ -7,7 +7,7 @@ namespace Magento\Framework\Stdlib; -use Laminas\Stdlib\Parameters as ZendParameters; +use Laminas\Stdlib\Parameters as LaminasParameters; /** * Class Parameters @@ -15,16 +15,15 @@ class Parameters { /** - * @var ZendParameters + * @var LaminasParameters */ private $parameters; /** - * @param ZendParameters $parameters + * @param LaminasParameters $parameters */ - public function __construct( - ZendParameters $parameters - ) { + public function __construct(LaminasParameters $parameters) + { $this->parameters = $parameters; } diff --git a/lib/internal/Magento/Framework/Url/Test/Unit/ValidatorTest.php b/lib/internal/Magento/Framework/Url/Test/Unit/ValidatorTest.php index 171430df906fe..fbdfec82a58dd 100644 --- a/lib/internal/Magento/Framework/Url/Test/Unit/ValidatorTest.php +++ b/lib/internal/Magento/Framework/Url/Test/Unit/ValidatorTest.php @@ -13,7 +13,7 @@ class ValidatorTest extends \PHPUnit\Framework\TestCase protected $object; /** @var \Laminas\Validator\Uri */ - protected $zendValidator; + protected $laminasValidator; /** @var string[] */ protected $expectedValidationMessages = ['invalidUrl' => "Invalid URL '%value%'."]; @@ -22,10 +22,10 @@ protected function setUp() { $objectManager = new ObjectManager($this); - $this->zendValidator = $this->createMock(\Laminas\Validator\Uri::class); + $this->laminasValidator = $this->createMock(\Laminas\Validator\Uri::class); $this->object = $objectManager->getObject( \Magento\Framework\Url\Validator::class, - ['validator' => $this->zendValidator] + ['validator' => $this->laminasValidator] ); } @@ -36,7 +36,7 @@ public function testConstruct() public function testIsValidWhenValid() { - $this->zendValidator + $this->laminasValidator ->method('isValid') ->with('http://example.com') ->willReturn(true); @@ -47,7 +47,7 @@ public function testIsValidWhenValid() public function testIsValidWhenInvalid() { - $this->zendValidator + $this->laminasValidator ->method('isValid') ->with('%value%') ->willReturn(false); diff --git a/setup/src/Magento/Setup/Application.php b/setup/src/Magento/Setup/Application.php index 2c34cf00aa66c..5a729dc03e97e 100644 --- a/setup/src/Magento/Setup/Application.php +++ b/setup/src/Magento/Setup/Application.php @@ -5,7 +5,7 @@ */ namespace Magento\Setup; -use Laminas\Mvc\Application as ZendApplication; +use Laminas\Mvc\Application as LaminasApplication; use Laminas\Mvc\Service\ServiceManagerConfig; use Laminas\ServiceManager\ServiceManager; @@ -21,7 +21,7 @@ class Application * Magento specific services. * * @param array $configuration - * @return ZendApplication + * @return LaminasApplication */ public function bootstrap(array $configuration) { @@ -40,7 +40,7 @@ public function bootstrap(array $configuration) } $listeners = $this->getListeners($serviceManager, $configuration); - $application = new ZendApplication( + $application = new LaminasApplication( $configuration, $serviceManager, $serviceManager->get('EventManager'), diff --git a/setup/src/Magento/Setup/Model/InstallerFactory.php b/setup/src/Magento/Setup/Model/InstallerFactory.php index aaa33bd1a5ae4..24634be6beba8 100644 --- a/setup/src/Magento/Setup/Model/InstallerFactory.php +++ b/setup/src/Magento/Setup/Model/InstallerFactory.php @@ -17,7 +17,7 @@ class InstallerFactory { /** - * Zend Framework's service locator + * Laminas Framework's service locator * * @var ServiceLocatorInterface */ diff --git a/setup/src/Magento/Setup/Model/ObjectManagerProvider.php b/setup/src/Magento/Setup/Model/ObjectManagerProvider.php index d89976552ab33..022f89f2eea78 100644 --- a/setup/src/Magento/Setup/Model/ObjectManagerProvider.php +++ b/setup/src/Magento/Setup/Model/ObjectManagerProvider.php @@ -15,7 +15,7 @@ /** * Object manager provider * - * Links Zend Framework's service locator and Magento object manager. + * Links Laminas Framework's service locator and Magento object manager. * Guaranties single object manager per application run. * Hides complexity of creating Magento object manager */ diff --git a/setup/src/Magento/Setup/Module/ResourceFactory.php b/setup/src/Magento/Setup/Module/ResourceFactory.php index e29e6a8726c4b..947afda59dc34 100644 --- a/setup/src/Magento/Setup/Module/ResourceFactory.php +++ b/setup/src/Magento/Setup/Module/ResourceFactory.php @@ -12,7 +12,7 @@ class ResourceFactory { /** - * Zend Framework's service locator + * Laminas Framework's service locator * * @var ServiceLocatorInterface */ diff --git a/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php b/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php index f57fdd4c66962..dc564c3a8f7c5 100644 --- a/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php +++ b/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php @@ -54,8 +54,8 @@ class InitParamListener implements ListenerAggregateInterface, FactoryInterface /** * @inheritdoc * - * The $priority argument is added to support latest versions of Zend Event Manager. - * Starting from Zend Event Manager 3.0.0 release the ListenerAggregateInterface::attach() + * The $priority argument is added to support latest versions of Laminas Event Manager. + * Starting from Laminas Event Manager 3.0.0 release the ListenerAggregateInterface::attach() * supports the `priority` argument. * * @param EventManagerInterface $events diff --git a/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php b/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php index 2b4b2af1bc84f..1f230ddbaefa5 100644 --- a/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php +++ b/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php @@ -7,9 +7,9 @@ namespace Magento\Setup\Mvc\View\Http; use Laminas\Mvc\MvcEvent; -use Laminas\Mvc\View\Http\InjectTemplateListener as ZendInjectTemplateListener; +use Laminas\Mvc\View\Http\InjectTemplateListener as LaminasInjectTemplateListener; -class InjectTemplateListener extends ZendInjectTemplateListener +class InjectTemplateListener extends LaminasInjectTemplateListener { /** * Determine the top-level namespace of the controller diff --git a/setup/src/Magento/Setup/Test/Unit/Model/PackagesAuthTest.php b/setup/src/Magento/Setup/Test/Unit/Model/PackagesAuthTest.php index 222b7a6812ab8..8f0f9d830da43 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/PackagesAuthTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/PackagesAuthTest.php @@ -33,8 +33,8 @@ class PackagesAuthTest extends \PHPUnit\Framework\TestCase public function setUp() { - $zendServiceLocator = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); - $zendServiceLocator + $laminasServiceLocator = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); + $laminasServiceLocator ->expects($this->any()) ->method('get') ->with('config') @@ -55,7 +55,7 @@ function ($serializedData) { } ); $this->packagesAuth = new PackagesAuth( - $zendServiceLocator, + $laminasServiceLocator, $this->curl, $this->filesystem, $this->serializerMock diff --git a/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php b/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php index eb37e5452bac0..b063186c26fca 100644 --- a/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php @@ -107,7 +107,7 @@ public function testCreateServiceNotConsole() } /** - * @param array $zfAppConfig Data that comes from Zend Framework Application config + * @param array $zfAppConfig Data that comes from Laminas Framework Application config * @param array $env Config that comes from SetEnv * @param string $cliParam Parameter string * @param array $expectedArray Expected result array From e9300b790b9a21c3d217e56665ac1553d949adf6 Mon Sep 17 00:00:00 2001 From: "Galla, Daniel" Date: Thu, 23 Jan 2020 09:52:41 +0100 Subject: [PATCH 066/369] #26499 Always transliterate product url key --- .../CatalogUrlRewrite/Model/ProductUrlPathGenerator.php | 2 +- .../Test/Unit/Model/ProductUrlPathGeneratorTest.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php index ac3a5092bb3bf..a5553535b390a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php @@ -150,7 +150,7 @@ protected function prepareProductUrlKey(Product $product) $urlKey = (string)$product->getUrlKey(); $urlKey = trim(strtolower($urlKey)); - return $urlKey ?: $product->formatUrlKey($product->getName()); + return $product->formatUrlKey($urlKey ?: $product->getName()); } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php index 5076577447af3..233d0703448ca 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php @@ -80,8 +80,8 @@ protected function setUp(): void public function getUrlPathDataProvider(): array { return [ - 'path based on url key uppercase' => ['Url-Key', null, 0, 'url-key'], - 'path based on url key' => ['url-key', null, 0, 'url-key'], + 'path based on url key uppercase' => ['Url-Key', null, 1, 'url-key'], + 'path based on url key' => ['url-key', null, 1, 'url-key'], 'path based on product name 1' => ['', 'product-name', 1, 'product-name'], 'path based on product name 2' => [null, 'product-name', 1, 'product-name'], 'path based on product name 3' => [false, 'product-name', 1, 'product-name'] From 13890db4775b4ee6b2eba44d9f3c4a8387e17ed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tjitse=20Efd=C3=A9?= Date: Sun, 26 Jan 2020 14:55:50 +0100 Subject: [PATCH 067/369] Refactor DateTime constructor --- .../Framework/Stdlib/DateTime/DateTime.php | 16 ++++------------ .../Test/Unit/DateTime/DateTimeTest.php | 15 +++++++++++++++ ...-55b4-4669-a60b-76127f9e1c48-testsuite.xml | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/var/allure-results/618530c1-55b4-4669-a60b-76127f9e1c48-testsuite.xml diff --git a/lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php b/lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php index 7ac93c7b94305..fdb6dd5c816dc 100644 --- a/lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php +++ b/lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php @@ -13,13 +13,6 @@ */ class DateTime { - /** - * Current config offset in seconds - * - * @var int - */ - private $_offset = 0; - /** * @var TimezoneInterface */ @@ -31,7 +24,6 @@ class DateTime public function __construct(TimezoneInterface $localeDate) { $this->_localeDate = $localeDate; - $this->_offset = $this->calculateOffset($this->_localeDate->getConfigTimezone()); } /** @@ -157,18 +149,18 @@ public function timestamp($input = null) */ public function getGmtOffset($type = 'seconds') { - $result = $this->_offset; + $offset = $this->calculateOffset($this->_localeDate->getConfigTimezone()); switch ($type) { case 'seconds': default: break; case 'minutes': - $result = $result / 60; + $offset = $offset / 60; break; case 'hours': - $result = $result / 60 / 60; + $offset = $offset / 60 / 60; break; } - return $result; + return $offset; } } diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeTest.php b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeTest.php index f86269b1647b2..3c7f49671d74a 100644 --- a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeTest.php +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeTest.php @@ -46,6 +46,21 @@ public function testTimestamp($input) $this->assertEquals($expected, (new DateTime($timezone))->timestamp($input)); } + public function testGtmOffset() + { + /** @var TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject $timezone */ + $timezone = $this->getMockBuilder(TimezoneInterface::class)->getMock(); + $timezone->method('getConfigTimezone')->willReturn('Europe/Amsterdam'); + + /** @var DateTime|\PHPUnit_Framework_MockObject_MockObject $dateTime */ + $dateTime = $this->getMockBuilder(DateTime::class) + ->setConstructorArgs([$timezone]) + ->setMethods(null) + ->getMock(); + + $this->assertEquals(3600, $dateTime->getGmtOffset()); + } + /** * @return array */ diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/var/allure-results/618530c1-55b4-4669-a60b-76127f9e1c48-testsuite.xml b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/var/allure-results/618530c1-55b4-4669-a60b-76127f9e1c48-testsuite.xml new file mode 100644 index 0000000000000..91077b15aeb69 --- /dev/null +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/var/allure-results/618530c1-55b4-4669-a60b-76127f9e1c48-testsuite.xml @@ -0,0 +1,19 @@ + + + Magento\Framework\Stdlib\Test\Unit\DateTime\DateTimeTest + + + testGtmOffset + + Too few arguments to function PHPUnit\Framework\TestCase::getMockBuilder(), 0 passed in /Users/tjitse/sites/magentolive/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeTest.php on line 56 and exactly 1 expected + #0 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/Framework/TestCase.php(894): PHPUnit\Framework\TestResult->run(Object(Magento\Framework\Stdlib\Test\Unit\DateTime\DateTimeTest)) +#1 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/Framework/TestSuite.php(755): PHPUnit\Framework\TestCase->run(Object(PHPUnit\Framework\TestResult)) +#2 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(545): PHPUnit\Framework\TestSuite->run(Object(PHPUnit\Framework\TestResult)) +#3 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/TextUI/Command.php(195): PHPUnit\TextUI\TestRunner->doRun(Object(PHPUnit\Framework\TestSuite), Array, true) +#4 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/TextUI/Command.php(148): PHPUnit\TextUI\Command->run(Array, true) +#5 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/phpunit(53): PHPUnit\TextUI\Command::main() +#6 {main} + + + + From 628991634eb5ebd6e3a49b798145795b071f9ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tjitse=20Efd=C3=A9?= Date: Sun, 26 Jan 2020 14:57:20 +0100 Subject: [PATCH 068/369] Remove accidentally commited testsuite file --- ...-55b4-4669-a60b-76127f9e1c48-testsuite.xml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/var/allure-results/618530c1-55b4-4669-a60b-76127f9e1c48-testsuite.xml diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/var/allure-results/618530c1-55b4-4669-a60b-76127f9e1c48-testsuite.xml b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/var/allure-results/618530c1-55b4-4669-a60b-76127f9e1c48-testsuite.xml deleted file mode 100644 index 91077b15aeb69..0000000000000 --- a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/var/allure-results/618530c1-55b4-4669-a60b-76127f9e1c48-testsuite.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - Magento\Framework\Stdlib\Test\Unit\DateTime\DateTimeTest - - - testGtmOffset - - Too few arguments to function PHPUnit\Framework\TestCase::getMockBuilder(), 0 passed in /Users/tjitse/sites/magentolive/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/DateTimeTest.php on line 56 and exactly 1 expected - #0 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/Framework/TestCase.php(894): PHPUnit\Framework\TestResult->run(Object(Magento\Framework\Stdlib\Test\Unit\DateTime\DateTimeTest)) -#1 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/Framework/TestSuite.php(755): PHPUnit\Framework\TestCase->run(Object(PHPUnit\Framework\TestResult)) -#2 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/TextUI/TestRunner.php(545): PHPUnit\Framework\TestSuite->run(Object(PHPUnit\Framework\TestResult)) -#3 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/TextUI/Command.php(195): PHPUnit\TextUI\TestRunner->doRun(Object(PHPUnit\Framework\TestSuite), Array, true) -#4 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/src/TextUI/Command.php(148): PHPUnit\TextUI\Command->run(Array, true) -#5 /Users/tjitse/sites/magentolive/vendor/phpunit/phpunit/phpunit(53): PHPUnit\TextUI\Command::main() -#6 {main} - - - - From 6cf9ebcad0a4f1747a2c9a8e56504b278f0dd863 Mon Sep 17 00:00:00 2001 From: Alexander Steshuk Date: Tue, 28 Jan 2020 16:50:48 +0200 Subject: [PATCH 069/369] magento/magento2#25733: MFTF test added. --- .../AdminMassDeleteWidgetActionGroup.xml | 49 +++++++++++++++++ .../Widget/Test/Mftf/Data/WidgetData.xml | 5 ++ .../Test/Mftf/Section/AdminWidgetsSection.xml | 12 +++- .../AdminContentWidgetsMassDeletesTest.xml | 55 +++++++++++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminMassDeleteWidgetActionGroup.xml create mode 100644 app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsMassDeletesTest.xml diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminMassDeleteWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminMassDeleteWidgetActionGroup.xml new file mode 100644 index 0000000000000..e93f99fd475fd --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminMassDeleteWidgetActionGroup.xml @@ -0,0 +1,49 @@ + + + + + + + Goes to the Admin Widgets list page. Mass delete widgets. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml b/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml index 4c6e98aafd765..9beb5d1da1b1c 100644 --- a/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml +++ b/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml @@ -11,9 +11,14 @@ CMS Static Block Magento Luma + Magento Luma testName All Store Views + + All Store Views + All Pages Page Top + All Pages diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml index 162f4e9c41064..ad9db0dfb4af1 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml @@ -11,7 +11,17 @@
- + + + + + + + + + + +
diff --git a/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsMassDeletesTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsMassDeletesTest.xml new file mode 100644 index 0000000000000..073cdabf37698 --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/Test/AdminContentWidgetsMassDeletesTest.xml @@ -0,0 +1,55 @@ + + + + + + + + + + <description value="Admin select widgets in grid and try to mass delete action, will show pop-up with confirm action"/> + <severity value="MAJOR"/> + <group value="widget"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentWidgetsPageFirst"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentElementsWidgets.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitleFirst"> + <argument name="title" value="{{AdminMenuContentElementsWidgets.pageTitle}}"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> + <actionGroup ref="AdminCreateAndSaveWidgetActionGroup" stepKey="addWidgetForTest1"> + <argument name="widget" value="ProductsListWidget"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear2"/> + <actionGroup ref="AdminCreateAndSaveWidgetActionGroup" stepKey="addWidgetForTest2"> + <argument name="widget" value="ProductsListWidget"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear4"/> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToContentWidgetsPageSecond"> + <argument name="menuUiId" value="{{AdminMenuContent.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuContentElementsWidgets.dataUiId}}"/> + </actionGroup> + <actionGroup ref="AdminAssertPageTitleActionGroup" stepKey="seePageTitleSecond"> + <argument name="title" value="{{AdminMenuContentElementsWidgets.pageTitle}}"/> + </actionGroup> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <actionGroup ref="AdminMassDeleteWidgetActionGroup" stepKey="massWidgetDelete"> + <argument name="row" value="1"/> + </actionGroup> + </test> +</tests> + From d64082feabdc8ab356314a180ab1d3678c7d2973 Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Tue, 28 Jan 2020 13:19:30 +0200 Subject: [PATCH 070/369] magento/magento2#: Test Coverage. API functional tests. removeItemFromCart --- .../Quote/Customer/RemoveItemFromCartTest.php | 34 +++++++++++++++++++ .../Quote/Guest/RemoveItemFromCartTest.php | 32 +++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php index c93db424834ef..b1a950bd76ca4 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php @@ -155,6 +155,40 @@ public function testRemoveItemFromAnotherCustomerCart() $this->graphQlMutation($query, [], '', $this->getHeaderMap('customer2@search.example.com')); } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveItemWithEmptyCartId() + { + $cartId = ""; + $cartItemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); + + $this->expectExceptionMessage("Required parameter \"cart_id\" is missing."); + + $query = $this->getQuery($cartId, $cartItemId); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveItemWithZeroCartItemId() + { + $cartId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $cartItemId = 0; + + $this->expectExceptionMessage("Required parameter \"cart_item_id\" is missing."); + + $query = $this->getQuery($cartId, $cartItemId); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + } + /** * @param string $maskedQuoteId * @param int $itemId diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php index 6f105259bf65c..931819af781d5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php @@ -119,6 +119,38 @@ public function testRemoveItemFromCustomerCart() $this->graphQlMutation($query); } + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveItemWithEmptyCartId() + { + $cartId = ""; + $cartItemId = $this->getQuoteItemIdByReservedQuoteIdAndSku->execute('test_quote', 'simple_product'); + + $this->expectExceptionMessage("Required parameter \"cart_id\" is missing."); + + $query = $this->getQuery($cartId, $cartItemId); + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testRemoveItemWithZeroCartItemId() + { + $cartId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $cartItemId = 0; + + $this->expectExceptionMessage("Required parameter \"cart_item_id\" is missing."); + + $query = $this->getQuery($cartId, $cartItemId); + $this->graphQlMutation($query); + } + /** * @param string $maskedQuoteId * @param int $itemId From 2685e4396b2d891556def6a50d6fd345cd725d3a Mon Sep 17 00:00:00 2001 From: Alexander Steshuk <grp-engcom-vendorworker-Kilo@adobe.com> Date: Wed, 29 Jan 2020 13:42:49 +0200 Subject: [PATCH 071/369] magento/magento2#25733: MFTF test updated. --- .../Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml index ad9db0dfb4af1..f6ca1b25a90f2 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml @@ -11,6 +11,7 @@ <section name="AdminWidgetsSection"> <element name="widgetTitleSearch" type="input" selector="#widgetInstanceGrid_filter_title"/> <element name="searchButton" type="button" selector=".action-default.scalable.action-secondary"/> + <element name="searchResult" type="text" selector="#widgetInstanceGrid_table>tbody>tr:nth-child(1)"/> <element name="resetFilter" type="button" selector="button[data-action='grid-filter-reset']"/> <element name="massActionSelect" type="select" selector="#widgetInstanceGrid_massaction-mass-select"/> <element name="massActionSelectOptionAll" type="select" selector="//*[@id='widgetInstanceGrid_massaction-mass-select']//option[@value='selectAll']"/> From d702078c9b8dc3dc996d53aabad76ee96c107d65 Mon Sep 17 00:00:00 2001 From: Prince Antil <prince.antil@cueblocks.com> Date: Sun, 2 Feb 2020 13:53:54 +0530 Subject: [PATCH 072/369] MAG-251090-26590: Fixed Customer registration multiple form submit --- .../Customer/view/frontend/templates/form/register.phtml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/code/Magento/Customer/view/frontend/templates/form/register.phtml b/app/code/Magento/Customer/view/frontend/templates/form/register.phtml index da0bb6e4cbc8b..0defee8b22fe3 100644 --- a/app/code/Magento/Customer/view/frontend/templates/form/register.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/form/register.phtml @@ -198,6 +198,12 @@ require([ ignore: ignore ? ':hidden:not(' + ignore + ')' : ':hidden' <?php endif ?> }).find('input:text').attr('autocomplete', 'off'); + dataForm.submit(function () { + $(this).find(':submit').attr('disabled', 'disabled'); + }); + dataForm.bind("invalid-form.validate", function () { + $(this).find(':submit').prop('disabled', false); + }); }); </script> From 3a5cc24f5e1537b4da4abb63ecbdd01c7c29865e Mon Sep 17 00:00:00 2001 From: Tejash Kumbhare <tejas@wagento.com> Date: Sun, 2 Feb 2020 13:57:51 +0530 Subject: [PATCH 073/369] table bottom color different then thead and tbody border color --- .../Magento/luma/Magento_Customer/web/css/source/_module.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less index 6354cc35d32ed..34a2dbfeca472 100644 --- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less @@ -8,7 +8,7 @@ // _____________________________________________ @account-title-border-color: @color-gray-middle2; -@account-table-border-bottom-color: @color-gray-middle1; +@account-table-border-bottom-color: @color-gray_light; @account-table-action-delete: @color-red12; @_password-default: @color-gray-light01; From b1b3e6ce6ddef57a8d6e59cc678160670f8b9e76 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Tue, 4 Feb 2020 14:08:07 +0200 Subject: [PATCH 074/369] Cover MFTF test --- .../Customer/Test/Mftf/Data/CustomerData.xml | 12 ++++ ...formationWithGeneratedEmailActionGroup.xml | 19 ++++++ .../Sales/Test/Mftf/Data/ConfigData.xml | 8 +++ ...reateOrderWithCustomerWithoutEmailTest.xml | 61 +++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationWithGeneratedEmailActionGroup.xml create mode 100644 app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index b9227505871cf..d75afe0388193 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -178,6 +178,18 @@ <data key="website_id">0</data> <requiredEntity type="address">US_Address_CA</requiredEntity> </entity> + <entity name="Simple_US_Customer_CA_Without_Email" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_CA</requiredEntity> + </entity> <entity name="Simple_US_Customer_For_Update" type="customer"> <var key="id" entityKey="id" entityType="customer"/> <data key="firstname">Jane</data> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationWithGeneratedEmailActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationWithGeneratedEmailActionGroup.xml new file mode 100644 index 0000000000000..9b1666763b2bc --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/VerifyCreatedOrderInformationWithGeneratedEmailActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="VerifyCreatedOrderInformationWithGeneratedEmailActionGroup"> + <annotations> + <description>Validate customer email on order page. Starts on order page.</description> + </annotations> + <arguments> + <argument name="email" type="string" defaultValue="{{CustomerEntityOne.email}}"/> + </arguments> + <see selector="{{AdminOrderDetailsInformationSection.customerEmail}}" userInput="{{email}}" stepKey="seeGeneratedEmail"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Data/ConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/ConfigData.xml index 730bebc047f93..25e25006d040a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Data/ConfigData.xml +++ b/app/code/Magento/Sales/Test/Mftf/Data/ConfigData.xml @@ -20,4 +20,12 @@ <data key="label">No</data> <data key="value">0</data> </entity> + <entity name="DisableEmailRequiredForOrder"> + <data key="path">customer/create_account/email_required_create_order</data> + <data key="value">0</data> + </entity> + <entity name="EnableEmailRequiredForOrder"> + <data key="path">customer/create_account/email_required_create_order</data> + <data key="value">1</data> + </entity> </entities> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml new file mode 100644 index 0000000000000..bb1f5840ed6bf --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithCustomerWithoutEmailTest.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateOrderWithCustomerWithoutEmailTest"> + <annotations> + <title value="Admin Create Order"/> + <stories value="Admin create order with customer without email."/> + <description value="Verify, admin able to create order with customer without email."/> + <severity value="MINOR"/> + <group value="Sales"/> + </annotations> + <before> + <!--Disable required 'email' field on create order page.--> + <magentoCLI command="config:set {{DisableEmailRequiredForOrder.path}} {{DisableEmailRequiredForOrder.value}}" stepKey="disableRequiredFieldEmailForAdminOrderCreation"/> + <!--Create test data.--> + <createData entity="_defaultCategory" stepKey="category"/> + <createData entity="SimpleProduct" stepKey="simpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <!--Clean up created test data.--> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <!--Enable required 'email' field on create order page.--> + <magentoCLI command="config:set {{EnableEmailRequiredForOrder.path}} {{EnableEmailRequiredForOrder.value}}" stepKey="enableRequiredFieldEmailForAdminOrderCreation"/> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + + <!--Create order.--> + <actionGroup ref="NavigateToNewOrderPageNewCustomerActionGroup" stepKey="navigateToNewOrderPageNewCustomerActionGroup" /> + <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addSimpleProductToOrder"> + <argument name="product" value="$$simpleProduct$$"/> + <argument name="productQty" value="{{SimpleProduct.quantity}}"/> + </actionGroup> + <!--Fill customer address without 'email'--> + <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerInformation"> + <argument name="customer" value="Simple_US_Customer_CA_Without_Email"/> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="orderSelectFlatRateShippingMethod"/> + <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> + <!--Verify, 'email' is generated.--> + <actionGroup ref="VerifyCreatedOrderInformationWithGeneratedEmailActionGroup" stepKey="verifyCustomerEmail"> + <argument name="email" value="@example.com"/> + </actionGroup> + <grabTextFrom selector="{{AdminOrderDetailsInformationSection.customerEmail}}" stepKey="generatedCustomerEmail"/> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="$generatedCustomerEmail"/> + </actionGroup> + </test> +</tests> From 6633cc22aa266a0399a60d6a7b94f8d8841004be Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Tue, 4 Feb 2020 15:27:52 +0200 Subject: [PATCH 075/369] Covered unit test --- .../Lock/Test/Unit/Backend/CacheTest.php | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php diff --git a/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php new file mode 100644 index 0000000000000..2ec812092d654 --- /dev/null +++ b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/CacheTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Lock\Test\Unit\Backend; + +use Magento\Framework\Cache\FrontendInterface; +use Magento\Framework\Lock\Backend\Cache; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CacheTest extends TestCase +{ + const LOCK_PREFIX = 'LOCKED_RECORD_INFO_'; + + /** + * @var FrontendInterface|MockObject + */ + private $frontendCacheMock; + + /** + * @var Cache + */ + private $cache; + + /** + * @inheritDoc + */ + public function setUp() + { + $this->frontendCacheMock = $this->createMock(FrontendInterface::class); + + $objectManager = new ObjectManagerHelper($this); + + $this->cache = $objectManager->getObject( + Cache::class, + [ + 'cache' => $this->frontendCacheMock + ] + ); + } + + /** + * Verify released a lock. + * + * @return void + */ + public function testUnlock(): void + { + $identifier = 'lock_name'; + + $this->frontendCacheMock + ->expects($this->once()) + ->method('remove') + ->with(self::LOCK_PREFIX . $identifier) + ->willReturn(true); + + $this->assertEquals(true, $this->cache->unlock($identifier)); + } +} From a21b2466f6d63e5b9d120ff0295f80f51c2ec7cc Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Thu, 6 Feb 2020 13:57:24 +0200 Subject: [PATCH 076/369] Cover unit test --- .../Adminhtml/Order/Create/ReorderTest.php | 193 +++++++++++++----- 1 file changed, 144 insertions(+), 49 deletions(-) diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php index b11d73de736d4..ce777046b584d 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Create/ReorderTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order\Create; use Magento\Backend\App\Action\Context; @@ -14,19 +16,24 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\Message\ManagerInterface; use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Controller\Adminhtml\Order\Create\Reorder; use Magento\Sales\Model\AdminOrder\Create; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Reorder\UnavailableProductsProvider; use Magento\Sales\Helper\Reorder as ReorderHelper; +use Magento\Framework\Exception\NoSuchEntityException; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class ReorderTest + * Verify reorder class. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) */ -class ReorderTest extends \PHPUnit\Framework\TestCase +class ReorderTest extends TestCase { /** * @var Reorder @@ -39,67 +46,67 @@ class ReorderTest extends \PHPUnit\Framework\TestCase private $context; /** - * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject + * @var RequestInterface|MockObject */ private $requestMock; /** - * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ObjectManagerInterface|MockObject */ private $objectManagerMock; /** - * @var Order|\PHPUnit_Framework_MockObject_MockObject + * @var Order|MockObject */ private $orderMock; /** - * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ManagerInterface|MockObject */ private $messageManagerMock; /** - * @var ForwardFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ForwardFactory|MockObject */ private $resultForwardFactoryMock; /** - * @var RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + * @var RedirectFactory|MockObject */ private $resultRedirectFactoryMock; /** - * @var Redirect|\PHPUnit_Framework_MockObject_MockObject + * @var Redirect|MockObject */ private $resultRedirectMock; /** - * @var Forward|\PHPUnit_Framework_MockObject_MockObject + * @var Forward|MockObject */ private $resultForwardMock; /** - * @var Quote|\PHPUnit_Framework_MockObject_MockObject + * @var Quote|MockObject */ private $quoteSessionMock; /** - * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var OrderRepositoryInterface|MockObject */ private $orderRepositoryMock; /** - * @var ReorderHelper|\PHPUnit_Framework_MockObject_MockObject + * @var ReorderHelper|MockObject */ private $reorderHelperMock; /** - * @var UnavailableProductsProvider|\PHPUnit_Framework_MockObject_MockObject + * @var UnavailableProductsProvider|MockObject */ private $unavailableProductsProviderMock; /** - * @var Create|\PHPUnit_Framework_MockObject_MockObject + * @var Create|MockObject */ private $orderCreateMock; @@ -109,39 +116,33 @@ class ReorderTest extends \PHPUnit\Framework\TestCase private $orderId; /** - * @return void + * @inheritDoc */ protected function setUp() { $this->orderId = 111; - $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class)->getMockForAbstractClass(); + $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class); + $this->requestMock = $this->createMock(RequestInterface::class); + $this->objectManagerMock = $this->createMock(ObjectManagerInterface::class); + $this->resultForwardFactoryMock = $this->createMock(ForwardFactory::class); + $this->resultRedirectFactoryMock = $this->createMock(RedirectFactory::class); + $this->resultRedirectMock = $this->createMock(Redirect::class); + $this->resultForwardMock = $this->createMock(Forward::class); + $this->reorderHelperMock = $this->createMock(ReorderHelper::class); + $this->unavailableProductsProviderMock = $this->createMock(UnavailableProductsProvider::class); + $this->orderCreateMock = $this->createMock(Create::class); $this->orderMock = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->setMethods(['getEntityId', 'getId', 'setReordered']) ->getMock(); - $this->requestMock = $this->getMockBuilder(RequestInterface::class)->getMockForAbstractClass(); - $this->objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)->getMockForAbstractClass(); - $this->resultForwardFactoryMock = $this->getMockBuilder(ForwardFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resultRedirectFactoryMock = $this->getMockBuilder(RedirectFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resultRedirectMock = $this->getMockBuilder(Redirect::class)->disableOriginalConstructor()->getMock(); - $this->resultForwardMock = $this->getMockBuilder(Forward::class)->disableOriginalConstructor()->getMock(); $this->quoteSessionMock = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() ->setMethods(['clearStorage', 'setUseOldShippingMethod']) ->getMock(); - $this->reorderHelperMock = $this->getMockBuilder(ReorderHelper::class) - ->disableOriginalConstructor() - ->getMock(); - $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class)->getMockForAbstractClass(); - $this->unavailableProductsProviderMock = $this->getMockBuilder(UnavailableProductsProvider::class) - ->disableOriginalConstructor() - ->getMock(); - $this->orderCreateMock = $this->getMockBuilder(Create::class)->disableOriginalConstructor()->getMock(); - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) + ->getMockForAbstractClass(); + + $objectManager = new ObjectManager($this); $this->context = $objectManager->getObject( Context::class, [ @@ -165,9 +166,11 @@ protected function setUp() } /** + * Verify execute with no route. + * * @return void */ - public function testExecuteForward() + public function testExecuteForward(): void { $this->clearStorage(); $this->getOrder(); @@ -178,9 +181,11 @@ public function testExecuteForward() } /** + * Verify execute redirect order grid + * * @return void */ - public function testExecuteRedirectOrderGrid() + public function testExecuteRedirectOrderGrid(): void { $this->clearStorage(); $this->getOrder(); @@ -193,9 +198,11 @@ public function testExecuteRedirectOrderGrid() } /** + * Verify execute redirect back. + * * @return void */ - public function testExecuteRedirectBack() + public function testExecuteRedirectBack(): void { $this->clearStorage(); $this->getOrder(); @@ -210,9 +217,11 @@ public function testExecuteRedirectBack() } /** + * Verify execute redirect new order. + * * @return void */ - public function testExecuteRedirectNewOrder() + public function testExecuteRedirectNewOrder(): void { $this->clearStorage(); $this->getOrder(); @@ -227,9 +236,75 @@ public function testExecuteRedirectNewOrder() } /** + * Verify redirect new order with throws exception. + * + * @return void + */ + public function testExecuteRedirectNewOrderWithThrowsException(): void + { + $exception = new NoSuchEntityException(); + + $this->clearStorage(); + $this->getOrder(); + $this->canReorder(true); + $this->createRedirect(); + $this->getOrderId($this->orderId); + $this->getUnavailableProducts([]); + + $this->orderMock->expects($this->once()) + ->method('setReordered') + ->with(true) + ->willThrowException($exception); + $this->messageManagerMock + ->expects($this->once()) + ->method('addErrorMessage') + ->willReturnSelf(); + $this->resultRedirectMock + ->expects($this->once()) + ->method('setPath') + ->with('sales/*') + ->willReturnSelf(); + $this->assertInstanceOf(Redirect::class, $this->reorder->execute()); + } + + /** + * Verify redirect new order with exception. + * * @return void */ - private function clearStorage() + public function testExecuteRedirectNewOrderWithException(): void + { + $exception = new \Exception(); + + $this->clearStorage(); + $this->getOrder(); + $this->canReorder(true); + $this->createRedirect(); + $this->getOrderId($this->orderId); + $this->getUnavailableProducts([]); + $this->orderMock->expects($this->once()) + ->method('setReordered') + ->with(true) + ->willThrowException(new $exception); + $this->messageManagerMock + ->expects($this->once()) + ->method('addException') + ->with($exception, __('Error while processing order.')) + ->willReturnSelf(); + $this->resultRedirectMock + ->expects($this->once()) + ->method('setPath') + ->with('sales/*') + ->willReturnSelf(); + $this->assertInstanceOf(Redirect::class, $this->reorder->execute()); + } + + /** + * Mock clear storage. + * + * @return void + */ + private function clearStorage(): void { $this->objectManagerMock->expects($this->at(0)) ->method('get') @@ -239,9 +314,11 @@ private function clearStorage() } /** + * Mock get order. + * * @return void */ - private function getOrder() + private function getOrder(): void { $this->requestMock->expects($this->once()) ->method('getParam') @@ -254,9 +331,12 @@ private function getOrder() } /** + * Mock and return 'canReorder' method. + * * @param bool $result + * @return void */ - private function canReorder($result) + private function canReorder(bool $result): void { $entityId = 1; $this->orderMock->expects($this->once())->method('getEntityId')->willReturn($entityId); @@ -267,18 +347,22 @@ private function canReorder($result) } /** + * Mock result forward. + * * @return void */ - private function prepareForward() + private function prepareForward(): void { $this->resultForwardFactoryMock->expects($this->once())->method('create')->willReturn($this->resultForwardMock); $this->resultForwardMock->expects($this->once())->method('forward')->with('noroute')->willReturnSelf(); } /** + * Mock create. + * * @return void */ - private function createRedirect() + private function createRedirect(): void { $this->resultRedirectFactoryMock->expects($this->once()) ->method('create') @@ -286,26 +370,35 @@ private function createRedirect() } /** + * Mock order 'getId' method. + * * @param null|int $orderId + * @return void */ - private function getOrderId($orderId) + private function getOrderId($orderId): void { $this->orderMock->expects($this->once())->method('getId')->willReturn($orderId); } /** + * Mock result redirect 'setPath' method. + * * @param string $path * @param null|array $params + * @return void */ - private function setPath($path, $params = []) + private function setPath(string $path, $params = []): void { $this->resultRedirectMock->expects($this->once())->method('setPath')->with($path, $params); } /** + * Mock unavailable products provider. + * * @param array $unavailableProducts + * @return void */ - private function getUnavailableProducts(array $unavailableProducts) + private function getUnavailableProducts(array $unavailableProducts): void { $this->unavailableProductsProviderMock->expects($this->any()) ->method('getForOrder') @@ -314,9 +407,11 @@ private function getUnavailableProducts(array $unavailableProducts) } /** + * Mock init form order. + * * @return void */ - private function initFromOrder() + private function initFromOrder(): void { $this->orderMock->expects($this->once())->method('setReordered')->with(true)->willReturnSelf(); $this->objectManagerMock->expects($this->at(1)) From b1ecb96ac7639bf6c56de1dbfc49c210af9705be Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Thu, 6 Feb 2020 16:23:53 -0600 Subject: [PATCH 077/369] MC-31198: Group titles not found for downloadable products while importing products --- .../Model/Import/Product/Type/Downloadable.php | 5 ----- .../product_downloadable_with_link_url_and_sample_url.php | 1 - 2 files changed, 6 deletions(-) diff --git a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php index f148550dd96bb..30e08530d2536 100644 --- a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php +++ b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php @@ -406,11 +406,6 @@ protected function isRowValidLink(array $rowData) $linkData = $this->prepareLinkData($rowData[self::COL_DOWNLOADABLE_LINKS]); - if ($this->linksAdditionalAttributes($rowData, 'group_title', self::DEFAULT_GROUP_TITLE) == '') { - $this->_entityModel->addRowError(self::ERROR_GROUP_TITLE_NOT_FOUND, $this->rowNum); - $result = true; - } - $result = $result ?? $this->isTitle($linkData); foreach ($linkData as $link) { diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php index 32fed4730adfc..d7de542be6e22 100644 --- a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php @@ -44,7 +44,6 @@ ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) ->setLinksPurchasedSeparately(true) - ->setLinksTitle('Links') ->setSamplesTitle('Samples') ->setStockData( [ From 5276ecb0b2675a84ec4dfc83cc25d28b9e5c749f Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Thu, 6 Feb 2020 16:42:17 -0600 Subject: [PATCH 078/369] MC-30636: TinyMCE4: some HTML code breaks the editor --- .../plugins/magentowidget/editor_plugin.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js index f163206a13656..a9259a9a2daf3 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/plugins/magentowidget/editor_plugin.js @@ -168,17 +168,22 @@ define([ /(<span class="[^"]*magento-widget[^"]*"[^>]*>)?<img([^>]+id="[^>]+)>(([^>]*)<\/span>)?/i, function (match) { var attributes = wysiwyg.parseAttributesString(match[2]), - widgetCode; + widgetCode, + result = match[0]; if (attributes.id) { - widgetCode = Base64.idDecode(attributes.id); + try { + widgetCode = Base64.idDecode(attributes.id); + } catch (e) { + // Ignore and continue. + } - if (widgetCode.indexOf('{{widget') !== -1) { - return widgetCode; + if (widgetCode && widgetCode.indexOf('{{widget') !== -1) { + result = widgetCode; } } - return match[0]; + return result; } ); }, From 43f9e2b61dca391ca4c887f0c0c939dcbace34b7 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Thu, 6 Feb 2020 14:50:28 +0200 Subject: [PATCH 079/369] Add implement HttpPostActionInterface for renderer controller --- .../Adminhtml/Order/Create/Reorder.php | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php index 04013df790a1d..481fa669b72d3 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php @@ -3,16 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Controller\Adminhtml\Order\Create; use Magento\Backend\App\Action; +use Magento\Backend\Model\View\Result\Forward; use Magento\Backend\Model\View\Result\ForwardFactory; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Catalog\Helper\Product; +use Magento\Framework\Escaper; use Magento\Framework\View\Result\PageFactory; -use Magento\Sales\Model\Order\Reorder\UnavailableProductsProvider; use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Controller\Adminhtml\Order\Create; use Magento\Sales\Helper\Reorder as ReorderHelper; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Reorder\UnavailableProductsProvider; +use Magento\Framework\App\Action\HttpPostActionInterface; -class Reorder extends \Magento\Sales\Controller\Adminhtml\Order\Create +/** + * Controller create order. + */ +class Reorder extends Create implements HttpPostActionInterface { /** * @var UnavailableProductsProvider @@ -31,8 +43,8 @@ class Reorder extends \Magento\Sales\Controller\Adminhtml\Order\Create /** * @param Action\Context $context - * @param \Magento\Catalog\Helper\Product $productHelper - * @param \Magento\Framework\Escaper $escaper + * @param Product $productHelper + * @param Escaper $escaper * @param PageFactory $resultPageFactory * @param ForwardFactory $resultForwardFactory * @param UnavailableProductsProvider $unavailableProductsProvider @@ -41,8 +53,8 @@ class Reorder extends \Magento\Sales\Controller\Adminhtml\Order\Create */ public function __construct( Action\Context $context, - \Magento\Catalog\Helper\Product $productHelper, - \Magento\Framework\Escaper $escaper, + Product $productHelper, + Escaper $escaper, PageFactory $resultPageFactory, ForwardFactory $resultForwardFactory, UnavailableProductsProvider $unavailableProductsProvider, @@ -62,19 +74,21 @@ public function __construct( } /** - * @return \Magento\Backend\Model\View\Result\Forward|\Magento\Backend\Model\View\Result\Redirect + * Adminhtml controller create order. + * + * @return Forward|Redirect */ public function execute() { $this->_getSession()->clearStorage(); $orderId = $this->getRequest()->getParam('order_id'); - /** @var \Magento\Sales\Model\Order $order */ + /** @var Order $order */ $order = $this->orderRepository->get($orderId); if (!$this->reorderHelper->canReorder($order->getEntityId())) { return $this->resultForwardFactory->create()->forward('noroute'); } - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + /** @var Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); if (!$order->getId()) { $resultRedirect->setPath('sales/order/'); @@ -90,7 +104,7 @@ public function execute() } $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]); } else { - try { + try { $order->setReordered(true); $this->_getSession()->setUseOldShippingMethod(true); $this->_getOrderCreateModel()->initFromOrder($order); From f0e14b0480effcc2b6f44a410f86b357fdb0f7cf Mon Sep 17 00:00:00 2001 From: Piotr Markiewicz <piotr.markiewicz@vaimo.com> Date: Fri, 7 Feb 2020 11:03:12 +0100 Subject: [PATCH 080/369] Use messageManager instead of throwing exceptions --- .../Adminhtml/Export/File/Delete.php | 34 ++++++++++--------- .../Adminhtml/Export/File/Download.php | 19 +++++++---- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php index 1e9d194653c9c..75d772922c70c 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php @@ -9,9 +9,7 @@ use Magento\Backend\App\Action; use Magento\Framework\App\Action\HttpPostActionInterface; -use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Exception\FileSystemException; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\ImportExport\Controller\Adminhtml\Export as ExportController; use Magento\Framework\Filesystem; @@ -56,29 +54,33 @@ public function __construct( /** * Controller basic method implementation. * - * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface - * @throws LocalizedException + * @return \Magento\Framework\Controller\ResultInterface */ public function execute() { + /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('adminhtml/export/index'); + $fileName = $this->getRequest()->getParam('filename'); + if (empty($fileName)) { + $this->messageManager->addErrorMessage(\__('Please provide valid export file name')); + + return $resultRedirect; + } try { - if (empty($fileName = $this->getRequest()->getParam('filename'))) { - throw new LocalizedException(__('Please provide export file name')); - } $directory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR); $path = $directory->getAbsolutePath() . 'export/' . $fileName; - if (!$directory->isFile($path)) { - throw new LocalizedException(__('Sorry, but the data is invalid or the file is not uploaded.')); + if ($directory->isFile($path)) { + $this->file->deleteFile($path); + $this->messageManager->addSuccessMessage(\__('File %1 deleted', $fileName)); + } else { + $this->messageManager->addErrorMessage(\__('%1 is not a valid file', $fileName)); } - - $this->file->deleteFile($path); - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ - $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); - $resultRedirect->setPath('adminhtml/export/index'); - return $resultRedirect; } catch (FileSystemException $exception) { - throw new LocalizedException(__('There are no export file with such name %1', $fileName)); + $this->messageManager->addErrorMessage($exception->getMessage()); } + + return $resultRedirect; } } diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php index 8dbd9a0ae44ba..48e8b8f1d9d07 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php @@ -10,7 +10,6 @@ use Magento\Backend\App\Action; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Response\Http\FileFactory; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\ImportExport\Controller\Adminhtml\Export as ExportController; use Magento\Framework\Filesystem; @@ -55,12 +54,17 @@ public function __construct( * Controller basic method implementation. * * @return \Magento\Framework\App\ResponseInterface - * @throws LocalizedException */ public function execute() { - if (empty($fileName = $this->getRequest()->getParam('filename'))) { - throw new LocalizedException(__('Please provide export file name')); + /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + $resultRedirect = $this->resultRedirectFactory->create(); + $resultRedirect->setPath('adminhtml/export/index'); + $fileName = $this->getRequest()->getParam('filename'); + if (empty($fileName) || \preg_match('/\.\.(\\\|\/)/', $fileName) !== 0) { + $this->messageManager->addErrorMessage(\__('Please provide valid export file name')); + + return $resultRedirect; } try { $path = 'export/' . $fileName; @@ -72,8 +76,11 @@ public function execute() DirectoryList::VAR_DIR ); } - } catch (LocalizedException | \Exception $exception) { - throw new LocalizedException(__('There are no export file with such name %1', $fileName)); + $this->messageManager->addErrorMessage(\__('%1 is not a valid file', $fileName)); + } catch (\Exception $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); } + + return $resultRedirect; } } From c5ca58cfcec9cff2e99610058757bd8174bc91df Mon Sep 17 00:00:00 2001 From: Antonino Bonumore <a.bonumore@emergento.com> Date: Fri, 7 Feb 2020 11:24:48 +0100 Subject: [PATCH 081/369] magento#26745 add method setAdditionalInformation to OrderPaymentInterface --- app/code/Magento/Sales/Api/Data/OrderPaymentInterface.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/code/Magento/Sales/Api/Data/OrderPaymentInterface.php b/app/code/Magento/Sales/Api/Data/OrderPaymentInterface.php index ea0cf5d7b32be..ac400206b8a2f 100644 --- a/app/code/Magento/Sales/Api/Data/OrderPaymentInterface.php +++ b/app/code/Magento/Sales/Api/Data/OrderPaymentInterface.php @@ -1042,6 +1042,14 @@ public function setCcNumberEnc($ccNumberEnc); */ public function setCcTransId($id); + /** + * Set the additional information for the order payment. + * + * @param string[] $additionalInformation + * @return $this + */ + public function setAdditionalInformation($additionalInformation); + /** * Sets the address status for the order payment. * From ba120b94508c8ae1a0bf488ffb0c72794ec512e5 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Fri, 7 Feb 2020 09:26:07 -0600 Subject: [PATCH 082/369] MC-31198: Group titles not found for downloadable products while importing products --- .../Model/Import/Product/Type/Downloadable.php | 5 ----- .../product_downloadable_with_link_url_and_sample_url.php | 1 - 2 files changed, 6 deletions(-) diff --git a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php index 30e08530d2536..f0e50b2837153 100644 --- a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php +++ b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php @@ -365,11 +365,6 @@ protected function isRowValidSample(array $rowData) $sampleData = $this->prepareSampleData($rowData[static::COL_DOWNLOADABLE_SAMPLES]); - if ($this->sampleGroupTitle($rowData) == '') { - $result = true; - $this->_entityModel->addRowError(self::ERROR_GROUP_TITLE_NOT_FOUND, $this->rowNum); - } - $result = $result ?? $this->isTitle($sampleData); foreach ($sampleData as $link) { diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php index d7de542be6e22..2686b7c095b2f 100644 --- a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_link_url_and_sample_url.php @@ -44,7 +44,6 @@ ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) ->setLinksPurchasedSeparately(true) - ->setSamplesTitle('Samples') ->setStockData( [ 'qty' => 100, From cb685d15b2b590bdb4d99a3d4ebe563037d0aa31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maksymilian=20Szyd=C5=82o?= <maksymilian.szydlo@bold.net.pl> Date: Sat, 24 Mar 2018 17:11:45 +0100 Subject: [PATCH 083/369] Fix generating product URL rewrites for anchor categories (cherry picked from commit 63111ed399b2e058efc0bf1c7c5427299cb8c5fc) --- .../Model/Product/AnchorUrlRewriteGenerator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php index 5d08ea33ff8a1..a6589c6062846 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php @@ -67,7 +67,7 @@ public function generate($storeId, Product $product, ObjectRegistry $productCate $anchorCategoryIds = $category->getAnchorsAbove(); if ($anchorCategoryIds) { foreach ($anchorCategoryIds as $anchorCategoryId) { - $anchorCategory = $this->categoryRepository->get($anchorCategoryId); + $anchorCategory = $this->categoryRepository->get($anchorCategoryId, $storeId); if ((int)$anchorCategory->getParentId() === Category::TREE_ROOT_ID) { continue; } From acb8642c44d1ecb60ad023c81f6541fa9ebae47d Mon Sep 17 00:00:00 2001 From: Pieter Hoste <hoste.pieter@gmail.com> Date: Sun, 9 Feb 2020 14:32:08 +0100 Subject: [PATCH 084/369] Updated unit test and make sure it passes the store id to the get call on the category repository. This is not testing for the bugfix but makes sure that when not passing the storeId param in the tested code, the test starts complaining. --- .../Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php index 662e156b8f100..8eb46f912d491 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php @@ -100,9 +100,9 @@ public function testGenerateCategories() ->expects($this->any()) ->method('get') ->withConsecutive( - [ 'category_id' => $categoryIds[0]], - [ 'category_id' => $categoryIds[1]], - [ 'category_id' => $categoryIds[2]] + [$categoryIds[0], $storeId], + [$categoryIds[1], $storeId], + [$categoryIds[2], $storeId], ) ->will($this->returnValue($category)); $this->categoryRegistry->expects($this->any())->method('getList') From 99a9405b24083c88d00bb35a2be2248d3d1d3f07 Mon Sep 17 00:00:00 2001 From: Pieter Hoste <hoste.pieter@gmail.com> Date: Sun, 6 Oct 2019 11:22:55 +0200 Subject: [PATCH 085/369] Create missing directories in imageuploader tree if they don't already exist. --- .../Magento/Cms/Helper/Wysiwyg/Images.php | 37 ++++++++++++----- .../Test/Unit/Helper/Wysiwyg/ImagesTest.php | 41 ++++++++++++------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/Cms/Helper/Wysiwyg/Images.php b/app/code/Magento/Cms/Helper/Wysiwyg/Images.php index cd3473c6bab87..c634910f4468f 100644 --- a/app/code/Magento/Cms/Helper/Wysiwyg/Images.php +++ b/app/code/Magento/Cms/Helper/Wysiwyg/Images.php @@ -224,8 +224,7 @@ public function getImageHtmlDeclaration($filename, $renderAsTag = false) } /** - * Return path of the current selected directory or root directory for startup - * Try to create target directory if it doesn't exist + * Return path of the root directory for startup. Also try to create target directory if it doesn't exist * * @return string * @throws \Magento\Framework\Exception\LocalizedException @@ -241,18 +240,34 @@ public function getCurrentPath() $currentPath = $path; } } + + $currentTreePath = $this->_getRequest()->getParam('current_tree_path'); + if ($currentTreePath) { + $currentTreePath = $this->convertIdToPath($currentTreePath); + $this->createSubDirIfNotExist($currentTreePath); + } + + $this->_currentPath = $currentPath; + } + + return $this->_currentPath; + } + + private function createSubDirIfNotExist(string $absPath) + { + $relPath = $this->_directory->getRelativePath($absPath); + if (!$this->_directory->isExist($relPath)) { try { - $currentDir = $this->_directory->getRelativePath($currentPath); - if (!$this->_directory->isExist($currentDir)) { - $this->_directory->create($currentDir); - } + $this->_directory->create($relPath); } catch (\Magento\Framework\Exception\FileSystemException $e) { - $message = __('The directory %1 is not writable by server.', $currentPath); + $message = __( + 'Can\'t create %1 as subdirectory of %2, you might have some permission issue.', + $relPath, + $this->_directory->getAbsolutePath() + ); throw new \Magento\Framework\Exception\LocalizedException($message); } - $this->_currentPath = $currentPath; } - return $this->_currentPath; } /** @@ -294,6 +309,8 @@ public function idEncode($string) public function idDecode($string) { $string = strtr($string, ':_-', '+/='); + + // phpcs:ignore Magento2.Functions.DiscouragedFunction return base64_decode($string); } @@ -315,7 +332,7 @@ public function getShortFilename($filename, $maxLength = 20) /** * Set user-traversable image directory subpath relative to media directory and relative to nested storage root * - * @var string $subpath + * @param string $subpath * @return void */ public function setImageDirectorySubpath($subpath) diff --git a/app/code/Magento/Cms/Test/Unit/Helper/Wysiwyg/ImagesTest.php b/app/code/Magento/Cms/Test/Unit/Helper/Wysiwyg/ImagesTest.php index d13b4f47a85e7..4acef951d8f4a 100644 --- a/app/code/Magento/Cms/Test/Unit/Helper/Wysiwyg/ImagesTest.php +++ b/app/code/Magento/Cms/Test/Unit/Helper/Wysiwyg/ImagesTest.php @@ -339,13 +339,14 @@ public function providerIsUsingStaticUrlsAllowed() * @param bool $isExist * @dataProvider providerGetCurrentPath */ - public function testGetCurrentPath($pathId, $expectedPath, $isExist) + public function testGetCurrentPath($pathId, $subDir, $expectedPath, $isExist) { $this->requestMock->expects($this->any()) ->method('getParam') ->willReturnMap( [ ['node', null, $pathId], + ['current_tree_path', null, $subDir], ] ); @@ -367,21 +368,33 @@ public function testGetCurrentPath($pathId, $expectedPath, $isExist) ['PATH', '.'], ] ); - $this->directoryWriteMock->expects($this->once()) - ->method('isExist') - ->willReturn($isExist); - $this->directoryWriteMock->expects($this->any()) - ->method('create') - ->with($this->directoryWriteMock->getRelativePath($expectedPath)); + + if ($subDir) { + $this->directoryWriteMock->expects($this->once()) + ->method('isExist') + ->willReturn($isExist); + $this->directoryWriteMock->expects($this->any()) + ->method('create') + ->with($this->directoryWriteMock->getRelativePath($expectedPath)); + } $this->assertEquals($expectedPath, $this->imagesHelper->getCurrentPath()); } public function testGetCurrentPathThrowException() { + $this->requestMock->expects($this->any()) + ->method('getParam') + ->willReturn('PATH'); + $this->expectException(\Magento\Framework\Exception\LocalizedException::class); - $this->expectExceptionMessage('The directory PATH is not writable by server.'); + $this->expectExceptionMessage( + 'Can\'t create SUBDIR as subdirectory of PATH, you might have some permission issue.' + ); + $this->directoryWriteMock->expects($this->any()) + ->method('getRelativePath') + ->willReturn('SUBDIR'); $this->directoryWriteMock->expects($this->once()) ->method('isExist') ->willReturn(false); @@ -402,12 +415,12 @@ public function testGetCurrentPathThrowException() public function providerGetCurrentPath() { return [ - ['L3Rlc3RfcGF0aA--', 'PATH/test_path', true], - ['L215LmpwZw--', 'PATH', true], - [null, 'PATH', true], - ['L3Rlc3RfcGF0aA--', 'PATH/test_path', false], - ['L215LmpwZw--', 'PATH', false], - [null, 'PATH', false], + ['L3Rlc3RfcGF0aA--', 'L3Rlc3RfcGF0aA--', 'PATH/test_path', true], + ['L215LmpwZw--', '', 'PATH', true], + [null, '', 'PATH', true], + ['L3Rlc3RfcGF0aA--', 'L3Rlc3RfcGF0aA--', 'PATH/test_path', false], + ['L215LmpwZw--', '', 'PATH', false], + [null, '', 'PATH', false], ]; } From 97258e1a9ce97ad32971731eceac8fee32f66b74 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Thu, 13 Feb 2020 16:21:06 +0200 Subject: [PATCH 086/369] Changes are covered by the integration test --- .../Product/AnchorUrlRewriteGeneratorTest.php | 81 +++++++++++++++++++ .../_files/categories_with_stores.php | 81 +++++++++++++++++++ .../_files/product_with_stores.php | 46 +++++++++++ 3 files changed, 208 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php create mode 100644 dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php create mode 100644 dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php new file mode 100644 index 0000000000000..a348a3eca2b74 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Model\Product; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/**3 + * Verify generate url rewrites for anchor categories. + */ +class AnchorUrlRewriteGeneratorTest extends TestCase +{ + + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var Category + */ + private $collectionCategory; + + /** + * @var ObjectRegistryFactory + */ + private $objectRegistryFactory; + + /** + * @inheritDoc + */ + public function setUp() + { + parent::setUp(); // TODO: Change the autogenerated stub + + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->collectionCategory = $this->objectManager->create(Category::class); + $this->objectRegistryFactory = $this->objectManager->create(ObjectRegistryFactory::class); + } + + /** + * Verify correct generate of the relative "StoreId" + * + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_with_stores.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testGenerate(): void + { + $product = $this->productRepository->get('simple'); + $categories = $product->getCategoryCollection(); + $productCategories = $this->objectRegistryFactory->create(['entities' => $categories]); + + /** @var Store $store */ + $store = Bootstrap::getObjectManager()->get(Store::class); + $store->load('fixture_second_store', 'code'); + + /** @var AnchorUrlRewriteGenerator $generator */ + $generator = $this->objectManager->get(AnchorUrlRewriteGenerator::class); + + $this->assertEquals([], $generator->generate(1, $product, $productCategories)); + $this->assertNotEquals([], $generator->generate($store->getId(), $product, $productCategories)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php new file mode 100644 index 0000000000000..7ac0487478d57 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Backend\App\Area\FrontNameResolver; +use Magento\Catalog\Model\Category; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/../../../Magento/Store/_files/second_store.php'; +Bootstrap::getInstance() + ->loadArea(FrontNameResolver::AREA_CODE); + +/** + * After installation system has categories: + * + * root one with ID:1 and Default category with ID:3 both with StoreId:1, + * + * root one with ID:1 and Default category with ID:2 both with StoreId:2 + */ + +$store = Bootstrap::getObjectManager()->get(Store::class); +$store->load('fixture_second_store', 'code'); + +/** @var $category Category */ +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId(3) + ->setName('Category 1') + ->setParentId(1) + ->setPath('1/2') + ->setLevel(1) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setPosition(1) + ->save(); + +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId(4) + ->setName('Category 1.1') + ->setParentId(3) + ->setPath('1/2/3') + ->setLevel(2) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setPosition(1) + ->save(); + +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId(3) + ->setName('Category 1') + ->setParentId(2) + ->setPath('1/2/3') + ->setLevel(2) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setStoreId($store->getId()) + ->setIsActive(true) + ->setPosition(1) + ->save(); + +$category = Bootstrap::getObjectManager()->create(Category::class); +$category->isObjectNew(true); +$category->setId(4) + ->setName('Category 1.1') + ->setParentId(3) + ->setPath('1/2/3/4') + ->setLevel(3) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setStoreId($store->getId()) + ->setIsActive(true) + ->setPosition(1) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php new file mode 100644 index 0000000000000..bcc7c9ed313d3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Catalog\Setup\CategorySetup $installer */ +$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Setup\CategorySetup::class +); +require __DIR__ . '/categories_with_stores.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$categoryLinkRepository = $objectManager->create( + \Magento\Catalog\Api\CategoryLinkRepositoryInterface::class, + [ + 'productRepository' => $productRepository + ] +); + +/** @var Magento\Catalog\Api\CategoryLinkManagementInterface $linkManagement */ +$categoryLinkManagement = $objectManager->create( + \Magento\Catalog\Api\CategoryLinkManagementInterface::class, + [ + 'productRepository' => $productRepository, + 'categoryLinkRepository' => $categoryLinkRepository + ] +); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setStoreId(1) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple') + ->setPrice(10) + ->setWeight(18) + ->setStockData(['use_config_manage_stock' => 0]) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->save(); +$categoryLinkManagement->assignProductToCategories($product->getSku(), [4]); From 0b941218dcc86dcbd518f5400934e995ad1f2e3c Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Fri, 14 Feb 2020 10:20:18 +0200 Subject: [PATCH 087/369] Minor change --- .../Product/AnchorUrlRewriteGeneratorTest.php | 60 +++++++++++++------ .../Product/AnchorUrlRewriteGeneratorTest.php | 2 +- .../_files/categories_with_stores.php | 3 +- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php index 8eb46f912d491..d8fec2de0e46e 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/AnchorUrlRewriteGeneratorTest.php @@ -3,53 +3,67 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogUrlRewrite\Test\Unit\Model\Product; +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\CatalogUrlRewrite\Model\ObjectRegistry; +use Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; -class AnchorUrlRewriteGeneratorTest extends \PHPUnit\Framework\TestCase +class AnchorUrlRewriteGeneratorTest extends TestCase { - /** @var \Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator */ + /** @var AnchorUrlRewriteGenerator */ protected $anchorUrlRewriteGenerator; - /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ProductUrlPathGenerator|MockObject */ protected $productUrlPathGenerator; - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ + /** @var Product|MockObject */ protected $product; - /** @var \Magento\Catalog\Api\CategoryRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var CategoryRepositoryInterface|MockObject */ private $categoryRepositoryInterface; - /** @var \Magento\CatalogUrlRewrite\Model\ObjectRegistry|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ObjectRegistry|MockObject */ protected $categoryRegistry; - /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var UrlRewriteFactory|MockObject */ protected $urlRewriteFactory; - /** @var \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|\PHPUnit_Framework_MockObject_MockObject */ + /** @var UrlRewrite|MockObject */ protected $urlRewrite; + /** + * @inheritDoc + */ protected function setUp() { - $this->urlRewriteFactory = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory::class) + $this->urlRewriteFactory = $this->getMockBuilder(UrlRewriteFactory::class) ->setMethods(['create']) ->disableOriginalConstructor()->getMock(); - $this->urlRewrite = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) + $this->urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $this->product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $this->product = $this->getMockBuilder(Product::class) ->disableOriginalConstructor()->getMock(); $this->categoryRepositoryInterface = $this->getMockBuilder( - \Magento\Catalog\Api\CategoryRepositoryInterface::class + CategoryRepositoryInterface::class )->disableOriginalConstructor()->getMock(); - $this->categoryRegistry = $this->getMockBuilder(\Magento\CatalogUrlRewrite\Model\ObjectRegistry::class) + $this->categoryRegistry = $this->getMockBuilder(ObjectRegistry::class) ->disableOriginalConstructor()->getMock(); $this->productUrlPathGenerator = $this->getMockBuilder( - \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::class + ProductUrlPathGenerator::class )->disableOriginalConstructor()->getMock(); $this->anchorUrlRewriteGenerator = (new ObjectManager($this))->getObject( - \Magento\CatalogUrlRewrite\Model\Product\AnchorUrlRewriteGenerator::class, + AnchorUrlRewriteGenerator::class, [ 'productUrlPathGenerator' => $this->productUrlPathGenerator, 'urlRewriteFactory' => $this->urlRewriteFactory, @@ -58,7 +72,12 @@ protected function setUp() ); } - public function testGenerateEmpty() + /** + * Verify generate if category registry list is empty. + * + * @return void + */ + public function testGenerateEmpty(): void { $this->categoryRegistry->expects($this->any())->method('getList')->will($this->returnValue([])); @@ -68,7 +87,12 @@ public function testGenerateEmpty() ); } - public function testGenerateCategories() + /** + * Verify generate product rewrites for anchor categories. + * + * @return void + */ + public function testGenerateCategories(): void { $urlPathWithCategory = 'category1/category2/category3/simple-product.html'; $storeId = 10; @@ -102,7 +126,7 @@ public function testGenerateCategories() ->withConsecutive( [$categoryIds[0], $storeId], [$categoryIds[1], $storeId], - [$categoryIds[2], $storeId], + [$categoryIds[2], $storeId] ) ->will($this->returnValue($category)); $this->categoryRegistry->expects($this->any())->method('getList') diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php index a348a3eca2b74..32eee8dd78250 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php @@ -15,7 +15,7 @@ use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; -/**3 +/** * Verify generate url rewrites for anchor categories. */ class AnchorUrlRewriteGeneratorTest extends TestCase diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php index 7ac0487478d57..6794316b4bb93 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php @@ -11,8 +11,7 @@ use Magento\TestFramework\Helper\Bootstrap; require __DIR__ . '/../../../Magento/Store/_files/second_store.php'; -Bootstrap::getInstance() - ->loadArea(FrontNameResolver::AREA_CODE); +Bootstrap::getInstance()->loadArea(FrontNameResolver::AREA_CODE); /** * After installation system has categories: From 58dde946843684dc0eece859f3943b4c897e7ab8 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Tue, 11 Feb 2020 13:46:59 +0200 Subject: [PATCH 088/369] Add support for db_schema.xml char type column --- app/etc/di.xml | 1 + .../Schema/Dto/Columns/StringBinary.php | 7 ++-- .../Setup/Declaration/Schema/etc/schema.xsd | 1 + .../Schema/etc/types/texts/char.xsd | 33 +++++++++++++++++++ .../CharDefinition.php | 29 ++++++++++++++++ 5 files changed, 67 insertions(+), 4 deletions(-) create mode 100644 lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/types/texts/char.xsd create mode 100644 lib/internal/Magento/Framework/Setup/SchemaListenerDefinition/CharDefinition.php diff --git a/app/etc/di.xml b/app/etc/di.xml index dcd6a4253c98a..177c6b67fb50b 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1592,6 +1592,7 @@ <item name="longblog" xsi:type="object">Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition</item> <item name="varbinary" xsi:type="object">Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition</item> <item name="varchar" xsi:type="object">Magento\Framework\Setup\SchemaListenerDefinition\TextBlobDefinition</item> + <item name="char" xsi:type="object">Magento\Framework\Setup\SchemaListenerDefinition\CharDefinition</item> <item name="timestamp" xsi:type="object">Magento\Framework\Setup\SchemaListenerDefinition\TimestampDefinition</item> <item name="datetime" xsi:type="object">Magento\Framework\Setup\SchemaListenerDefinition\TimestampDefinition</item> <item name="date" xsi:type="object">Magento\Framework\Setup\SchemaListenerDefinition\DateDefinition</item> diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Columns/StringBinary.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Columns/StringBinary.php index 58e6df1146300..4de198ae631f4 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Columns/StringBinary.php +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Columns/StringBinary.php @@ -11,7 +11,7 @@ /** * String or Binary column. - * Declared in SQL, like VARCHAR(L), BINARY(L) + * Declared in SQL, like CHAR(L), VARCHAR(L), BINARY(L) * where L - length. */ class StringBinary extends Column implements @@ -73,10 +73,9 @@ public function isNullable() } /** - * Return default value. - * Note: default value should be string. + * Return default value, Note: default value should be string. * - * @return string | null + * @return string|null */ public function getDefault() { diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/schema.xsd b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/schema.xsd index bb9136d8a9ae6..2fbab18161925 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/schema.xsd +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/schema.xsd @@ -20,6 +20,7 @@ <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/types/texts/longtext.xsd" /> <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/types/texts/mediumtext.xsd" /> <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/types/texts/varchar.xsd" /> + <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/types/texts/char.xsd" /> <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/types/texts/json.xsd" /> <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/types/binaries/blob.xsd" /> <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/types/binaries/mediumblob.xsd" /> diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/types/texts/char.xsd b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/types/texts/char.xsd new file mode 100644 index 0000000000000..27ec7852b3b8d --- /dev/null +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/types/texts/char.xsd @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"> + <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/types/column.xsd"/> + + <xs:complexType name="char"> + <xs:complexContent> + <xs:extension base="abstractColumnType"> + <xs:annotation> + <xs:documentation> + Here plain text can be persisted without trailing spaces. Length of this field can't be more than 255 characters + When CHAR values are retrieved, trailing spaces are removed unless the PAD_CHAR_TO_FULL_LENGTH SQL mode is enabled. + </xs:documentation> + </xs:annotation> + + <xs:attribute name="length"> + <xs:simpleType> + <xs:restriction base="xs:integer"> + <xs:maxInclusive value="255"/> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="default" type="xs:string" /> + <xs:attribute name="nullable" type="xs:boolean" /> + </xs:extension> + </xs:complexContent> + </xs:complexType> +</xs:schema> diff --git a/lib/internal/Magento/Framework/Setup/SchemaListenerDefinition/CharDefinition.php b/lib/internal/Magento/Framework/Setup/SchemaListenerDefinition/CharDefinition.php new file mode 100644 index 0000000000000..058ca69564871 --- /dev/null +++ b/lib/internal/Magento/Framework/Setup/SchemaListenerDefinition/CharDefinition.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Setup\SchemaListenerDefinition; + +/** + * Char type definition. + */ +class CharDefinition implements DefinitionConverterInterface +{ + private const DEFAULT_TEXT_LENGTH = 255; + + /** + * @inheritdoc + */ + public function convertToDefinition(array $definition) + { + return [ + 'xsi:type' => $definition['type'], + 'name' => $definition['name'], + 'length' => $definition['length'] ?? self::DEFAULT_TEXT_LENGTH, + 'default' => isset($definition['default']) ? (bool) $definition['default'] : null, + 'nullable' => $definition['nullable'] ?? true, + ]; + } +} From dce54cec4391c732f76830173da6b504cfa5c5e4 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Mon, 17 Feb 2020 15:24:40 +0400 Subject: [PATCH 089/369] Removed unnecessary check from the plugin --- .../Plugin/Wysiwyg/Images/Storage.php | 4 ---- .../Test/Unit/Plugin/Images/StorageTest.php | 17 ----------------- 2 files changed, 21 deletions(-) diff --git a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php index 7a4aa219cdf29..11331e4b9303f 100644 --- a/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/MediaGallery/Plugin/Wysiwyg/Images/Storage.php @@ -111,7 +111,6 @@ public function afterDeleteFile(StorageSubject $subject, StorageSubject $result, * * @return null * - * @throws CouldNotDeleteException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterDeleteDirectory(StorageSubject $subject, $result, $path) @@ -123,9 +122,6 @@ public function afterDeleteDirectory(StorageSubject $subject, $result, $path) try { $mediaDirectoryRead = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); $relativePath = $mediaDirectoryRead->getRelativePath($path); - if ($mediaDirectoryRead->isExist($relativePath) === false) { - throw new CouldNotDeleteException(__('Cannot remove assets, the provided path does not exist')); - } $this->deleteMediaAssetByDirectoryPath->execute($relativePath); } catch (ValidatorException $exception) { $this->logger->critical($exception); diff --git a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php b/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php index f57d6e9c7635b..4ac448733c47f 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Plugin/Images/StorageTest.php @@ -105,18 +105,6 @@ public function testAfterDeleteDirectory(string $path): void $result = $this->storagePlugin->afterDeleteDirectory($storageSubject, null, (int)$path); self::assertNull($result); break; - case self::NON_EXISTENT_PATH: - $directoryRead->expects($this->once()) - ->method('getRelativePath') - ->with($path) - ->willReturn($path); - $directoryRead->expects($this->once()) - ->method('isExist') - ->with($path) - ->willReturn(false); - self::expectException('Magento\Framework\Exception\CouldNotDeleteException'); - $this->storagePlugin->afterDeleteDirectory($storageSubject, null, $path); - break; case self::INVALID_PATH: $exception = new ValidatorException(__('Path cannot be used with directory')); $directoryRead->expects($this->once()) @@ -133,10 +121,6 @@ public function testAfterDeleteDirectory(string $path): void ->method('getRelativePath') ->with($path) ->willReturn($path); - $directoryRead->expects($this->once()) - ->method('isExist') - ->with($path) - ->willReturn(true); $this->deleteMediaAssetByDirectoryPath->expects($this->once()) ->method('execute') ->with($path); @@ -154,7 +138,6 @@ public function pathPathDataProvider(): array { return [ 'Non string path' => [2020], - 'Non-existent path' => [self::NON_EXISTENT_PATH], 'Invalid path' => [self::INVALID_PATH], 'Existent path' => [self::VALID_PATH] ]; From b0c0c59900b4ddf8d70cf2ae9c9ec12805d13908 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Mon, 17 Feb 2020 13:48:51 +0200 Subject: [PATCH 090/369] Cover MFTF test --- ...rSubCategoryWithoutRedirectActionGroup.xml | 21 +++++ .../Catalog/Test/Mftf/Data/CategoryData.xml | 16 ++++ ...roductRewriteUrlSubCategoryActionGroup.xml | 22 +++++ ...ateCategoryProductUrlRewriteConfigData.xml | 8 ++ .../AdminRewriteProductWithTwoStoreTest.xml | 80 +++++++++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml create mode 100644 app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml create mode 100644 app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml new file mode 100644 index 0000000000000..4f0b87937baa9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" extends="ChangeSeoUrlKeyForSubCategoryActionGroup"> + <annotations> + <description>Requires navigation to subcategory creation/edit. Updates the Search Engine Optimization with uncheck Redirect Checkbox .</description> + </annotations> + <arguments> + <argument name="value" type="string"/> + </arguments> + + <uncheckOption selector="{{AdminCategorySEOSection.UrlKeyRedirectCheckbox}}" stepKey="uncheckRedirectCheckbox" after="enterURLKey"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 6ffb4e1902424..e766a233c401c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -110,6 +110,22 @@ <data key="is_active">false</data> <data key="include_in_menu">false</data> </entity> + <entity name="_defaultCategoryDifferentUrlStore" type="category"> + <data key="name" unique="suffix">SimpleCategory</data> + <data key="name_lwr" unique="suffix">simplecategory</data> + <data key="is_active">true</data> + <data key="url_key_default_store" unique="suffix">default-simplecategory</data> + <data key="url_key_custom_store" unique="suffix">custom-simplecategory</data> + </entity> + <entity name="SimpleSubCategoryDifferentUrlStore" type="category"> + <data key="name" unique="suffix">SimpleSubCategory</data> + <data key="name_lwr" unique="suffix">simplesubcategory</data> + <data key="is_active">true</data> + <data key="url_key_default_store" unique="suffix">default-simplesubcategory</data> + <data key="url_key_custom_store" unique="suffix">custom-simplesubcategory</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id" /> + </entity> <!-- Category from file "prepared-for-sample-data.csv"--> <entity name="Gear" type="category"> <data key="name">Gear</data> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml new file mode 100644 index 0000000000000..8c0519d08545c --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertProductRewriteUrlSubCategoryActionGroup"> + <annotations> + <description>Validates that the provided Product Title is present on the Rewrite URL with a subcategory page.</description> + </annotations> + <arguments> + <argument name="category" defaultValue="_defaultCategory"/> + <argument name="subCategory" defaultValue="SimpleSubCategory"/> + <argument name="product" defaultValue="SimpleProduct" /> + </arguments> + + <amOnPage url="{{category.url_key_default_store}}/{{subCategory.url_key_default_store}}/{{product.urlKey}}2.html" stepKey="goToProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{product.name}}" stepKey="seeProductNameInStoreFront"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/GenerateCategoryProductUrlRewriteConfigData.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/GenerateCategoryProductUrlRewriteConfigData.xml index 10d2213b64717..9ce6d397a551b 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/GenerateCategoryProductUrlRewriteConfigData.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Data/GenerateCategoryProductUrlRewriteConfigData.xml @@ -19,4 +19,12 @@ <data key="label">No</data> <data key="value">0</data> </entity> + <entity name="EnableCategoriesPathProductUrls"> + <data key="path">catalog/seo/product_use_categories</data> + <data key="value">1</data> + </entity> + <entity name="DisableCategoriesPathProductUrls"> + <data key="path">catalog/seo/product_use_categories</data> + <data key="value">0</data> + </entity> </entities> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml new file mode 100644 index 0000000000000..2f421276b1889 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminRewriteProductWithTwoStoreTest"> + <annotations> + <title value="Rewriting URL of product"/> + <description value="Rewriting URL of product. Verify the full URL address"/> + <group value="CatalogUrlRewrite"/> + </annotations> + + <before> + <magentoCLI command="config:set {{EnableCategoriesPathProductUrls.path}} {{EnableCategoriesPathProductUrls.value}}" stepKey="enableUseCategoriesPath"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView" /> + <createData entity="_defaultCategoryDifferentUrlStore" stepKey="defaultCategory"/> + <createData entity="SimpleSubCategoryDifferentUrlStore" stepKey="subCategory"> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="simpleProduct"> + <requiredEntity createDataKey="subCategory"/> + </createData> + </before> + + <after> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"/> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="subCategory" stepKey="deleteSubCategory"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteNewRootCategory"/> + + <magentoCLI command="config:set {{DisableCategoriesPathProductUrls.path}} {{DisableCategoriesPathProductUrls.value}}" stepKey="disableUseCategoriesPath"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + + <actionGroup ref="NavigateToCreatedCategoryActionGroup" stepKey="navigateToCreatedDefaultCategory"> + <argument name="Category" value="$$defaultCategory$$"/> + </actionGroup> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchDefaultStoreViewForDefaultCategory"> + <argument name="storeView" value="_defaultStore.name"/> + </actionGroup> + <actionGroup ref="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForDefaultCategoryDefaultStore"> + <argument name="value" value="{{_defaultCategoryDifferentUrlStore.url_key_default_store}}"/> + </actionGroup> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchCustomStoreViewForDefaultCategory"> + <argument name="storeView" value="customStore.name"/> + </actionGroup> + <actionGroup ref="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForDefaultCategoryCustomStore"> + <argument name="value" value="{{_defaultCategoryDifferentUrlStore.url_key_custom_store}}"/> + </actionGroup> + + <actionGroup ref="NavigateToCreatedCategoryActionGroup" stepKey="navigateToCreatedSubCategory"> + <argument name="Category" value="$$subCategory$$"/> + </actionGroup> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchDefaultStoreViewForSubCategory"> + <argument name="storeView" value="_defaultStore.name"/> + </actionGroup> + <actionGroup ref="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForSubCategoryDefaultStore"> + <argument name="value" value="{{SimpleSubCategoryDifferentUrlStore.url_key_default_store}}"/> + </actionGroup> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchCustomStoreViewForSubCategory"> + <argument name="storeView" value="customStore.name"/> + </actionGroup> + <actionGroup ref="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForSubCategoryCustomStore"> + <argument name="value" value="{{SimpleSubCategoryDifferentUrlStore.url_key_custom_store}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrl"> + <argument name="category" value="_defaultCategoryDifferentUrlStore"/> + <argument name="subCategory" value="SimpleSubCategoryDifferentUrlStore" /> + <argument name="product" value="SimpleProduct" /> + </actionGroup> + + </test> +</tests> From 531e1b596c00eddd4af774c8d3a5ea998f409600 Mon Sep 17 00:00:00 2001 From: solwininfotech <info@solwininfotech.com> Date: Mon, 17 Feb 2020 17:58:01 +0530 Subject: [PATCH 091/369] resolved merge conflict --- app/code/Magento/Customer/etc/adminhtml/system.xml | 3 +++ app/code/Magento/Customer/etc/config.xml | 1 + 2 files changed, 4 insertions(+) diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml index 6234c2a84ac83..fca625d847a1d 100644 --- a/app/code/Magento/Customer/etc/adminhtml/system.xml +++ b/app/code/Magento/Customer/etc/adminhtml/system.xml @@ -86,6 +86,9 @@ <comment>To show VAT number on Storefront, set Show VAT Number on Storefront option to Yes.</comment> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> + <field id="email_domain" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Default Email Domain</label> + </field> <field id="email_template" translate="label comment" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Default Welcome Email</label> <comment>Email template chosen based on theme fallback when "Default" option is selected.</comment> diff --git a/app/code/Magento/Customer/etc/config.xml b/app/code/Magento/Customer/etc/config.xml index db04ad7c94963..ab2020580a2eb 100644 --- a/app/code/Magento/Customer/etc/config.xml +++ b/app/code/Magento/Customer/etc/config.xml @@ -23,6 +23,7 @@ <email_confirmed_template>customer_create_account_email_confirmed_template</email_confirmed_template> <viv_disable_auto_group_assign_default>0</viv_disable_auto_group_assign_default> <vat_frontend_visibility>0</vat_frontend_visibility> + <email_domain>example.com</email_domain> <generate_human_friendly_id>0</generate_human_friendly_id> </create_account> <default> From 28fa34531f4c7cd4bcad46500ba027b9b58ff297 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Mon, 17 Feb 2020 17:11:51 +0200 Subject: [PATCH 092/369] Minor change --- ...tProductRewriteUrlSubCategoryActionGroup.xml | 5 ++--- .../AdminRewriteProductWithTwoStoreTest.xml | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml index 8c0519d08545c..4e72c7f704866 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml @@ -11,12 +11,11 @@ <description>Validates that the provided Product Title is present on the Rewrite URL with a subcategory page.</description> </annotations> <arguments> - <argument name="category" defaultValue="_defaultCategory"/> - <argument name="subCategory" defaultValue="SimpleSubCategory"/> + <argument name="category" type="string" defaultValue="simplecategory"/> <argument name="product" defaultValue="SimpleProduct" /> </arguments> - <amOnPage url="{{category.url_key_default_store}}/{{subCategory.url_key_default_store}}/{{product.urlKey}}2.html" stepKey="goToProductPage"/> + <amOnPage url="{{category}}/{{product.urlKey}}2.html" stepKey="goToProductPage"/> <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{product.name}}" stepKey="seeProductNameInStoreFront"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml index 2f421276b1889..ac9b3f573deba 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml @@ -31,9 +31,8 @@ <after> <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"/> <actionGroup ref="logout" stepKey="logout"/> - <deleteData createDataKey="subCategory" stepKey="deleteSubCategory"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> <deleteData createDataKey="defaultCategory" stepKey="deleteNewRootCategory"/> - <magentoCLI command="config:set {{DisableCategoriesPathProductUrls.path}} {{DisableCategoriesPathProductUrls.value}}" stepKey="disableUseCategoriesPath"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> </after> @@ -70,9 +69,17 @@ <argument name="value" value="{{SimpleSubCategoryDifferentUrlStore.url_key_custom_store}}"/> </actionGroup> - <actionGroup ref="StorefrontAssertProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrl"> - <argument name="category" value="_defaultCategoryDifferentUrlStore"/> - <argument name="subCategory" value="SimpleSubCategoryDifferentUrlStore" /> + <actionGroup ref="StorefrontAssertProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrlDefaultStore"> + <argument name="category" value="{{_defaultCategoryDifferentUrlStore.url_key_default_store}}"/> + <argument name="product" value="SimpleProduct" /> + </actionGroup> + + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStore"> + <argument name="storeView" value="customStore" /> + </actionGroup> + + <actionGroup ref="StorefrontAssertProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrlCustomStore"> + <argument name="category" value="{{_defaultCategoryDifferentUrlStore.url_key_custom_store}}"/> <argument name="product" value="SimpleProduct" /> </actionGroup> From a49163a56caa223b3c020e34f1f169fd76f61373 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <p.bystritsky@yandex.ru> Date: Tue, 18 Feb 2020 16:00:46 +0200 Subject: [PATCH 093/369] security-package/issues/115: Fix AdminSessionsManagerTest and AuthSessionTest. --- .../Magento/Security/Model/AdminSessionsManagerTest.php | 3 +++ .../Magento/Security/Model/Plugin/AuthSessionTest.php | 1 + 2 files changed, 4 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php index a201dbfdf1b03..eb10b34f5482b 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/AdminSessionsManagerTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Security\Model; +/** + * @magentoAppArea adminhtml + */ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase { /** diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index 52268dc96d8a3..6509e3af1050a 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -6,6 +6,7 @@ namespace Magento\Security\Model\Plugin; /** + * @magentoAppArea adminhtml * @magentoAppIsolation enabled */ class AuthSessionTest extends \PHPUnit\Framework\TestCase From 82b0b304e90213f10688eca9d0af77086dc4955c Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Tue, 18 Feb 2020 15:07:46 -0600 Subject: [PATCH 094/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Tests refactor for <executeSelenium> (removal) --- ...leteAllProductCustomOptionsActionGroup.xml | 26 ------------ ...leteDefaultCategoryChildrenActionGroup.xml | 35 ---------------- ...UpdateProductAttributesGlobalScopeTest.xml | 1 - ...ductGridFilteringByCustomAttributeTest.xml | 1 - ...dminRemoveCustomOptionsFromProductTest.xml | 10 ++++- ...rontCatalogNavigationMenuUIDesktopTest.xml | 4 +- .../DeleteAllExportedFilesActionGroup.xml | 32 --------------- .../Test/AdminExportBundleProductTest.xml | 10 ++--- ...portGroupedProductWithSpecialPriceTest.xml | 10 ++--- ...mportConfigurableProductWithImagesTest.xml | 13 +++--- ...figurableProductsWithCustomOptionsTest.xml | 10 ++--- ...igurableProductsWithAssignedImagesTest.xml | 10 ++--- ...ableProductAssignedToCustomWebsiteTest.xml | 10 ++--- ...rtSimpleProductWithCustomAttributeTest.xml | 10 ++--- ...inCatalogPriceRuleDeleteAllActionGroup.xml | 41 ------------------- ...hipArePersistedUnderLongTermCookieTest.xml | 7 ++-- .../StorefrontInactiveCatalogRuleTest.xml | 5 ++- ...ProductWithAssignedSimpleProducts2Test.xml | 5 ++- ...ForConfigurableProductWithOptions2Test.xml | 18 +++++++- ...ConfigurableWithCatalogRuleAppliedTest.xml | 5 ++- ...dminDeleteStoreViewIfExistsActionGroup.xml | 34 --------------- ...tipleStoreviewsDuringProductImportTest.xml | 28 ++++--------- 22 files changed, 84 insertions(+), 241 deletions(-) delete mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteAllProductCustomOptionsActionGroup.xml delete mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml delete mode 100644 app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DeleteAllExportedFilesActionGroup.xml delete mode 100644 app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml delete mode 100644 app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewIfExistsActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteAllProductCustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteAllProductCustomOptionsActionGroup.xml deleted file mode 100644 index 103c25b2c6f50..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteAllProductCustomOptionsActionGroup.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminDeleteAllProductCustomOptionsActionGroup"> - <conditionalClick selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" dependentSelector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" visible="true" stepKey="expandContentTab"/> - <waitForPageLoad time="10" stepKey="waitCustomizableOptionsTabOpened"/> - <executeInSelenium function="function($webdriver) use ($I) { - $buttons = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('[data-index=\'options\'] [data-index=\'delete_button\']')); - while(!empty($buttons)) { - $button = reset($buttons); - $I->executeJS('arguments[0].scrollIntoView(false)', [$button]); - $button->click(); - $webdriver->wait()->until(\Facebook\WebDriver\WebDriverExpectedCondition::stalenessOf($button)); - $buttons = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('[data-index=\'options\'] [data-index=\'delete_button\']')); - } - }" stepKey="deleteCustomOptions"/> - <dontSeeElement selector="{{AdminProductCustomizableOptionsSection.customOptionButtonDelete}}" stepKey="assertNoCustomOptions"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml deleted file mode 100644 index 2fb4e0e05887a..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="DeleteDefaultCategoryChildrenActionGroup"> - <annotations> - <description>Deletes all children categories of Default Root Category.</description> - </annotations> - - <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToAdminCategoryPage"/> - <executeInSelenium function="function ($webdriver) use ($I) { - $children = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., - \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a')); - while (!empty($children)) { - $I->click('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., - \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a'); - $I->waitForPageLoad(30); - $I->click('#delete'); - $I->waitForElementVisible('aside.confirm .modal-footer button.action-accept'); - $I->click('aside.confirm .modal-footer button.action-accept'); - $I->waitForPageLoad(30); - $I->waitForElementVisible('#messages div.message-success', 30); - $I->see('You deleted the category.', '#messages div.message-success'); - $children = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., - \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a')); - } - }" stepKey="deleteAllChildCategories"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml index 71873fe5b0960..9d8457f909f24 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml @@ -21,7 +21,6 @@ </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteAllProductsUsingProductGridActionGroup" stepKey="deleteAllProducts"/> <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView" /> <createData entity="_defaultCategory" stepKey="createCategory"/> <createData entity="ApiSimpleProduct" stepKey="createProductOne"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml index a4986117380ff..8fc82ebf23c35 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml @@ -22,7 +22,6 @@ <before> <!--Login as admin and delete all products --> <actionGroup ref="LoginAsAdmin" stepKey="login"/> - <actionGroup ref="DeleteAllProductsUsingProductGridActionGroup" stepKey="deleteAllProducts"/> <!--Create dropdown product attribute--> <createData entity="productDropDownAttribute" stepKey="createDropdownAttribute"/> <!--Create attribute options--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml index fb54b0b601d85..cd59cc90a5efb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveCustomOptionsFromProductTest.xml @@ -84,7 +84,15 @@ <argument name="option" value="ProductOptionFieldSecond"/> </actionGroup> <!-- Delete All options and See no more options present on the page --> - <actionGroup ref="AdminDeleteAllProductCustomOptionsActionGroup" stepKey="deleteAllCustomOptions"/> + <actionGroup ref="AdminDeleteProductCustomOptionActionGroup" stepKey="deleteCustomOptionField"> + <argument name="option" value="ProductOptionField"/> + </actionGroup> + <actionGroup ref="AdminDeleteProductCustomOptionActionGroup" stepKey="deleteCustomOptionFile2"> + <argument name="option" value="ProductOptionFileSecond"/> + </actionGroup> + <actionGroup ref="AdminDeleteProductCustomOptionActionGroup" stepKey="deleteCustomOptionFieldSecond"> + <argument name="option" value="ProductOptionFieldSecond"/> + </actionGroup> <!-- Product successfully saved and it has no options --> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductWithoutCustomOptions"/> <actionGroup ref="AdminAssertProductHasNoCustomOptionsActionGroup" stepKey="assertNoCustomOptions"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml index 22ec0048497fa..e618adf80ab8b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml @@ -21,10 +21,8 @@ <before> <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteDefaultCategoryChildrenActionGroup" stepKey="deleteRootCategoryChildren"/> </before> <after> - <actionGroup ref="DeleteDefaultCategoryChildrenActionGroup" stepKey="deleteRootCategoryChildren"/> <actionGroup ref="AdminChangeStorefrontThemeActionGroup" stepKey="changeThemeToDefault"> <argument name="theme" value="{{MagentoLumaTheme.name}}"/> </actionGroup> @@ -159,7 +157,7 @@ </actionGroup> <!-- Submenu appears rightward --> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level0')}}" stepKey="assertTopLevelMenuLeftDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuLeftDirection('level0')}}" stepKey="assertTopLevelMenuLeftDirection"/> <!-- Nested level 1 & 5 --> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelTwoBlank.name$$)}}" stepKey="hoverCategoryLevelTwo"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DeleteAllExportedFilesActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DeleteAllExportedFilesActionGroup.xml deleted file mode 100644 index aa8fad2a1d575..0000000000000 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DeleteAllExportedFilesActionGroup.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="DeleteAllExportedFilesActionGroup"> - <amOnPage url="{{AdminExportIndexPage.url}}" stepKey="goToExportIndexPage"/> - <executeInSelenium - function=" - function ($webdriver) use ($I) { - $buttons = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//tr[@data-repeat-index=\'0\']//button')); - while(!empty($buttons)) { - $buttons[0]->click(); - $I->waitForElementVisible('//tr[@data-repeat-index=\'0\']//a[text()=\'Delete\']', 10); - $deleteButton = $webdriver->findElement(\Facebook\WebDriver\WebDriverBy::xpath('//tr[@data-repeat-index=\'0\']//a[text()=\'Delete\']')); - $deleteButton->click(); - $I->waitForElementVisible('.modal-popup.confirm button.action-accept', 10); - $I->click('.modal-popup.confirm button.action-accept'); - $I->waitForPageLoad(60); - $buttons = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//tr[@data-repeat-index=\'0\']//button')); - } - }" - stepKey="deleteAllExportedFilesOneByOne"/> - <waitForElementVisible selector="{{AdminDataGridTableSection.dataGridEmpty}}" stepKey="waitDataGridEmptyMessageAppears"/> - <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml index 7fb4d8b025b07..146f6cc948f5d 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml @@ -86,9 +86,13 @@ <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteAllExportedFilesActionGroup" stepKey="clearExportedFilesList"/> </before> <after> + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> + <!-- Delete products creations --> <deleteData createDataKey="createDynamicBundleProduct" stepKey="deleteDynamicBundleProduct"/> <deleteData createDataKey="firstSimpleProductForDynamic" stepKey="deleteFirstSimpleProductForDynamic"/> @@ -101,10 +105,6 @@ <deleteData createDataKey="secondSimpleProductForFixedWithAttribute" stepKey="deleteSecondSimpleProductForFixedWithAttribute"/> <deleteData createDataKey="createProductAttribute" stepKey="deleteProductAttribute"/> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml index d9b93196db060..b20f5e3802e41 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml @@ -54,9 +54,13 @@ <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteAllExportedFilesActionGroup" stepKey="clearExportedFilesList"/> </before> <after> + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> + <!-- Deleted created products --> <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> @@ -65,10 +69,6 @@ <!-- Delete category --> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml index c27a1716e84e5..f898935657dee 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -128,27 +128,30 @@ <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteAllExportedFilesActionGroup" stepKey="clearExportedFilesList"/> </before> <after> + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> + <!-- Remove downloadable domains --> <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> <!-- Delete created data --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFisrtSimpleProduct"/> <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> <deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/> <deleteData createDataKey="createExportImportConfigurableProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createExportImportConfigurableProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createExportImportCategory" stepKey="deleteExportImportCategory"/> <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> <deleteData createDataKey="createConfigSecondChildProduct" stepKey="deleteConfigSecondChildProduct"/> - <deleteData createDataKey="createExportImportConfigurableProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createConfigurableProduct" stepKey="deleteConfigurableProduct"/> <deleteData createDataKey="createConfigChildProduct" stepKey="deleteConfigChildProduct"/> <deleteData createDataKey="createConfigProductAttr" stepKey="deleteConfigProductAttr"/> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <deleteData createDataKey="createExportImportCategory" stepKey="deleteExportImportCategory"/> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGridColumnsInitial"/> <!-- Admin logout--> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml index a55e92d64ce00..9d721fe44efa3 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml @@ -79,9 +79,12 @@ <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteAllExportedFilesActionGroup" stepKey="clearExportedFilesList"/> </before> <after> + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> <!-- Delete configurable product creation --> <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> @@ -89,10 +92,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> + <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml index 7131fe41ea5ec..2ba0ac2fb6c93 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml @@ -95,9 +95,13 @@ <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteAllExportedFilesActionGroup" stepKey="clearExportedFilesList"/> </before> <after> + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> + <!-- Delete configurable product creation --> <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> @@ -105,10 +109,6 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml index ea27d61e3b00c..97dc8e052d190 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml @@ -77,9 +77,13 @@ <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteAllExportedFilesActionGroup" stepKey="clearExportedFilesList"/> </before> <after> + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> + <!-- Delete simple product --> <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> @@ -90,10 +94,6 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml index 8553fb8a2cf7e..ff480b60a0fbf 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml @@ -34,18 +34,18 @@ <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteAllExportedFilesActionGroup" stepKey="clearExportedFilesList"/> </before> <after> + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="rowIndex" value="0"/> + </actionGroup> + <!-- Delete product creations --> <deleteData createDataKey="createSimpleProductWithCustomAttributeSet" stepKey="deleteSimpleProductWithCustomAttributeSet"/> <deleteData createDataKey="createAttributeSet" stepKey="deleteAttributeSet"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml deleted file mode 100644 index 5860137c1ab8d..0000000000000 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml +++ /dev/null @@ -1,41 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminCatalogPriceRuleDeleteAllActionGroup"> - <annotations> - <description>Open Catalog Price Rule grid and delete all rules one by one. Need to avoid interference with other tests that test catalog price rules.</description> - </annotations> - <amOnPage url="{{AdminCatalogPriceRuleGridPage.url}}" stepKey="goToAdminCatalogPriceRuleGridPage"/> - <!-- It sometimes is loading too long for default 10s --> - <waitForPageLoad time="60" stepKey="waitForPageFullyLoaded"/> - <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingFilters"/> - <executeInSelenium - function=" - function ($webdriver) use ($I) { - $rows = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('table.data-grid tbody tr[data-role=row]:not(.data-grid-tr-no-data):nth-of-type(1)')); - while(!empty($rows)) { - $rows[0]->click(); - $I->waitForPageLoad(30); - $I->click('#delete'); - $I->waitForPageLoad(30); - $I->waitForElementVisible('aside.confirm .modal-footer button.action-accept', 10); - $I->waitForPageLoad(60); - $I->click('aside.confirm .modal-footer button.action-accept'); - $I->waitForPageLoad(60); - $I->waitForLoadingMaskToDisappear(); - $I->waitForElementVisible('#messages div.message-success', 10); - $I->see('You deleted the rule.', '#messages div.message-success'); - $rows = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('table.data-grid tbody tr[data-role=row]:not(.data-grid-tr-no-data):nth-of-type(1)')); - } - }" - stepKey="deleteAllCatalogPriceRulesOneByOne"/> - <waitForElementVisible selector="{{AdminDataGridTableSection.dataGridEmpty}}" stepKey="waitDataGridEmptyMessageAppears"/> - <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml index 102054c315f4c..1c21301196b73 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml @@ -27,10 +27,7 @@ </createData> <createData entity="Simple_US_Customer" stepKey="createCustomer"/> - <!--Delete all Catalog Price Rule if exist--> <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> - <!--Create Catalog Rule--> <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingFirstPriceRule"/> @@ -53,7 +50,9 @@ <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> <!-- Delete the rule --> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteCatalogPriceRule"> + <argument name="ruleName" value="{{_defaultCatalogRule.name}}"/> + </actionGroup> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml index 45b0c1bcaa5a0..1198ca4fcac84 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml @@ -25,7 +25,6 @@ <requiredEntity createDataKey="createCategory"/> </createData> <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingFirstPriceRule"/> <actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForFirstPriceRule"> @@ -41,7 +40,9 @@ <after> <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteCatalogPriceRule"> + <argument name="ruleName" value="{{_defaultCatalogRule.name}}"/> + </actionGroup> <actionGroup ref="logout" stepKey="logoutFromAdmin"/> </after> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml index 80ff2a447052c..6f7b8654d9402 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml @@ -145,7 +145,6 @@ <!-- Login as Admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> </before> <after> <!-- Delete created data --> @@ -169,7 +168,9 @@ <deleteData createDataKey="customerGroup" stepKey="deleteCustomerGroup"/> <!-- Delete created price rules --> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteCatalogPriceRule"> + <argument name="ruleName" value="{{_defaultCatalogRule.name}}"/> + </actionGroup> <!-- Admin log out --> <actionGroup ref="logout" stepKey="logoutFromAdmin"/> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml index 2a5786b38107f..bbc076867af9d 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml @@ -93,7 +93,6 @@ <!-- Login as Admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> </before> <after> <!-- Delete created data --> @@ -104,7 +103,16 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="simpleCategory" stepKey="deleteCategory"/> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + <!-- Delete created price rules --> + <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteFirstCatalogPriceRule"> + <argument name="ruleName" value="{{CatalogRuleToFixed.name}}"/> + </actionGroup> + <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteSecondCatalogPriceRule"> + <argument name="ruleName" value="{{CatalogRuleWithoutDiscount.name}}"/> + </actionGroup> + <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteThirdCatalogPriceRule"> + <argument name="ruleName" value="{{_defaultCatalogRule.name}}"/> + </actionGroup> <actionGroup ref="logout" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> @@ -114,6 +122,8 @@ <!-- Create price rule for first configurable product option --> <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingFirstPriceRule"/> <actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForFirstPriceRule"> + <argument name="name" value="{{CatalogRuleToFixed.name}}"/> + <argument name="description" value="{{CatalogRuleToFixed.description}}"/> <argument name="groups" value="'NOT LOGGED IN'"/> </actionGroup> <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="fillConditionsForFirstPriceRule"> @@ -131,6 +141,8 @@ <!-- Create price rule for second configurable product option --> <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingThirdPriceRule"/> <actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForThirdPriceRule"> + <argument name="name" value="{{_defaultCatalogRule.name}}"/> + <argument name="description" value="{{_defaultCatalogRule.description}}"/> <argument name="groups" value="'NOT LOGGED IN'"/> </actionGroup> <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="fillConditionsForThirdPriceRule"> @@ -145,6 +157,8 @@ <!-- Create price rule for third configurable product option --> <actionGroup ref="AdminOpenNewCatalogPriceRuleFormPageActionGroup" stepKey="startCreatingSecondPriceRule"/> <actionGroup ref="AdminCatalogPriceRuleFillMainInfoActionGroup" stepKey="fillMainInfoForSecondPriceRule"> + <argument name="name" value="{{CatalogRuleWithoutDiscount.name}}"/> + <argument name="description" value="{{CatalogRuleWithoutDiscount.description}}"/> <argument name="groups" value="'NOT LOGGED IN'"/> </actionGroup> <actionGroup ref="CreateCatalogPriceRuleConditionWithAttributeAndOptionActionGroup" stepKey="fillConditionsForSecondPriceRule"> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml index ce5bad8c333a6..6d4a99509d2d3 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml @@ -98,7 +98,6 @@ <actionGroup ref="ChangeUseForPromoRuleConditionsProductAttributeActionGroup" stepKey="changeUseForPromoRuleConditionsProductAttributeToYes"> <argument name="option" value="Yes"/> </actionGroup> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllRules"/> <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> </before> @@ -120,7 +119,9 @@ <actionGroup ref="ChangeUseForPromoRuleConditionsProductAttributeActionGroup" stepKey="changeUseForPromoRuleConditionsProductAttributeToNo"> <argument name="option" value="No"/> </actionGroup> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllRules"/> + <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteCatalogPriceRule"> + <argument name="ruleName" value="{{_defaultCatalogRule.name}}"/> + </actionGroup> <actionGroup ref="logout" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewIfExistsActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewIfExistsActionGroup.xml deleted file mode 100644 index 6ebf72a893c04..0000000000000 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewIfExistsActionGroup.xml +++ /dev/null @@ -1,34 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminDeleteStoreViewIfExistsActionGroup" extends="AdminSearchStoreViewByNameActionGroup"> - <annotations> - <description>EXTENDS: AdminSearchStoreViewByNameActionGroup. Goes to the Admin Stores grid page. Deletes the provided Store (if exists) without creating a Backup. Validates that the Success Message is present and correct.</description> - </annotations> - - <executeInSelenium function="function($webdriver) use ($I) { - $items = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::cssSelector('.col-store_title>a')); - if(!empty($items)) { - $I->click('.col-store_title>a'); - $I->waitForPageLoad(10); - $I->click('#delete'); - $I->waitForPageLoad(30); - $I->selectOption('select#store_create_backup', 'No'); - $I->click('#delete'); - $I->waitForPageLoad(30); - $I->waitForElementVisible('aside.confirm .modal-title', 10); - $I->click('aside.confirm .modal-footer button.action-accept'); - $I->waitForPageLoad(60); - $I->waitForElementVisible('#messages div.message-success', 10); - $I->see('You deleted the store view.', '#messages div.message-success'); - } - }" after="clickSearchButton" stepKey="deleteStoreViewIfExists"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml index 77fb2b5285ac3..619a227af244d 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml @@ -22,12 +22,6 @@ <field key="name">category-admin</field> </createData> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="AdminDeleteStoreViewIfExistsActionGroup" stepKey="deleteENStoreViewIfExists"> - <argument name="storeViewName" value="{{customStoreENNotUnique.name}}"/> - </actionGroup> - <actionGroup ref="AdminDeleteStoreViewIfExistsActionGroup" stepKey="deleteNLStoreViewIfExists"> - <argument name="storeViewName" value="{{customStoreNLNotUnique.name}}"/> - </actionGroup> <!-- Create Store View EN --> <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewEn"> <argument name="customStore" value="customStoreENNotUnique"/> @@ -39,11 +33,11 @@ </before> <after> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <actionGroup ref="AdminDeleteStoreViewIfExistsActionGroup" stepKey="deleteStoreViewEn"> - <argument name="storeViewName" value="{{customStoreENNotUnique.name}}"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreENNotUnique"/> </actionGroup> - <actionGroup ref="AdminDeleteStoreViewIfExistsActionGroup" stepKey="deleteStoreViewNl"> - <argument name="storeViewName" value="{{customStoreNLNotUnique.name}}"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewNl"> + <argument name="customStore" value="customStoreNLNotUnique"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearStoreFilters"/> <actionGroup ref="DeleteProductByNameActionGroup" stepKey="deleteImportedProduct"> @@ -141,12 +135,6 @@ <field key="name">category-admin</field> </createData> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="AdminDeleteStoreViewIfExistsActionGroup" stepKey="deleteENStoreViewIfExists"> - <argument name="storeViewName" value="{{customStoreENNotUnique.name}}"/> - </actionGroup> - <actionGroup ref="AdminDeleteStoreViewIfExistsActionGroup" stepKey="deleteNLStoreViewIfExists"> - <argument name="storeViewName" value="{{customStoreNLNotUnique.name}}"/> - </actionGroup> <!-- Create Store View EN --> <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewEn"> <argument name="customStore" value="customStoreENNotUnique"/> @@ -163,11 +151,11 @@ </before> <after> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <actionGroup ref="AdminDeleteStoreViewIfExistsActionGroup" stepKey="deleteStoreViewEn"> - <argument name="storeViewName" value="{{customStoreENNotUnique.name}}"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreENNotUnique"/> </actionGroup> - <actionGroup ref="AdminDeleteStoreViewIfExistsActionGroup" stepKey="deleteStoreViewNl"> - <argument name="storeViewName" value="{{customStoreNLNotUnique.name}}"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewNl"> + <argument name="customStore" value="customStoreNLNotUnique"/> </actionGroup> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearStoreGridFilters"/> <actionGroup ref="DeleteProductByNameActionGroup" stepKey="deleteImportedProduct"> From 684b951cb29ad0ecb0c6186cbcbcd1fbc25b51b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Mon, 17 Feb 2020 23:20:45 +0100 Subject: [PATCH 095/369] Fix #20309 - URL Rewrites redirect loop --- .../Magento/UrlRewrite/Controller/Router.php | 40 ++- .../UrlRewrite/Model/Storage/DbStorage.php | 13 +- .../Test/Unit/Controller/RouterTest.php | 308 +++++++++++------- .../Test/Unit/Model/Storage/DbStorageTest.php | 303 ++++++++--------- 4 files changed, 360 insertions(+), 304 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Controller/Router.php b/app/code/Magento/UrlRewrite/Controller/Router.php index 0525621b6a20e..edefbb5f4ba3a 100644 --- a/app/code/Magento/UrlRewrite/Controller/Router.php +++ b/app/code/Magento/UrlRewrite/Controller/Router.php @@ -3,15 +3,22 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\UrlRewrite\Controller; +use Magento\Framework\App\Action\Forward; use Magento\Framework\App\Action\Redirect; +use Magento\Framework\App\ActionFactory; use Magento\Framework\App\ActionInterface; use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\App\RequestInterface; use Magento\Framework\App\Response\Http as HttpResponse; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\App\RouterInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\UrlInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\UrlRewrite\Controller\Adminhtml\Url\Rewrite; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; @@ -21,10 +28,10 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Router implements \Magento\Framework\App\RouterInterface +class Router implements RouterInterface { /** - * @var \Magento\Framework\App\ActionFactory + * @var ActionFactory */ protected $actionFactory; @@ -34,7 +41,7 @@ class Router implements \Magento\Framework\App\RouterInterface protected $url; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $storeManager; @@ -49,17 +56,17 @@ class Router implements \Magento\Framework\App\RouterInterface protected $urlFinder; /** - * @param \Magento\Framework\App\ActionFactory $actionFactory + * @param ActionFactory $actionFactory * @param UrlInterface $url - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Framework\App\ResponseInterface $response + * @param StoreManagerInterface $storeManager + * @param ResponseInterface $response * @param UrlFinderInterface $urlFinder */ public function __construct( - \Magento\Framework\App\ActionFactory $actionFactory, + ActionFactory $actionFactory, UrlInterface $url, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\App\ResponseInterface $response, + StoreManagerInterface $storeManager, + ResponseInterface $response, UrlFinderInterface $urlFinder ) { $this->actionFactory = $actionFactory; @@ -83,24 +90,25 @@ public function match(RequestInterface $request) $this->storeManager->getStore()->getId() ); - if ($rewrite === null) { - //No rewrite rule matching current URl found, continuing with - //processing of this URL. + if ($rewrite === null || $rewrite->getRequestPath() === $rewrite->getTargetPath()) { + // Either no rewrite rule matching current URl found or found one with request path equal to + // target path, continuing with processing of this URL. return null; } + if ($rewrite->getRedirectType()) { - //Rule requires the request to be redirected to another URL - //and cannot be processed further. + // Rule requires the request to be redirected to another URL + // and cannot be processed further. return $this->processRedirect($request, $rewrite); } - //Rule provides actual URL that can be processed by a controller. + // Rule provides actual URL that can be processed by a controller. $request->setAlias( UrlInterface::REWRITE_REQUEST_PATH_ALIAS, $rewrite->getRequestPath() ); $request->setPathInfo('/' . $rewrite->getTargetPath()); return $this->actionFactory->create( - \Magento\Framework\App\Action\Forward::class + Forward::class ); } diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index f0e94e8379ad2..f187408d45a9d 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -138,17 +138,22 @@ private function extractMostRelevantUrlRewrite(string $requestPath, array $urlRe { $prioritizedUrlRewrites = []; foreach ($urlRewrites as $urlRewrite) { + $urlRewriteRequestPath = $urlRewrite[UrlRewrite::REQUEST_PATH]; + $urlRewriteTargetPath = $urlRewrite[UrlRewrite::TARGET_PATH]; switch (true) { - case $urlRewrite[UrlRewrite::REQUEST_PATH] === $requestPath: + case rtrim($urlRewriteRequestPath, '/') === rtrim($urlRewriteTargetPath, '/'): + $priority = 99; + break; + case $urlRewriteRequestPath === $requestPath: $priority = 1; break; - case $urlRewrite[UrlRewrite::REQUEST_PATH] === urldecode($requestPath): + case $urlRewriteRequestPath === urldecode($requestPath): $priority = 2; break; - case rtrim($urlRewrite[UrlRewrite::REQUEST_PATH], '/') === rtrim($requestPath, '/'): + case rtrim($urlRewriteRequestPath, '/') === rtrim($requestPath, '/'): $priority = 3; break; - case rtrim($urlRewrite[UrlRewrite::REQUEST_PATH], '/') === rtrim(urldecode($requestPath), '/'): + case rtrim($urlRewriteRequestPath, '/') === rtrim(urldecode($requestPath), '/'): $priority = 4; break; default: diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php index c67f3f400b007..7038e75f16456 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php @@ -6,11 +6,19 @@ namespace Magento\UrlRewrite\Test\Unit\Controller; use Magento\Framework\App\Action\Forward; +use Magento\Framework\App\Action\Redirect; +use Magento\Framework\App\ActionFactory; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\ResponseInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\UrlInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\UrlRewrite\Controller\Router; +use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\Store\Model\Store; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Zend\Stdlib\ParametersInterface; /** @@ -18,15 +26,15 @@ * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class RouterTest extends \PHPUnit\Framework\TestCase +class RouterTest extends TestCase { /** - * @var \Magento\UrlRewrite\Controller\Router + * @var Router */ private $router; /** - * @var \Magento\Framework\App\ActionFactory|MockObject + * @var ActionFactory|MockObject */ private $actionFactory; @@ -36,7 +44,7 @@ class RouterTest extends \PHPUnit\Framework\TestCase private $url; /** - * @var \Magento\Store\Model\StoreManagerInterface|MockObject + * @var StoreManagerInterface|MockObject */ private $storeManager; @@ -46,12 +54,12 @@ class RouterTest extends \PHPUnit\Framework\TestCase private $store; /** - * @var \Magento\Framework\App\ResponseInterface|MockObject + * @var ResponseInterface|MockObject */ private $response; /** - * @var \Magento\Framework\App\RequestInterface|MockObject + * @var RequestInterface|MockObject */ private $request; @@ -61,7 +69,7 @@ class RouterTest extends \PHPUnit\Framework\TestCase private $requestQuery; /** - * @var \Magento\UrlRewrite\Model\UrlFinderInterface|MockObject + * @var UrlFinderInterface|MockObject */ private $urlFinder; @@ -71,24 +79,24 @@ class RouterTest extends \PHPUnit\Framework\TestCase protected function setUp() { $objectManager = new ObjectManager($this); - $this->actionFactory = $this->createMock(\Magento\Framework\App\ActionFactory::class); + $this->actionFactory = $this->createMock(ActionFactory::class); $this->url = $this->createMock(UrlInterface::class); - $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); $this->response = $this->createPartialMock( - \Magento\Framework\App\ResponseInterface::class, + ResponseInterface::class, ['setRedirect', 'sendResponse'] ); $this->requestQuery = $this->createMock(ParametersInterface::class); $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) ->disableOriginalConstructor()->getMock(); $this->request->method('getQuery')->willReturn($this->requestQuery); - $this->urlFinder = $this->createMock(\Magento\UrlRewrite\Model\UrlFinderInterface::class); + $this->urlFinder = $this->createMock(UrlFinderInterface::class); $this->store = $this->getMockBuilder( Store::class )->disableOriginalConstructor()->getMock(); $this->router = $objectManager->getObject( - \Magento\UrlRewrite\Controller\Router::class, + Router::class, [ 'actionFactory' => $this->actionFactory, 'url' => $this->url, @@ -104,9 +112,14 @@ protected function setUp() */ public function testNoRewriteExist() { - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue(null)); - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); - $this->store->expects($this->any())->method('getId')->will($this->returnValue('current-store-id')); + $this->request->method('getPathInfo') + ->willReturn(''); + $this->urlFinder->method('findOneByData') + ->willReturn(null); + $this->storeManager->method('getStore') + ->willReturn($this->store); + $this->store->method('getId') + ->willReturn(1); $this->assertNull($this->router->match($this->request)); } @@ -118,55 +131,43 @@ public function testRewriteAfterStoreSwitcher() { $initialRequestPath = 'request-path'; $newRequestPath = 'new-request-path'; + $newTargetPath = 'new-target-path'; $oldStoreAlias = 'old-store'; $oldStoreId = 'old-store-id'; $currentStoreId = 'current-store-id'; $rewriteEntityType = 'entity-type'; $rewriteEntityId = 42; - $this->request - ->expects($this->any()) - ->method('getParam') + $this->request->method('getParam') ->with('___from_store') ->willReturn($oldStoreAlias); - $this->request - ->expects($this->any()) - ->method('getPathInfo') + $this->request->method('getPathInfo') ->willReturn($initialRequestPath); $oldStore = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->getMock(); - $oldStore->expects($this->any()) - ->method('getId') + $oldStore->method('getId') ->willReturn($oldStoreId); - $this->store - ->expects($this->any()) - ->method('getId') + $this->store->method('getId') ->willReturn($currentStoreId); - $this->storeManager - ->expects($this->any()) - ->method('getStore') + $this->storeManager->method('getStore') ->willReturnMap([[$oldStoreAlias, $oldStore], [null, $this->store]]); $oldUrlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor() ->getMock(); - $oldUrlRewrite->expects($this->any()) - ->method('getEntityType') + $oldUrlRewrite->method('getEntityType') ->willReturn($rewriteEntityType); - $oldUrlRewrite->expects($this->any()) - ->method('getEntityId') + $oldUrlRewrite->method('getEntityId') ->willReturn($rewriteEntityId); - $oldUrlRewrite->expects($this->any()) - ->method('getRedirectType') + $oldUrlRewrite->method('getRedirectType') ->willReturn(0); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor() ->getMock(); - $urlRewrite->expects($this->any()) - ->method('getRequestPath') + $urlRewrite->method('getRequestPath') ->willReturn($newRequestPath); - $this->urlFinder - ->expects($this->any()) - ->method('findOneByData') + $urlRewrite->method('getTargetPath') + ->willReturn($newTargetPath); + $this->urlFinder->method('findOneByData') ->willReturnMap( [ [ @@ -190,22 +191,22 @@ public function testRewriteAfterStoreSwitcher() */ public function testNoRewriteAfterStoreSwitcherWhenNoOldRewrite() { - $this->request->expects($this->any())->method('getPathInfo')->will($this->returnValue('request-path')); - $this->request->expects($this->any())->method('getParam')->with('___from_store') - ->will($this->returnValue('old-store')); + $this->request->method('getPathInfo')->willReturn('request-path'); + $this->request->method('getParam')->with('___from_store') + ->willReturn('old-store'); $oldStore = $this->getMockBuilder(Store::class)->disableOriginalConstructor()->getMock(); - $this->storeManager->expects($this->any())->method('getStore') - ->will($this->returnValueMap([['old-store', $oldStore], [null, $this->store]])); - $oldStore->expects($this->any())->method('getId')->will($this->returnValue('old-store-id')); - $this->store->expects($this->any())->method('getId')->will($this->returnValue('current-store-id')); + $this->storeManager->method('getStore') + ->willReturnMap([['old-store', $oldStore], [null, $this->store]]); + $oldStore->method('getId')->willReturn('old-store-id'); + $this->store->method('getId')->willReturn('current-store-id'); $oldUrlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $oldUrlRewrite->expects($this->any())->method('getEntityType')->will($this->returnValue('entity-type')); - $oldUrlRewrite->expects($this->any())->method('getEntityId')->will($this->returnValue('entity-id')); - $oldUrlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('request-path')); + $oldUrlRewrite->method('getEntityType')->willReturn('entity-type'); + $oldUrlRewrite->method('getEntityId')->willReturn('entity-id'); + $oldUrlRewrite->method('getRequestPath')->willReturn('request-path'); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('request-path')); + $urlRewrite->method('getRequestPath')->willReturn('request-path'); $this->assertNull($this->router->match($this->request)); } @@ -215,41 +216,39 @@ public function testNoRewriteAfterStoreSwitcherWhenNoOldRewrite() */ public function testNoRewriteAfterStoreSwitcherWhenOldRewriteEqualsToNewOne() { - $this->request->expects($this->any())->method('getPathInfo')->will($this->returnValue('request-path')); - $this->request->expects($this->any())->method('getParam')->with('___from_store') - ->will($this->returnValue('old-store')); + $this->request->method('getPathInfo')->willReturn('request-path'); + $this->request->method('getParam')->with('___from_store') + ->willReturn('old-store'); $oldStore = $this->getMockBuilder(Store::class)->disableOriginalConstructor()->getMock(); - $this->storeManager->expects($this->any())->method('getStore') - ->will($this->returnValueMap([['old-store', $oldStore], [null, $this->store]])); - $oldStore->expects($this->any())->method('getId')->will($this->returnValue('old-store-id')); - $this->store->expects($this->any())->method('getId')->will($this->returnValue('current-store-id')); + $this->storeManager->method('getStore') + ->willReturnMap([['old-store', $oldStore], [null, $this->store]]); + $oldStore->method('getId')->willReturn('old-store-id'); + $this->store->method('getId')->willReturn('current-store-id'); $oldUrlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $oldUrlRewrite->expects($this->any())->method('getEntityType')->will($this->returnValue('entity-type')); - $oldUrlRewrite->expects($this->any())->method('getEntityId')->will($this->returnValue('entity-id')); - $oldUrlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('old-request-path')); + $oldUrlRewrite->method('getEntityType')->willReturn('entity-type'); + $oldUrlRewrite->method('getEntityId')->willReturn('entity-id'); + $oldUrlRewrite->method('getRequestPath')->willReturn('old-request-path'); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('old-request-path')); + $urlRewrite->method('getRequestPath')->willReturn('old-request-path'); - $this->urlFinder->expects($this->any())->method('findOneByData')->will( - $this->returnValueMap( + $this->urlFinder->method('findOneByData')->willReturnMap( + [ + [ + [UrlRewrite::REQUEST_PATH => 'request-path', UrlRewrite::STORE_ID => 'old-store-id'], + $oldUrlRewrite, + ], [ [ - [UrlRewrite::REQUEST_PATH => 'request-path', UrlRewrite::STORE_ID => 'old-store-id'], - $oldUrlRewrite, + UrlRewrite::ENTITY_TYPE => 'entity-type', + UrlRewrite::ENTITY_ID => 'entity-id', + UrlRewrite::STORE_ID => 'current-store-id', + UrlRewrite::IS_AUTOGENERATED => 1, ], - [ - [ - UrlRewrite::ENTITY_TYPE => 'entity-type', - UrlRewrite::ENTITY_ID => 'entity-id', - UrlRewrite::STORE_ID => 'current-store-id', - UrlRewrite::IS_AUTOGENERATED => 1, - ], - $urlRewrite - ], - ] - ) + $urlRewrite + ], + ] ); $this->assertNull($this->router->match($this->request)); @@ -261,51 +260,103 @@ public function testNoRewriteAfterStoreSwitcherWhenOldRewriteEqualsToNewOne() public function testMatchWithRedirect() { $queryParams = []; - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); + $redirectType = 'redirect-code'; + $requestPath = 'request-path'; + $targetPath = 'target-path'; + $newTargetPath = 'new-target-path'; + $this->storeManager->method('getStore') + ->willReturn($this->store); + $this->request->method('getPathInfo') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) - ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getRedirectType')->will($this->returnValue('redirect-code')); - $urlRewrite->expects($this->any())->method('getTargetPath')->will($this->returnValue('target-path')); - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue($urlRewrite)); - $this->response->expects($this->once())->method('setRedirect') - ->with('new-target-path', 'redirect-code'); - $this->request->expects($this->once())->method('getParams')->willReturn($queryParams); - $this->url->expects($this->once())->method('getUrl')->with( - '', - ['_direct' => 'target-path', '_query' => $queryParams] - ) - ->will($this->returnValue('new-target-path')); - $this->request->expects($this->once())->method('setDispatched')->with(true); - $this->actionFactory->expects($this->once())->method('create') - ->with(\Magento\Framework\App\Action\Redirect::class); + ->disableOriginalConstructor() + ->getMock(); + $urlRewrite->method('getRedirectType')->willReturn($redirectType); + $urlRewrite->method('getRequestPath')->willReturn($requestPath); + $urlRewrite->method('getTargetPath')->willReturn($targetPath); + $this->urlFinder->method('findOneByData')->willReturn($urlRewrite); + $this->response->expects($this->once()) + ->method('setRedirect') + ->with($newTargetPath, $redirectType); + $this->request->expects($this->once()) + ->method('getParams') + ->willReturn($queryParams); + $this->url->expects($this->once()) + ->method('getUrl') + ->with( + '', + ['_direct' => $targetPath, '_query' => $queryParams] + ) + ->willReturn($newTargetPath); + $this->request->expects($this->once()) + ->method('setDispatched') + ->with(true); + $this->actionFactory->expects($this->once()) + ->method('create') + ->with(Redirect::class); $this->router->match($this->request); } /** - * @return void + * @param string $requestPath + * @param string $targetPath + * @param bool $shouldRedirect + * @dataProvider customInternalRedirectDataProvider */ - public function testMatchWithCustomInternalRedirect() + public function testMatchWithCustomInternalRedirect($requestPath, $targetPath, $shouldRedirect) { $queryParams = []; - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); + $redirectType = 'redirect-code'; + $this->storeManager->method('getStore') + ->willReturn($this->store); + $this->request->method('getPathInfo') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) - ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getEntityType')->will($this->returnValue('custom')); - $urlRewrite->expects($this->any())->method('getRedirectType')->will($this->returnValue('redirect-code')); - $urlRewrite->expects($this->any())->method('getTargetPath')->will($this->returnValue('target-path')); - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue($urlRewrite)); - $this->request->expects($this->any())->method('getParams')->willReturn($queryParams); - $this->response->expects($this->once())->method('setRedirect')->with('a', 'redirect-code'); - $this->url->expects($this->once())->method('getUrl')->with( - '', - ['_direct' => 'target-path', '_query' => $queryParams] - )->willReturn('a'); - $this->request->expects($this->once())->method('setDispatched')->with(true); - $this->actionFactory->expects($this->once())->method('create') - ->with(\Magento\Framework\App\Action\Redirect::class); + ->disableOriginalConstructor() + ->getMock(); + $urlRewrite->method('getEntityType')->willReturn('custom'); + $urlRewrite->method('getRedirectType')->willReturn($redirectType); + $urlRewrite->method('getRequestPath')->willReturn($requestPath); + $urlRewrite->method('getTargetPath')->willReturn($targetPath); + $this->urlFinder->method('findOneByData')->willReturn($urlRewrite); - $this->router->match($this->request); + if ($shouldRedirect) { + $this->request->method('getParams')->willReturn($queryParams); + $this->response->expects($this->once()) + ->method('setRedirect') + ->with('a', $redirectType); + $this->url->expects($this->once()) + ->method('getUrl') + ->with( + '', + ['_direct' => $targetPath, '_query' => $queryParams] + ) + ->willReturn('a'); + $this->request->expects($this->once()) + ->method('setDispatched') + ->with(true); + $this->actionFactory->expects($this->once()) + ->method('create') + ->with(Redirect::class); + } + + $routerResult = $this->router->match($this->request); + + if (!$shouldRedirect) { + $this->assertNull($routerResult); + } + } + + /** + * @return array + */ + public function customInternalRedirectDataProvider() + { + return [ + ['request-path', 'target-path', true], + ['/', '/', false], + ]; } /** @@ -314,19 +365,25 @@ public function testMatchWithCustomInternalRedirect() */ public function testMatchWithCustomExternalRedirect($targetPath) { - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); + $requestPath = 'request-path'; + $this->storeManager->method('getStore')->willReturn($this->store); + $this->request->method('getPathInfo') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getEntityType')->will($this->returnValue('custom')); - $urlRewrite->expects($this->any())->method('getRedirectType')->will($this->returnValue('redirect-code')); - $urlRewrite->expects($this->any())->method('getTargetPath')->will($this->returnValue($targetPath)); - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue($urlRewrite)); - $this->response->expects($this->once())->method('setRedirect')->with($targetPath, 'redirect-code'); + $urlRewrite->method('getEntityType')->willReturn('custom'); + $urlRewrite->method('getRedirectType')->willReturn('redirect-code'); + $urlRewrite->method('getRequestPath')->willReturn($requestPath); + $urlRewrite->method('getTargetPath')->willReturn($targetPath); + $this->urlFinder->method('findOneByData')->willReturn($urlRewrite); + $this->response->expects($this->once()) + ->method('setRedirect') + ->with($targetPath, 'redirect-code'); $this->request->expects($this->never())->method('getParams'); $this->url->expects($this->never())->method('getUrl'); $this->request->expects($this->once())->method('setDispatched')->with(true); $this->actionFactory->expects($this->once())->method('create') - ->with(\Magento\Framework\App\Action\Redirect::class); + ->with(Redirect::class); $this->router->match($this->request); } @@ -347,18 +404,21 @@ public function externalRedirectTargetPathDataProvider() */ public function testMatch() { - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); + $requestPath = 'request-path'; + $this->storeManager->method('getStore')->willReturn($this->store); + $this->request->method('getPathInfo') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getRedirectType')->will($this->returnValue(0)); - $urlRewrite->expects($this->any())->method('getTargetPath')->will($this->returnValue('target-path')); - $urlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('request-path')); - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue($urlRewrite)); + $urlRewrite->method('getRedirectType')->willReturn(0); + $urlRewrite->method('getRequestPath')->willReturn($requestPath); + $urlRewrite->method('getTargetPath')->willReturn('target-path'); + $this->urlFinder->method('findOneByData')->willReturn($urlRewrite); $this->request->expects($this->once())->method('setPathInfo')->with('/target-path'); $this->request->expects($this->once())->method('setAlias') ->with(UrlInterface::REWRITE_REQUEST_PATH_ALIAS, 'request-path'); $this->actionFactory->expects($this->once())->method('create') - ->with(\Magento\Framework\App\Action\Forward::class); + ->with(Forward::class); $this->router->match($this->request); } diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php index 697ce33be0fa7..946f30f609290 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php @@ -6,64 +6,68 @@ namespace Magento\UrlRewrite\Test\Unit\Model\Storage; +use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\DB\Select; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\UrlRewrite\Model\Storage\DbStorage; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class DbStorageTest extends \PHPUnit\Framework\TestCase +class DbStorageTest extends TestCase { /** - * @var \Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory|\PHPUnit_Framework_MockObject_MockObject + * @var UrlRewriteFactory|MockObject */ - protected $urlRewriteFactory; + private $urlRewriteFactory; /** - * @var \Magento\Framework\Api\DataObjectHelper|\PHPUnit_Framework_MockObject_MockObject + * @var DataObjectHelper|MockObject */ - protected $dataObjectHelper; + private $dataObjectHelper; /** - * @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject + * @var AdapterInterface|MockObject */ - protected $connectionMock; + private $connectionMock; /** - * @var \Magento\Framework\DB\Select|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\DB\Select|MockObject */ - protected $select; + private $select; /** - * @var \Magento\Framework\App\ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + * @var ResourceConnection|MockObject */ - protected $resource; + private $resource; /** - * @var \Magento\UrlRewrite\Model\Storage\DbStorage + * @var DbStorage */ - protected $storage; + private $storage; protected function setUp() { - $this->urlRewriteFactory = $this->getMockBuilder(\Magento\UrlRewrite\Service\V1\Data\UrlRewriteFactory::class) + $this->urlRewriteFactory = $this->getMockBuilder(UrlRewriteFactory::class) ->setMethods(['create']) ->disableOriginalConstructor()->getMock(); - $this->dataObjectHelper = $this->createMock(\Magento\Framework\Api\DataObjectHelper::class); - $this->connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $this->dataObjectHelper = $this->createMock(DataObjectHelper::class); + $this->connectionMock = $this->createMock(AdapterInterface::class); $this->select = $this->getMockBuilder(Select::class) ->disableOriginalConstructor() ->getMock(); - $this->resource = $this->createMock(\Magento\Framework\App\ResourceConnection::class); + $this->resource = $this->createMock(ResourceConnection::class); - $this->resource->expects($this->any()) - ->method('getConnection') - ->will($this->returnValue($this->connectionMock)); - $this->connectionMock->expects($this->any()) - ->method('select') - ->will($this->returnValue($this->select)); + $this->resource->method('getConnection') + ->willReturn($this->connectionMock); + $this->connectionMock->method('select') + ->willReturn($this->select); $this->storage = (new ObjectManager($this))->getObject( - \Magento\UrlRewrite\Model\Storage\DbStorage::class, + DbStorage::class, [ 'urlRewriteFactory' => $this->urlRewriteFactory, 'dataObjectHelper' => $this->dataObjectHelper, @@ -84,32 +88,32 @@ public function testFindAllByData() ->method('where') ->with('col2 IN (?)', 'val2'); - $this->connectionMock->expects($this->any()) + $this->connectionMock ->method('quoteIdentifier') ->will($this->returnArgument(0)); $this->connectionMock->expects($this->once()) ->method('fetchAll') ->with($this->select) - ->will($this->returnValue([['row1'], ['row2']])); + ->willReturn([['row1'], ['row2']]); $this->dataObjectHelper->expects($this->at(0)) ->method('populateWithArray') - ->with(['urlRewrite1'], ['row1'], \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) + ->with(['urlRewrite1'], ['row1'], UrlRewrite::class) ->will($this->returnSelf()); $this->urlRewriteFactory->expects($this->at(0)) ->method('create') - ->will($this->returnValue(['urlRewrite1'])); + ->willReturn(['urlRewrite1']); $this->dataObjectHelper->expects($this->at(1)) ->method('populateWithArray') - ->with(['urlRewrite2'], ['row2'], \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) + ->with(['urlRewrite2'], ['row2'], UrlRewrite::class) ->will($this->returnSelf()); $this->urlRewriteFactory->expects($this->at(1)) ->method('create') - ->will($this->returnValue(['urlRewrite2'])); + ->willReturn(['urlRewrite2']); $this->assertEquals([['urlRewrite1'], ['urlRewrite2']], $this->storage->findAllByData($data)); } @@ -126,25 +130,24 @@ public function testFindOneByData() ->method('where') ->with('col2 IN (?)', 'val2'); - $this->connectionMock->expects($this->any()) - ->method('quoteIdentifier') + $this->connectionMock->method('quoteIdentifier') ->will($this->returnArgument(0)); $this->connectionMock->expects($this->once()) ->method('fetchRow') ->with($this->select) - ->will($this->returnValue(['row1'])); + ->willReturn(['row1']); $this->connectionMock->expects($this->never())->method('fetchAll'); $this->dataObjectHelper->expects($this->at(0)) ->method('populateWithArray') - ->with(['urlRewrite1'], ['row1'], \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) + ->with(['urlRewrite1'], ['row1'], UrlRewrite::class) ->will($this->returnSelf()); $this->urlRewriteFactory->expects($this->at(0)) ->method('create') - ->will($this->returnValue(['urlRewrite1'])); + ->willReturn(['urlRewrite1']); $this->assertEquals(['urlRewrite1'], $this->storage->findOneByData($data)); } @@ -153,8 +156,8 @@ public function testFindOneByDataWithRequestPath() { $origRequestPath = 'page-one'; $data = [ - 'col1' => 'val1', - 'col2' => 'val2', + 'col1' => 'val1', + 'col2' => 'val2', UrlRewrite::REQUEST_PATH => $origRequestPath, ]; @@ -170,31 +173,31 @@ public function testFindOneByDataWithRequestPath() ->method('where') ->with('request_path IN (?)', [$origRequestPath, $origRequestPath . '/']); - $this->connectionMock->expects($this->any()) - ->method('quoteIdentifier') + $this->connectionMock->method('quoteIdentifier') ->will($this->returnArgument(0)); $this->connectionMock->expects($this->never()) ->method('fetchRow'); $urlRewriteRowInDb = [ - UrlRewrite::REQUEST_PATH => $origRequestPath, + UrlRewrite::REQUEST_PATH => $origRequestPath, + UrlRewrite::TARGET_PATH => $origRequestPath, UrlRewrite::REDIRECT_TYPE => 0, ]; $this->connectionMock->expects($this->once()) ->method('fetchAll') ->with($this->select) - ->will($this->returnValue([$urlRewriteRowInDb])); + ->willReturn([$urlRewriteRowInDb]); $this->dataObjectHelper->expects($this->at(0)) ->method('populateWithArray') - ->with(['urlRewrite1'], $urlRewriteRowInDb, \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) - ->will($this->returnSelf()); + ->with(['urlRewrite1'], $urlRewriteRowInDb, UrlRewrite::class) + ->willReturnSelf(); $this->urlRewriteFactory->expects($this->at(0)) ->method('create') - ->will($this->returnValue(['urlRewrite1'])); + ->willReturn(['urlRewrite1']); $this->assertEquals(['urlRewrite1'], $this->storage->findOneByData($data)); } @@ -203,8 +206,8 @@ public function testFindOneByDataWithRequestPathIsDifferent() { $origRequestPath = 'page-one'; $data = [ - 'col1' => 'val1', - 'col2' => 'val2', + 'col1' => 'val1', + 'col2' => 'val2', UrlRewrite::REQUEST_PATH => $origRequestPath, ]; @@ -220,44 +223,44 @@ public function testFindOneByDataWithRequestPathIsDifferent() ->method('where') ->with('request_path IN (?)', [$origRequestPath, $origRequestPath . '/']); - $this->connectionMock->expects($this->any()) - ->method('quoteIdentifier') + $this->connectionMock->method('quoteIdentifier') ->will($this->returnArgument(0)); $this->connectionMock->expects($this->never()) ->method('fetchRow'); $urlRewriteRowInDb = [ - UrlRewrite::REQUEST_PATH => $origRequestPath . '/', + UrlRewrite::REQUEST_PATH => $origRequestPath . '/', + UrlRewrite::TARGET_PATH => $origRequestPath . '/', UrlRewrite::REDIRECT_TYPE => 0, - UrlRewrite::STORE_ID => 1, + UrlRewrite::STORE_ID => 1, ]; $this->connectionMock->expects($this->once()) ->method('fetchAll') ->with($this->select) - ->will($this->returnValue([$urlRewriteRowInDb])); + ->willReturn([$urlRewriteRowInDb]); $urlRewriteRedirect = [ - 'request_path' => $origRequestPath, - 'redirect_type' => 301, - 'store_id' => 1, - 'entity_type' => 'custom', - 'entity_id' => '0', - 'target_path' => $origRequestPath . '/', - 'description' => null, + 'request_path' => $origRequestPath, + 'redirect_type' => 301, + 'store_id' => 1, + 'entity_type' => 'custom', + 'entity_id' => '0', + 'target_path' => $origRequestPath . '/', + 'description' => null, 'is_autogenerated' => '0', - 'metadata' => null, + 'metadata' => null, ]; $this->dataObjectHelper->expects($this->at(0)) ->method('populateWithArray') - ->with(['urlRewrite1'], $urlRewriteRedirect, \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) + ->with(['urlRewrite1'], $urlRewriteRedirect, UrlRewrite::class) ->will($this->returnSelf()); $this->urlRewriteFactory->expects($this->at(0)) ->method('create') - ->will($this->returnValue(['urlRewrite1'])); + ->willReturn(['urlRewrite1']); $this->assertEquals(['urlRewrite1'], $this->storage->findOneByData($data)); } @@ -266,8 +269,8 @@ public function testFindOneByDataWithRequestPathIsDifferent2() { $origRequestPath = 'page-one/'; $data = [ - 'col1' => 'val1', - 'col2' => 'val2', + 'col1' => 'val1', + 'col2' => 'val2', UrlRewrite::REQUEST_PATH => $origRequestPath, ]; @@ -283,7 +286,7 @@ public function testFindOneByDataWithRequestPathIsDifferent2() ->method('where') ->with('request_path IN (?)', [rtrim($origRequestPath, '/'), rtrim($origRequestPath, '/') . '/']); - $this->connectionMock->expects($this->any()) + $this->connectionMock ->method('quoteIdentifier') ->will($this->returnArgument(0)); @@ -291,36 +294,37 @@ public function testFindOneByDataWithRequestPathIsDifferent2() ->method('fetchRow'); $urlRewriteRowInDb = [ - UrlRewrite::REQUEST_PATH => rtrim($origRequestPath, '/'), + UrlRewrite::REQUEST_PATH => rtrim($origRequestPath, '/'), + UrlRewrite::TARGET_PATH => rtrim($origRequestPath, '/'), UrlRewrite::REDIRECT_TYPE => 0, - UrlRewrite::STORE_ID => 1, + UrlRewrite::STORE_ID => 1, ]; $this->connectionMock->expects($this->once()) ->method('fetchAll') ->with($this->select) - ->will($this->returnValue([$urlRewriteRowInDb])); + ->willReturn([$urlRewriteRowInDb]); $urlRewriteRedirect = [ - 'request_path' => $origRequestPath, - 'redirect_type' => 301, - 'store_id' => 1, - 'entity_type' => 'custom', - 'entity_id' => '0', - 'target_path' => rtrim($origRequestPath, '/'), - 'description' => null, + 'request_path' => $origRequestPath, + 'redirect_type' => 301, + 'store_id' => 1, + 'entity_type' => 'custom', + 'entity_id' => '0', + 'target_path' => rtrim($origRequestPath, '/'), + 'description' => null, 'is_autogenerated' => '0', - 'metadata' => null, + 'metadata' => null, ]; $this->dataObjectHelper->expects($this->at(0)) ->method('populateWithArray') - ->with(['urlRewrite1'], $urlRewriteRedirect, \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) + ->with(['urlRewrite1'], $urlRewriteRedirect, UrlRewrite::class) ->will($this->returnSelf()); $this->urlRewriteFactory->expects($this->at(0)) ->method('create') - ->will($this->returnValue(['urlRewrite1'])); + ->willReturn(['urlRewrite1']); $this->assertEquals(['urlRewrite1'], $this->storage->findOneByData($data)); } @@ -329,8 +333,8 @@ public function testFindOneByDataWithRequestPathIsRedirect() { $origRequestPath = 'page-one'; $data = [ - 'col1' => 'val1', - 'col2' => 'val2', + 'col1' => 'val1', + 'col2' => 'val2', UrlRewrite::REQUEST_PATH => $origRequestPath, ]; @@ -346,33 +350,32 @@ public function testFindOneByDataWithRequestPathIsRedirect() ->method('where') ->with('request_path IN (?)', [$origRequestPath, $origRequestPath . '/']); - $this->connectionMock->expects($this->any()) - ->method('quoteIdentifier') + $this->connectionMock->method('quoteIdentifier') ->will($this->returnArgument(0)); $this->connectionMock->expects($this->never()) ->method('fetchRow'); $urlRewriteRowInDb = [ - UrlRewrite::REQUEST_PATH => $origRequestPath . '/', - UrlRewrite::TARGET_PATH => 'page-A/', + UrlRewrite::REQUEST_PATH => $origRequestPath . '/', + UrlRewrite::TARGET_PATH => 'page-A/', UrlRewrite::REDIRECT_TYPE => 301, - UrlRewrite::STORE_ID => 1, + UrlRewrite::STORE_ID => 1, ]; $this->connectionMock->expects($this->once()) ->method('fetchAll') ->with($this->select) - ->will($this->returnValue([$urlRewriteRowInDb])); + ->willReturn([$urlRewriteRowInDb]); $this->dataObjectHelper->expects($this->at(0)) ->method('populateWithArray') - ->with(['urlRewrite1'], $urlRewriteRowInDb, \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) - ->will($this->returnSelf()); + ->with(['urlRewrite1'], $urlRewriteRowInDb, UrlRewrite::class) + ->willReturnSelf(); $this->urlRewriteFactory->expects($this->at(0)) ->method('create') - ->will($this->returnValue(['urlRewrite1'])); + ->willReturn(['urlRewrite1']); $this->assertEquals(['urlRewrite1'], $this->storage->findOneByData($data)); } @@ -398,40 +401,39 @@ public function testFindOneByDataWithRequestPathTwoResults() ->method('where') ->with('request_path IN (?)', [$origRequestPath, $origRequestPath . '/']); - $this->connectionMock->expects($this->any()) - ->method('quoteIdentifier') + $this->connectionMock->method('quoteIdentifier') ->will($this->returnArgument(0)); $this->connectionMock->expects($this->never()) ->method('fetchRow'); $urlRewriteRowInDb = [ - UrlRewrite::REQUEST_PATH => $origRequestPath . '/', - UrlRewrite::TARGET_PATH => 'page-A/', + UrlRewrite::REQUEST_PATH => $origRequestPath . '/', + UrlRewrite::TARGET_PATH => 'page-A/', UrlRewrite::REDIRECT_TYPE => 301, - UrlRewrite::STORE_ID => 1, + UrlRewrite::STORE_ID => 1, ]; $urlRewriteRowInDb2 = [ - UrlRewrite::REQUEST_PATH => $origRequestPath, - UrlRewrite::TARGET_PATH => 'page-B/', + UrlRewrite::REQUEST_PATH => $origRequestPath, + UrlRewrite::TARGET_PATH => 'page-B/', UrlRewrite::REDIRECT_TYPE => 301, - UrlRewrite::STORE_ID => 1, + UrlRewrite::STORE_ID => 1, ]; $this->connectionMock->expects($this->once()) ->method('fetchAll') ->with($this->select) - ->will($this->returnValue([$urlRewriteRowInDb, $urlRewriteRowInDb2])); + ->willReturn([$urlRewriteRowInDb, $urlRewriteRowInDb2]); $this->dataObjectHelper->expects($this->at(0)) ->method('populateWithArray') - ->with(['urlRewrite1'], $urlRewriteRowInDb2, \Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class) - ->will($this->returnSelf()); + ->with(['urlRewrite1'], $urlRewriteRowInDb2, UrlRewrite::class) + ->willReturnSelf(); $this->urlRewriteFactory->expects($this->at(0)) ->method('create') - ->will($this->returnValue(['urlRewrite1'])); + ->willReturn(['urlRewrite1']); $this->assertEquals(['urlRewrite1'], $this->storage->findOneByData($data)); } @@ -441,58 +443,48 @@ public function testFindOneByDataWithRequestPathTwoResults() */ public function testReplace() { - $urlFirst = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); - $urlSecond = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); + $urlFirst = $this->createMock(UrlRewrite::class); + $urlSecond = $this->createMock(UrlRewrite::class); // delete - $urlFirst->expects($this->any()) - ->method('getEntityType') + $urlFirst->method('getEntityType') ->willReturn('product'); - $urlFirst->expects($this->any()) - ->method('getEntityId') + $urlFirst->method('getEntityId') ->willReturn('entity_1'); - $urlFirst->expects($this->any()) - ->method('getStoreId') + $urlFirst->method('getStoreId') ->willReturn('store_id_1'); - $urlSecond->expects($this->any()) - ->method('getEntityType') + $urlSecond->method('getEntityType') ->willReturn('category'); - $urlSecond->expects($this->any()) - ->method('getEntityId') + $urlSecond->method('getEntityId') ->willReturn('entity_2'); - $urlSecond->expects($this->any()) - ->method('getStoreId') + $urlSecond->method('getStoreId') ->willReturn('store_id_2'); - $this->connectionMock->expects($this->any()) - ->method('quoteIdentifier') - ->will($this->returnArgument(0)); + $this->connectionMock->method('quoteIdentifier') + ->willReturnArgument(0); - $this->select->expects($this->any()) - ->method($this->anything()) + $this->select->method($this->anything()) ->willReturnSelf(); - $this->resource->expects($this->any()) - ->method('getTableName') + $this->resource->method('getTableName') ->with(DbStorage::TABLE_NAME) - ->will($this->returnValue('table_name')); + ->willReturn('table_name'); // insert - $urlFirst->expects($this->any()) - ->method('toArray') - ->will($this->returnValue(['row1'])); - $urlSecond->expects($this->any()) - ->method('toArray') - ->will($this->returnValue(['row2'])); + $urlFirst->method('toArray') + ->willReturn(['row1']); + $urlSecond->method('toArray') + ->willReturn(['row2']); - $this->resource->expects($this->any()) - ->method('getTableName') + $this->resource->method('getTableName') ->with(DbStorage::TABLE_NAME) - ->will($this->returnValue('table_name')); + ->willReturn('table_name'); + + $urls = [$urlFirst, $urlSecond]; - $this->storage->replace([$urlFirst, $urlSecond]); + $this->assertEquals($urls, $this->storage->replace($urls)); } /** @@ -500,23 +492,20 @@ public function testReplace() */ public function testReplaceIfThrewExceptionOnDuplicateUrl() { - $url = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); + $url = $this->createMock(UrlRewrite::class); - $url->expects($this->any()) - ->method('toArray') - ->will($this->returnValue(['row1'])); + $url->method('toArray') + ->willReturn(['row1']); $this->connectionMock->expects($this->once()) ->method('insertMultiple') - ->will( - $this->throwException( - new \Exception('SQLSTATE[23000]: test: 1062 test', DbStorage::ERROR_CODE_DUPLICATE_ENTRY) - ) + ->willThrowException( + new \Exception('SQLSTATE[23000]: test: 1062 test', DbStorage::ERROR_CODE_DUPLICATE_ENTRY) ); $conflictingUrl = [ UrlRewrite::URL_REWRITE_ID => 'conflicting-url' ]; - $this->connectionMock->expects($this->any()) + $this->connectionMock ->method('fetchRow') ->willReturn($conflictingUrl); @@ -533,18 +522,15 @@ public function testReplaceIfThrewExceptionOnDuplicateUrl() */ public function testReplaceIfThrewExceptionOnDuplicateEntry() { - $url = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); + $url = $this->createMock(UrlRewrite::class); - $url->expects($this->any()) - ->method('toArray') - ->will($this->returnValue(['row1'])); + $url->method('toArray') + ->willReturn(['row1']); $this->connectionMock->expects($this->once()) ->method('insertMultiple') - ->will( - $this->throwException( - new \Exception('SQLSTATE[23000]: test: 1062 test', DbStorage::ERROR_CODE_DUPLICATE_ENTRY) - ) + ->willThrowException( + new \Exception('SQLSTATE[23000]: test: 1062 test', DbStorage::ERROR_CODE_DUPLICATE_ENTRY) ); $this->storage->replace([$url]); @@ -555,15 +541,14 @@ public function testReplaceIfThrewExceptionOnDuplicateEntry() */ public function testReplaceIfThrewCustomException() { - $url = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); + $url = $this->createMock(UrlRewrite::class); - $url->expects($this->any()) - ->method('toArray') - ->will($this->returnValue(['row1'])); + $url->method('toArray') + ->willReturn(['row1']); $this->connectionMock->expects($this->once()) ->method('insertMultiple') - ->will($this->throwException(new \RuntimeException())); + ->willThrowException(new \RuntimeException()); $this->storage->replace([$url]); } @@ -572,8 +557,7 @@ public function testDeleteByData() { $data = ['col1' => 'val1', 'col2' => 'val2']; - $this->connectionMock->expects($this->any()) - ->method('quoteIdentifier') + $this->connectionMock->method('quoteIdentifier') ->will($this->returnArgument(0)); $this->select->expects($this->at(1)) @@ -587,12 +571,11 @@ public function testDeleteByData() $this->select->expects($this->at(3)) ->method('deleteFromSelect') ->with('table_name') - ->will($this->returnValue('sql delete query')); + ->willReturn('sql delete query'); - $this->resource->expects($this->any()) - ->method('getTableName') + $this->resource->method('getTableName') ->with(DbStorage::TABLE_NAME) - ->will($this->returnValue('table_name')); + ->willReturn('table_name'); $this->connectionMock->expects($this->once()) ->method('query') From 5ce5139593516ba127ded80c7a0e949bc1eff990 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Tue, 18 Feb 2020 21:07:16 -0600 Subject: [PATCH 096/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Deprecation warnings added for action groups --- .../Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml | 1 + .../Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml | 1 + ...nExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml index 9d8457f909f24..71873fe5b0960 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml @@ -21,6 +21,7 @@ </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="DeleteAllProductsUsingProductGridActionGroup" stepKey="deleteAllProducts"/> <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView" /> <createData entity="_defaultCategory" stepKey="createCategory"/> <createData entity="ApiSimpleProduct" stepKey="createProductOne"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml index 8fc82ebf23c35..a4986117380ff 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml @@ -22,6 +22,7 @@ <before> <!--Login as admin and delete all products --> <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <actionGroup ref="DeleteAllProductsUsingProductGridActionGroup" stepKey="deleteAllProducts"/> <!--Create dropdown product attribute--> <createData entity="productDropDownAttribute" stepKey="createDropdownAttribute"/> <!--Create attribute options--> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml index 9d721fe44efa3..b5efec8faec79 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml @@ -92,7 +92,6 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Log out --> <actionGroup ref="logout" stepKey="logout"/> From d359cdf8037187c53441965920c555a3bf95985a Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Thu, 20 Feb 2020 10:09:49 +0200 Subject: [PATCH 097/369] ObjectManager cleanup - Remove usage from AdminNotification module --- .../Block/System/Messages.php | 54 +++++++++---------- .../Adminhtml/Notification/AjaxMarkAsRead.php | 18 +++---- .../Adminhtml/Notification/MarkAsRead.php | 23 +++++--- .../Adminhtml/Notification/MassMarkAsRead.php | 20 +++++-- .../Adminhtml/Notification/MassRemove.php | 18 ++++++- .../Adminhtml/Notification/Remove.php | 20 +++++-- 6 files changed, 102 insertions(+), 51 deletions(-) diff --git a/app/code/Magento/AdminNotification/Block/System/Messages.php b/app/code/Magento/AdminNotification/Block/System/Messages.php index b950f5583e599..2fbd918c2d824 100644 --- a/app/code/Magento/AdminNotification/Block/System/Messages.php +++ b/app/code/Magento/AdminNotification/Block/System/Messages.php @@ -5,45 +5,51 @@ */ namespace Magento\AdminNotification\Block\System; -class Messages extends \Magento\Backend\Block\Template +use Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized; +use Magento\Backend\Block\Template; +use Magento\Backend\Block\Template\Context as TemplateContext; +use Magento\Framework\Json\Helper\Data as JsonDataHelper; +use Magento\Framework\Notification\MessageInterface; +use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; + +class Messages extends Template { /** - * Message list + * Synchronized Message collection * - * @var \Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized + * @var Synchronized */ protected $_messages; /** - * @var \Magento\Framework\Json\Helper\Data + * @var JsonDataHelper * @deprecated */ protected $jsonHelper; /** - * @var \Magento\Framework\Serialize\Serializer\Json + * @var JsonSerializer */ private $serializer; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized $messages - * @param \Magento\Framework\Json\Helper\Data $jsonHelper + * @param TemplateContext $context + * @param Synchronized $messages + * @param JsonDataHelper $jsonHelper + * @param JsonSerializer $serializer * @param array $data - * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\AdminNotification\Model\ResourceModel\System\Message\Collection\Synchronized $messages, - \Magento\Framework\Json\Helper\Data $jsonHelper, - array $data = [], - \Magento\Framework\Serialize\Serializer\Json $serializer = null + TemplateContext $context, + Synchronized $messages, + JsonDataHelper $jsonHelper, + JsonSerializer $serializer, + array $data = [] ) { $this->jsonHelper = $jsonHelper; parent::__construct($context, $data); $this->_messages = $messages; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializer = $serializer; } /** @@ -62,15 +68,13 @@ protected function _toHtml() /** * Retrieve message list * - * @return \Magento\Framework\Notification\MessageInterface[] + * @return MessageInterface[]|null */ public function getLastCritical() { $items = array_values($this->_messages->getItems()); - if (isset( - $items[0] - ) && $items[0]->getSeverity() == \Magento\Framework\Notification\MessageInterface::SEVERITY_CRITICAL - ) { + + if (isset($items[0]) && (int)$items[0]->getSeverity() === MessageInterface::SEVERITY_CRITICAL) { return $items[0]; } return null; @@ -83,9 +87,7 @@ public function getLastCritical() */ public function getCriticalCount() { - return $this->_messages->getCountBySeverity( - \Magento\Framework\Notification\MessageInterface::SEVERITY_CRITICAL - ); + return $this->_messages->getCountBySeverity(MessageInterface::SEVERITY_CRITICAL); } /** @@ -95,9 +97,7 @@ public function getCriticalCount() */ public function getMajorCount() { - return $this->_messages->getCountBySeverity( - \Magento\Framework\Notification\MessageInterface::SEVERITY_MAJOR - ); + return $this->_messages->getCountBySeverity(MessageInterface::SEVERITY_MAJOR); } /** diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php index da797fe12e75a..e05de78c92356 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php @@ -6,28 +6,26 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\AdminNotification\Model\NotificationService; use Magento\Backend\App\Action; use Magento\Framework\Controller\ResultFactory; -class AjaxMarkAsRead extends \Magento\AdminNotification\Controller\Adminhtml\Notification +class AjaxMarkAsRead extends Notification { /** - * @var \Magento\AdminNotification\Model\NotificationService + * @var NotificationService */ private $notificationService; /** * @param Action\Context $context - * @param \Magento\AdminNotification\Model\NotificationService|null $notificationService - * @throws \RuntimeException + * @param NotificationService $notificationService */ - public function __construct( - Action\Context $context, - \Magento\AdminNotification\Model\NotificationService $notificationService = null - ) { + public function __construct(Action\Context $context, NotificationService $notificationService) + { parent::__construct($context); - $this->notificationService = $notificationService?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\AdminNotification\Model\NotificationService::class); + $this->notificationService = $notificationService; } /** diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php index 6b5e0681139cf..edc6c702abe26 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php @@ -6,7 +6,11 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; -class MarkAsRead extends \Magento\AdminNotification\Controller\Adminhtml\Notification +use Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\AdminNotification\Model\NotificationService; +use Magento\Backend\App\Action; + +class MarkAsRead extends Notification { /** * Authorization level of a basic admin session @@ -15,6 +19,17 @@ class MarkAsRead extends \Magento\AdminNotification\Controller\Adminhtml\Notific */ const ADMIN_RESOURCE = 'Magento_AdminNotification::mark_as_read'; + /** + * @var NotificationService + */ + private $notificationService; + + public function __construct(Action\Context $context, NotificationService $notificationService) + { + parent::__construct($context); + $this->notificationService = $notificationService; + } + /** * @return void */ @@ -23,11 +38,7 @@ public function execute() $notificationId = (int)$this->getRequest()->getParam('id'); if ($notificationId) { try { - $this->_objectManager->create( - \Magento\AdminNotification\Model\NotificationService::class - )->markAsRead( - $notificationId - ); + $this->notificationService->markAsRead($notificationId); $this->messageManager->addSuccessMessage(__('The message has been marked as Read.')); } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addErrorMessage($e->getMessage()); diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php index 9ae4a7cdac0b9..e73f4219b7333 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php @@ -6,9 +6,12 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; -class MassMarkAsRead extends \Magento\AdminNotification\Controller\Adminhtml\Notification -{ +use Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; +use Magento\Backend\App\Action; +class MassMarkAsRead extends Notification +{ /** * Authorization level of a basic admin session * @@ -16,6 +19,17 @@ class MassMarkAsRead extends \Magento\AdminNotification\Controller\Adminhtml\Not */ const ADMIN_RESOURCE = 'Magento_AdminNotification::mark_as_read'; + /** + * @var InboxModelFactory + */ + private $inboxModelFactory; + + public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) + { + parent::__construct($context); + $this->inboxModelFactory = $inboxModelFactory; + } + /** * @return void */ @@ -27,7 +41,7 @@ public function execute() } else { try { foreach ($ids as $id) { - $model = $this->_objectManager->create(\Magento\AdminNotification\Model\Inbox::class)->load($id); + $model = $this->inboxModelFactory->create()->load($id); if ($model->getId()) { $model->setIsRead(1)->save(); } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php index 06659b8452cab..a248430b5660c 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php @@ -6,7 +6,11 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; -class MassRemove extends \Magento\AdminNotification\Controller\Adminhtml\Notification +use Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; +use Magento\Backend\App\Action; + +class MassRemove extends Notification { /** @@ -15,6 +19,16 @@ class MassRemove extends \Magento\AdminNotification\Controller\Adminhtml\Notific * @see _isAllowed() */ const ADMIN_RESOURCE = 'Magento_AdminNotification::adminnotification_remove'; + /** + * @var InboxModelFactory + */ + private $inboxModelFactory; + + public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) + { + parent::__construct($context); + $this->inboxModelFactory = $inboxModelFactory; + } /** * @return void @@ -27,7 +41,7 @@ public function execute() } else { try { foreach ($ids as $id) { - $model = $this->_objectManager->create(\Magento\AdminNotification\Model\Inbox::class)->load($id); + $model = $this->inboxModelFactory->create()->load($id); if ($model->getId()) { $model->setIsRemove(1)->save(); } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php index f0724a9587c50..0d74db43eef2b 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php @@ -6,9 +6,12 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; -class Remove extends \Magento\AdminNotification\Controller\Adminhtml\Notification -{ +use Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; +use Magento\Backend\App\Action; +class Remove extends Notification +{ /** * Authorization level of a basic admin session * @@ -16,13 +19,24 @@ class Remove extends \Magento\AdminNotification\Controller\Adminhtml\Notificatio */ const ADMIN_RESOURCE = 'Magento_AdminNotification::adminnotification_remove'; + /** + * @var InboxModelFactory + */ + private $inboxModelFactory; + + public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) + { + parent::__construct($context); + $this->inboxModelFactory = $inboxModelFactory; + } + /** * @return void */ public function execute() { if ($id = $this->getRequest()->getParam('id')) { - $model = $this->_objectManager->create(\Magento\AdminNotification\Model\Inbox::class)->load($id); + $model = $this->inboxModelFactory->create()->load($id); if (!$model->getId()) { $this->_redirect('adminhtml/*/'); From 6c5fbcb16196a5e7757fe82c77f358da3aae1678 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Thu, 20 Feb 2020 12:01:14 +0200 Subject: [PATCH 098/369] Changed implements class --- .../Sales/Controller/Adminhtml/Order/Create/Reorder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php index 481fa669b72d3..925e8bfdd05aa 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php @@ -12,6 +12,7 @@ use Magento\Backend\Model\View\Result\ForwardFactory; use Magento\Backend\Model\View\Result\Redirect; use Magento\Catalog\Helper\Product; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Escaper; use Magento\Framework\View\Result\PageFactory; use Magento\Sales\Api\OrderRepositoryInterface; @@ -19,12 +20,11 @@ use Magento\Sales\Helper\Reorder as ReorderHelper; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Reorder\UnavailableProductsProvider; -use Magento\Framework\App\Action\HttpPostActionInterface; /** * Controller create order. */ -class Reorder extends Create implements HttpPostActionInterface +class Reorder extends Create implements HttpGetActionInterface { /** * @var UnavailableProductsProvider From 37160a9524542f77eb77d837b8a9b9b4f04d43f3 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Thu, 20 Feb 2020 16:42:18 -0600 Subject: [PATCH 099/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Fixed downloadable tests to make it data agnostic --- .../DeleteExportedFileActionGroup.xml | 12 ++++++------ ...ionGroup.xml => DownloadFileActionGroup.xml} | 10 +++++----- .../Mftf/Test/AdminExportBundleProductTest.xml | 16 +++++++++------- ...ExportGroupedProductWithSpecialPriceTest.xml | 16 +++++++++------- ...tImportConfigurableProductWithImagesTest.xml | 16 +++++++++------- ...onfigurableProductsWithCustomOptionsTest.xml | 15 +++++++++------ ...nfigurableProductsWithAssignedImagesTest.xml | 11 +++++++++-- ...urableProductAssignedToCustomWebsiteTest.xml | 15 +++++++++------ ...portSimpleProductWithCustomAttributeTest.xml | 17 ++++++++++------- .../Section/AdminExportAttributeSection.xml | 4 ++++ 10 files changed, 79 insertions(+), 53 deletions(-) rename app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/{DownloadFileByRowIndexActionGroup.xml => DownloadFileActionGroup.xml} (67%) diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DeleteExportedFileActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DeleteExportedFileActionGroup.xml index 78d7293b7437b..49173f79736c2 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DeleteExportedFileActionGroup.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DeleteExportedFileActionGroup.xml @@ -10,19 +10,19 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="DeleteExportedFileActionGroup"> <annotations> - <description>Deletes the provided Grid Index on the Exports grid page.</description> + <description>Deletes the exported file on the Exports grid page.</description> </annotations> <arguments> - <argument name="rowIndex" type="string"/> + <argument name="fileName" type="string"/> </arguments> <amOnPage url="{{AdminExportIndexPage.url}}" stepKey="goToExportIndexPage"/> <waitForPageLoad time="30" stepKey="waitFormReload"/> - <click stepKey="clickSelectBtn" selector="{{AdminExportAttributeSection.selectByIndex(rowIndex)}}"/> - <click stepKey="clickOnDelete" selector="{{AdminExportAttributeSection.delete(rowIndex)}}" after="clickSelectBtn"/> + <click stepKey="clickSelectBtn" selector="{{AdminExportAttributeSection.selectByFileName(fileName)}}"/> + <click stepKey="clickOnDelete" selector="{{AdminExportAttributeSection.deleteByFileName(fileName)}}" after="clickSelectBtn"/> <waitForElementVisible selector="{{AdminProductGridConfirmActionSection.title}}" stepKey="waitForConfirmModal"/> <click selector="{{AdminProductGridConfirmActionSection.ok}}" stepKey="confirmDelete"/> - <waitForElementVisible selector="{{AdminDataGridTableSection.dataGridEmpty}}" stepKey="waitDataGridEmptyMessageAppears"/> - <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> + <waitForPageLoad time="30" stepKey="waitFormReload2"/> + <dontSeeElement selector="{{AdminExportAttributeSection.fileName(fileName)}}" stepKey="assertDontSeeFile"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DownloadFileByRowIndexActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DownloadFileActionGroup.xml similarity index 67% rename from app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DownloadFileByRowIndexActionGroup.xml rename to app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DownloadFileActionGroup.xml index ec164ff172625..7866a2ca61658 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DownloadFileByRowIndexActionGroup.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/DownloadFileActionGroup.xml @@ -8,17 +8,17 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="DownloadFileByRowIndexActionGroup"> + <actionGroup name="DownloadFileActionGroup"> <annotations> - <description>Downloads the provided Grid Index on the Exports grid page.</description> + <description>Downloads the provided fileName on the Exports grid page.</description> </annotations> <arguments> - <argument name="rowIndex" type="string"/> + <argument name="fileName" type="string"/> </arguments> <reloadPage stepKey="refreshPage"/> <waitForPageLoad stepKey="waitFormReload"/> - <click stepKey="clickSelectBtn" selector="{{AdminExportAttributeSection.selectByIndex(rowIndex)}}"/> - <click stepKey="clickOnDownload" selector="{{AdminExportAttributeSection.download(rowIndex)}}" after="clickSelectBtn"/> + <click stepKey="clickSelectBtn" selector="{{AdminExportAttributeSection.selectByFileName(fileName)}}"/> + <click stepKey="clickOnDownload" selector="{{AdminExportAttributeSection.downloadByFileName(fileName)}}" after="clickSelectBtn"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml index 146f6cc948f5d..f002fe100d16d 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml @@ -88,11 +88,6 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Delete products creations --> <deleteData createDataKey="createDynamicBundleProduct" stepKey="deleteDynamicBundleProduct"/> <deleteData createDataKey="firstSimpleProductForDynamic" stepKey="deleteFirstSimpleProductForDynamic"/> @@ -122,9 +117,16 @@ <magentoCLI command="cron:run" stepKey="runCron3"/> <magentoCLI command="cron:run" stepKey="runCron4"/> + <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> + <!-- Download product --> - <actionGroup ref="DownloadFileByRowIndexActionGroup" stepKey="downloadCreatedProducts"> - <argument name="rowIndex" value="0"/> + <actionGroup ref="DownloadFileActionGroup" stepKey="downloadCreatedProducts"> + <argument name="fileName" value="{$grabNameFile}"/> + </actionGroup> + + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="fileName" value="{$grabNameFile}"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml index b20f5e3802e41..e34be8fea8ae0 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml @@ -56,11 +56,6 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Deleted created products --> <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> @@ -84,9 +79,16 @@ <magentoCLI command="cron:run" stepKey="runCron3"/> <magentoCLI command="cron:run" stepKey="runCron4"/> + <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> + <!-- Download product --> - <actionGroup ref="DownloadFileByRowIndexActionGroup" stepKey="downloadCreatedProducts"> - <argument name="rowIndex" value="0"/> + <actionGroup ref="DownloadFileActionGroup" stepKey="downloadCreatedProducts"> + <argument name="fileName" value="{$grabNameFile}"/> + </actionGroup> + + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="fileName" value="{$grabNameFile}"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml index f898935657dee..dab2ead57f952 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -130,11 +130,6 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Remove downloadable domains --> <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> @@ -174,9 +169,11 @@ <magentoCLI command="cron:run" stepKey="runCronFirstTime"/> <magentoCLI command="cron:run" stepKey="runCronSecondTime"/> + <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> + <!-- Save exported file: file successfully downloaded --> - <actionGroup ref="DownloadFileByRowIndexActionGroup" stepKey="downloadCreatedProducts"> - <argument name="rowIndex" value="0"/> + <actionGroup ref="DownloadFileActionGroup" stepKey="downloadCreatedProducts"> + <argument name="fileName" value="{$grabNameFile}"/> </actionGroup> <!-- Go to Catalog > Products. Find ConfProd and delete it --> @@ -224,5 +221,10 @@ <argument name="image" value="MagentoLogo"/> </actionGroup> <closeTab stepKey="closeConfigChildProductPage"/> + + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="fileName" value="{$grabNameFile}"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml index b5efec8faec79..498fd5919aa26 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml @@ -81,10 +81,6 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> <!-- Delete configurable product creation --> <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> @@ -113,9 +109,16 @@ <magentoCLI command="cron:run" stepKey="runCron3"/> <magentoCLI command="cron:run" stepKey="runCron4"/> + <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> + <!-- Download product --> - <actionGroup ref="DownloadFileByRowIndexActionGroup" stepKey="downloadCreatedProducts"> - <argument name="rowIndex" value="0"/> + <actionGroup ref="DownloadFileActionGroup" stepKey="downloadCreatedProducts"> + <argument name="fileName" value="{$grabNameFile}"/> + </actionGroup> + + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="fileName" value="{$grabNameFile}"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml index 2ba0ac2fb6c93..cd18c2481b7fa 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml @@ -129,9 +129,16 @@ <magentoCLI command="cron:run" stepKey="runCron3"/> <magentoCLI command="cron:run" stepKey="runCron4"/> + <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> + <!-- Download product --> - <actionGroup ref="DownloadFileByRowIndexActionGroup" stepKey="downloadCreatedProducts"> - <argument name="rowIndex" value="0"/> + <actionGroup ref="DownloadFileActionGroup" stepKey="downloadCreatedProducts"> + <argument name="fileName" value="{$grabNameFile}"/> + </actionGroup> + + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="fileName" value="{$grabNameFile}"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml index 97dc8e052d190..1beabc9aa0014 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml @@ -79,10 +79,6 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> <!-- Delete simple product --> <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> @@ -112,9 +108,16 @@ <magentoCLI command="cron:run" stepKey="runCron3"/> <magentoCLI command="cron:run" stepKey="runCron4"/> + <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> + <!-- Download product --> - <actionGroup ref="DownloadFileByRowIndexActionGroup" stepKey="downloadCreatedProducts"> - <argument name="rowIndex" value="0"/> + <actionGroup ref="DownloadFileActionGroup" stepKey="downloadCreatedProducts"> + <argument name="fileName" value="{$grabNameFile}"/> + </actionGroup> + + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="fileName" value="{$grabNameFile}"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml index ff480b60a0fbf..6a7229e4bd77c 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml @@ -36,11 +36,6 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Delete product creations --> <deleteData createDataKey="createSimpleProductWithCustomAttributeSet" stepKey="deleteSimpleProductWithCustomAttributeSet"/> <deleteData createDataKey="createAttributeSet" stepKey="deleteAttributeSet"/> @@ -61,9 +56,17 @@ <magentoCLI command="cron:run" stepKey="runCron3"/> <magentoCLI command="cron:run" stepKey="runCron4"/> + <grabTextFrom selector="{{AdminExportAttributeSection.exportFileNameByPosition('0')}}" stepKey="grabNameFile"/> + <!-- Download product --> - <actionGroup ref="DownloadFileByRowIndexActionGroup" stepKey="downloadCreatedProducts"> - <argument name="rowIndex" value="0"/> + <actionGroup ref="DownloadFileActionGroup" stepKey="downloadCreatedProducts"> + <argument name="fileName" value="{$grabNameFile}"/> </actionGroup> + + <!-- Delete exported file --> + <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> + <argument name="fileName" value="{$grabNameFile}"/> + </actionGroup> + </test> </tests> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml index f9b07a59c8763..d6970feb4bb36 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml @@ -18,5 +18,9 @@ <element name="download" type="button" selector="//tr[@data-repeat-index='{{var}}']//a[text()='Download']" parameterized="true" timeout="30"/> <element name="delete" type="button" selector="//tr[@data-repeat-index='{{var}}']//a[text()='Delete']" parameterized="true" timeout="30"/> <element name="exportFileNameByPosition" type="text" selector="[data-role='grid'] tr[data-repeat-index='{{position}}'] div.data-grid-cell-content" parameterized="true"/> + <element name="fileName" type="text" selector="//div[@class='data-grid-cell-content'][text()='{{fileName}}']" parameterized="true"/> + <element name="selectByFileName" type="button" selector="//div[@class='data-grid-cell-content'][text()='{{fileName}}']/../..//button[@class='action-select']" parameterized="true"/> + <element name="downloadByFileName" type="button" selector="//div[@class='data-grid-cell-content'][text()='{{fileName}}']/../..//a[text()='Download']" parameterized="true" timeout="30"/> + <element name="deleteByFileName" type="button" selector="//div[@class='data-grid-cell-content'][text()='{{fileName}}']/../..//a[text()='Delete']" parameterized="true" timeout="30"/> </section> </sections> From 1c615c002a443ba8a969e7ced2ad8e653aa787a8 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Thu, 20 Feb 2020 18:46:20 -0600 Subject: [PATCH 100/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> --- ...eProductAndConfigurableProductsWithAssignedImagesTest.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml index cd18c2481b7fa..591ed4db8f974 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml @@ -97,11 +97,6 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> - <!-- Delete exported file --> - <actionGroup ref="DeleteExportedFileActionGroup" stepKey="deleteExportedFile"> - <argument name="rowIndex" value="0"/> - </actionGroup> - <!-- Delete configurable product creation --> <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> From 0c14fdfe7b01755aec6954a1d5c080fa4df2da1a Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Thu, 20 Feb 2020 22:51:56 -0600 Subject: [PATCH 101/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> --- ...leteDefaultCategoryChildrenActionGroup.xml | 35 +++++++++++++++++++ ...rontCatalogNavigationMenuUIDesktopTest.xml | 2 ++ 2 files changed, 37 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml new file mode 100644 index 0000000000000..2fb4e0e05887a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeleteDefaultCategoryChildrenActionGroup"> + <annotations> + <description>Deletes all children categories of Default Root Category.</description> + </annotations> + + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToAdminCategoryPage"/> + <executeInSelenium function="function ($webdriver) use ($I) { + $children = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., + \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a')); + while (!empty($children)) { + $I->click('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., + \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a'); + $I->waitForPageLoad(30); + $I->click('#delete'); + $I->waitForElementVisible('aside.confirm .modal-footer button.action-accept'); + $I->click('aside.confirm .modal-footer button.action-accept'); + $I->waitForPageLoad(30); + $I->waitForElementVisible('#messages div.message-success', 30); + $I->see('You deleted the category.', '#messages div.message-success'); + $children = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., + \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a')); + } + }" stepKey="deleteAllChildCategories"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml index e618adf80ab8b..9fbd6fddeb390 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml @@ -21,8 +21,10 @@ <before> <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="DeleteDefaultCategoryChildrenActionGroup" stepKey="deleteRootCategoryChildren"/> </before> <after> + <actionGroup ref="DeleteDefaultCategoryChildrenActionGroup" stepKey="deleteRootCategoryChildren"/> <actionGroup ref="AdminChangeStorefrontThemeActionGroup" stepKey="changeThemeToDefault"> <argument name="theme" value="{{MagentoLumaTheme.name}}"/> </actionGroup> From 6a8a103ce27d872e5687402a50ba6e594ae82194 Mon Sep 17 00:00:00 2001 From: Alexander Aleman <a.aleman@xsarus.nl> Date: Fri, 21 Feb 2020 10:39:36 +0100 Subject: [PATCH 102/369] Do not escape custom attributes --- .../Magento/Catalog/view/frontend/templates/product/image.phtml | 2 +- .../view/frontend/templates/product/image_with_borders.phtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml index 5a31f3d125c81..ef2ba856fad84 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml @@ -7,7 +7,7 @@ <?php /** @var $block \Magento\Catalog\Block\Product\Image */ ?> <img class="photo image <?= $block->escapeHtmlAttr($block->getClass()) ?>" - <?= $block->escapeHtml($block->getCustomAttributes()) ?> + <?= /* @noEscape */ $block->getCustomAttributes() ?> src="<?= $block->escapeUrl($block->getImageUrl()) ?>" width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>" diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml index 33f7620f1a1f5..8d0cd0294c48e 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml @@ -11,7 +11,7 @@ <span class="product-image-wrapper" style="padding-bottom: <?= ($block->getRatio() * 100) ?>%;"> <img class="<?= $block->escapeHtmlAttr($block->getClass()) ?>" - <?= $block->escapeHtmlAttr($block->getCustomAttributes()) ?> + <?= /* @noEscape */ $block->getCustomAttributes() ?> src="<?= $block->escapeUrl($block->getImageUrl()) ?>" max-width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" max-height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>" From 4d3a05d101723432d8610160552b563c25cc3703 Mon Sep 17 00:00:00 2001 From: Alexander Aleman <a.aleman@xsarus.nl> Date: Fri, 21 Feb 2020 10:45:36 +0100 Subject: [PATCH 103/369] Custom attributes can not be escaped in the current form --- .../Magento/Catalog/view/frontend/templates/product/image.phtml | 2 +- .../view/frontend/templates/product/image_with_borders.phtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml index ef2ba856fad84..2a251b00521d8 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml @@ -7,7 +7,7 @@ <?php /** @var $block \Magento\Catalog\Block\Product\Image */ ?> <img class="photo image <?= $block->escapeHtmlAttr($block->getClass()) ?>" - <?= /* @noEscape */ $block->getCustomAttributes() ?> + <?= /* @escapeNotVerified */ $block->getCustomAttributes() ?> src="<?= $block->escapeUrl($block->getImageUrl()) ?>" width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>" diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml index 8d0cd0294c48e..32d5ec8b288a9 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml @@ -11,7 +11,7 @@ <span class="product-image-wrapper" style="padding-bottom: <?= ($block->getRatio() * 100) ?>%;"> <img class="<?= $block->escapeHtmlAttr($block->getClass()) ?>" - <?= /* @noEscape */ $block->getCustomAttributes() ?> + <?= /* @escapeNotVerified */ $block->getCustomAttributes() ?> src="<?= $block->escapeUrl($block->getImageUrl()) ?>" max-width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" max-height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>" From b0ccd4f9fed45147101195d71a7af298d4d8ff4b Mon Sep 17 00:00:00 2001 From: Alexander Aleman <a.aleman@xsarus.nl> Date: Fri, 21 Feb 2020 12:13:24 +0100 Subject: [PATCH 104/369] correctly handle escaping --- .../Magento/Catalog/Block/Product/Image.php | 2 +- .../Catalog/Block/Product/ImageFactory.php | 21 ++----------------- .../frontend/templates/product/image.phtml | 6 +++++- .../product/image_with_borders.phtml | 6 +++++- 4 files changed, 13 insertions(+), 22 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/Image.php b/app/code/Magento/Catalog/Block/Product/Image.php index 7a7f9c0affc7d..ccc37029bedf7 100644 --- a/app/code/Magento/Catalog/Block/Product/Image.php +++ b/app/code/Magento/Catalog/Block/Product/Image.php @@ -14,7 +14,7 @@ * @method string getHeight() * @method string getLabel() * @method float getRatio() - * @method string getCustomAttributes() + * @method array getCustomAttributes() * @method string getClass() * @since 100.0.2 */ diff --git a/app/code/Magento/Catalog/Block/Product/ImageFactory.php b/app/code/Magento/Catalog/Block/Product/ImageFactory.php index 172cd794edfb9..2a0b31ec50942 100644 --- a/app/code/Magento/Catalog/Block/Product/ImageFactory.php +++ b/app/code/Magento/Catalog/Block/Product/ImageFactory.php @@ -67,23 +67,6 @@ public function __construct( $this->imageParamsBuilder = $imageParamsBuilder; } - /** - * Retrieve image custom attributes for HTML element - * - * @param array $attributes - * @return string - */ - private function getStringCustomAttributes(array $attributes): string - { - $result = []; - foreach ($attributes as $name => $value) { - if ($name != 'class') { - $result[] = $name . '="' . $value . '"'; - } - } - return !empty($result) ? implode(' ', $result) : ''; - } - /** * Retrieve image class for HTML element * @@ -161,7 +144,7 @@ public function create(Product $product, string $imageId, array $attributes = nu } $attributes = $attributes === null ? [] : $attributes; - + $data = [ 'data' => [ 'template' => 'Magento_Catalog::product/image_with_borders.phtml', @@ -170,7 +153,7 @@ public function create(Product $product, string $imageId, array $attributes = nu 'height' => $imageMiscParams['image_height'], 'label' => $this->getLabel($product, $imageMiscParams['image_type']), 'ratio' => $this->getRatio($imageMiscParams['image_width'], $imageMiscParams['image_height']), - 'custom_attributes' => $this->getStringCustomAttributes($attributes), + 'custom_attributes' => $attributes, 'class' => $this->getClass($attributes), 'product_id' => $product->getId() ], diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml index 2a251b00521d8..abd8038d266d9 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml @@ -7,7 +7,11 @@ <?php /** @var $block \Magento\Catalog\Block\Product\Image */ ?> <img class="photo image <?= $block->escapeHtmlAttr($block->getClass()) ?>" - <?= /* @escapeNotVerified */ $block->getCustomAttributes() ?> + <?php foreach ($block->getCustomAttributes() as $name => $value): ?> + <?php if ($name !== 'class'): ?> + <?= $block->escapeHtmlAttr($name) ?>="<?= $block->escapeHtmlAttr($value) ?>" + <?php endif; ?> + <?php endforeach; ?> src="<?= $block->escapeUrl($block->getImageUrl()) ?>" width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>" diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml index 32d5ec8b288a9..0ea0ef1aa7a95 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml @@ -11,7 +11,11 @@ <span class="product-image-wrapper" style="padding-bottom: <?= ($block->getRatio() * 100) ?>%;"> <img class="<?= $block->escapeHtmlAttr($block->getClass()) ?>" - <?= /* @escapeNotVerified */ $block->getCustomAttributes() ?> + <?php foreach ($block->getCustomAttributes() as $name => $value): ?> + <?php if ($name !== 'class'): ?> + <?= $block->escapeHtmlAttr($name) ?>="<?= $block->escapeHtmlAttr($value) ?>" + <?php endif; ?> + <?php endforeach; ?> src="<?= $block->escapeUrl($block->getImageUrl()) ?>" max-width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" max-height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>" From 79025f879e94c8b409a7d792588e4fe30ea14f36 Mon Sep 17 00:00:00 2001 From: Alexander Aleman <a.aleman@xsarus.nl> Date: Fri, 21 Feb 2020 13:18:08 +0100 Subject: [PATCH 105/369] Filter custom attributes and add changes to unit test --- .../Catalog/Block/Product/ImageFactory.php | 18 +++++++++++++++++- .../Unit/Block/Product/ImageFactoryTest.php | 7 +++++-- .../frontend/templates/product/image.phtml | 4 +--- .../templates/product/image_with_borders.phtml | 4 +--- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/ImageFactory.php b/app/code/Magento/Catalog/Block/Product/ImageFactory.php index 2a0b31ec50942..f025417095f29 100644 --- a/app/code/Magento/Catalog/Block/Product/ImageFactory.php +++ b/app/code/Magento/Catalog/Block/Product/ImageFactory.php @@ -67,6 +67,20 @@ public function __construct( $this->imageParamsBuilder = $imageParamsBuilder; } + /** + * Remove class from custom attributes + * + * @param array $attributes + * @return array + */ + private function filterCustomAttributes(array $attributes): array + { + if (isset($attributes['class'])) { + unset($attributes['class']); + } + return $attributes; + } + /** * Retrieve image class for HTML element * @@ -153,7 +167,7 @@ public function create(Product $product, string $imageId, array $attributes = nu 'height' => $imageMiscParams['image_height'], 'label' => $this->getLabel($product, $imageMiscParams['image_type']), 'ratio' => $this->getRatio($imageMiscParams['image_width'], $imageMiscParams['image_height']), - 'custom_attributes' => $attributes, + 'custom_attributes' => $this->filterCustomAttributes($attributes), 'class' => $this->getClass($attributes), 'product_id' => $product->getId() ], @@ -161,4 +175,6 @@ public function create(Product $product, string $imageId, array $attributes = nu return $this->objectManager->create(ImageBlock::class, $data); } + + } diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php index 95b06e40602bf..5a6fff4c729b2 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php @@ -144,7 +144,7 @@ private function getTestDataWithoutAttributes(): array 'height' => 100, 'label' => 'test_image_label', 'ratio' => 1, - 'custom_attributes' => '', + 'custom_attributes' => [], 'product_id' => null, 'class' => 'product-image-photo' ], @@ -202,7 +202,10 @@ private function getTestDataWithAttributes(): array 'height' => 50, 'label' => 'test_product_name', 'ratio' => 0.5, // <== - 'custom_attributes' => 'name_1="value_1" name_2="value_2"', + 'custom_attributes' => [ + 'name_1' => 'value_1', + 'name_2' => 'value_2', + ], 'product_id' => null, 'class' => 'my-class' ], diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml index abd8038d266d9..fcda005c655f9 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image.phtml @@ -8,9 +8,7 @@ <img class="photo image <?= $block->escapeHtmlAttr($block->getClass()) ?>" <?php foreach ($block->getCustomAttributes() as $name => $value): ?> - <?php if ($name !== 'class'): ?> - <?= $block->escapeHtmlAttr($name) ?>="<?= $block->escapeHtmlAttr($value) ?>" - <?php endif; ?> + <?= $block->escapeHtmlAttr($name) ?>="<?= $block->escapeHtmlAttr($value) ?>" <?php endforeach; ?> src="<?= $block->escapeUrl($block->getImageUrl()) ?>" width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml index 0ea0ef1aa7a95..6dcadfd2e4412 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/image_with_borders.phtml @@ -12,9 +12,7 @@ style="padding-bottom: <?= ($block->getRatio() * 100) ?>%;"> <img class="<?= $block->escapeHtmlAttr($block->getClass()) ?>" <?php foreach ($block->getCustomAttributes() as $name => $value): ?> - <?php if ($name !== 'class'): ?> - <?= $block->escapeHtmlAttr($name) ?>="<?= $block->escapeHtmlAttr($value) ?>" - <?php endif; ?> + <?= $block->escapeHtmlAttr($name) ?>="<?= $block->escapeHtmlAttr($value) ?>" <?php endforeach; ?> src="<?= $block->escapeUrl($block->getImageUrl()) ?>" max-width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>" From 5e84595da92ba3788edac27d8f35bc9a0e0e9d27 Mon Sep 17 00:00:00 2001 From: Alexander Aleman <a.aleman@xsarus.nl> Date: Fri, 21 Feb 2020 13:19:59 +0100 Subject: [PATCH 106/369] Remove empty lines --- app/code/Magento/Catalog/Block/Product/ImageFactory.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/ImageFactory.php b/app/code/Magento/Catalog/Block/Product/ImageFactory.php index f025417095f29..1dd55f75bdd0e 100644 --- a/app/code/Magento/Catalog/Block/Product/ImageFactory.php +++ b/app/code/Magento/Catalog/Block/Product/ImageFactory.php @@ -175,6 +175,4 @@ public function create(Product $product, string $imageId, array $attributes = nu return $this->objectManager->create(ImageBlock::class, $data); } - - } From 3f9254ddb96d9ce1333669de60d95b0c827a34e7 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Fri, 21 Feb 2020 09:23:33 -0600 Subject: [PATCH 107/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> --- .../Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml index 9fbd6fddeb390..22ec0048497fa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml @@ -159,7 +159,7 @@ </actionGroup> <!-- Submenu appears rightward --> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuLeftDirection('level0')}}" stepKey="assertTopLevelMenuLeftDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level0')}}" stepKey="assertTopLevelMenuLeftDirection"/> <!-- Nested level 1 & 5 --> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelTwoBlank.name$$)}}" stepKey="hoverCategoryLevelTwo"/> From bd97b8e4081fff71df3a73fe17921244844e872d Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Fri, 21 Feb 2020 16:43:24 -0600 Subject: [PATCH 108/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Refactored StorefrontCatalogNavigationMenuUIDesktopTest to make it isolated --- .../Catalog/Test/Mftf/Data/CategoryData.xml | 128 ++++++++++++++++++ ...rontCatalogNavigationMenuUIDesktopTest.xml | 111 +++++---------- .../StorefrontNavigationMenuSection.xml | 3 +- 3 files changed, 162 insertions(+), 80 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 6ffb4e1902424..1d7a163aeb58d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -127,4 +127,132 @@ <entity name="SubCategoryNonAnchor" extends="SubCategoryWithParent"> <requiredEntity type="custom_attribute">CustomAttributeCategoryNonAnchor</requiredEntity> </entity> + <entity name="SubCategoryWithParent" type="category"> + <data key="name" unique="suffix">ApiCategory</data> + <data key="is_active">true</data> + </entity> + <entity name="ConfigurableProductWithAttributeSet" type="category"> + <data key="name" unique="suffix">ApiCategory</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategory" type="category"> + <data key="name" unique="suffix">ApiCategory</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryA" type="category"> + <data key="name" unique="suffix">Category A</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest" type="category"> + <data key="name" unique="suffix">TEST</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest2" type="category"> + <data key="name" unique="suffix">_test2</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest3" type="category"> + <data key="name" unique="suffix">test 3</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategorySeveralProducts" type="category"> + <data key="name" unique="suffix">Category with several products</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest4" type="category"> + <data key="name" unique="suffix">test 4</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest5" type="category"> + <data key="name" unique="suffix">test 5</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest8" type="category"> + <data key="name" unique="suffix">test 8</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest6" type="category"> + <data key="name" unique="suffix">test 6</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest7" type="category"> + <data key="name" unique="suffix">test 7</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryLongTitle" type="category"> + <data key="name" unique="suffix">This is a very very very very very looong title</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryWithImage" type="category"> + <data key="name" unique="suffix">Category with image</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryTest0" type="category"> + <data key="name" unique="suffix">test 0</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryWithDescription" type="category"> + <data key="name" unique="suffix">Category with description & custom title</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiCategoryWithChildren" type="category"> + <data key="name" unique="suffix">Category with children</data> + <data key="is_active">true</data> + </entity> + <entity name="ApiSubCategoryWithParentLongName" type="category"> + <data key="name" unique="suffix">level 1 test category very very very long name</data> + <data key="name_lwr" unique="suffix">level 1 test category very very very long name</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> + <entity name="ApiSubCategoryWithParentLevel1" type="category"> + <data key="name" unique="suffix">level 1 test category name</data> + <data key="name_lwr" unique="suffix">level 1 test category name</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> + <entity name="ApiSubCategoryWithChildrenLevel1" type="category"> + <data key="name" unique="suffix">level 1 with children</data> + <data key="name_lwr" unique="suffix">level 1 with children</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> + <entity name="ApiSubCategoryWithChildrenLevel2" type="category"> + <data key="name" unique="suffix">level 2 with children</data> + <data key="name_lwr" unique="suffix">level 2 with children</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> + <entity name="ApiSubCategoryLevel3" type="category"> + <data key="name" unique="suffix">level 3 test</data> + <data key="name_lwr" unique="suffix">level 3 test</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> + <entity name="ApiSubCategoryLevel4" type="category"> + <data key="name" unique="suffix">level 4</data> + <data key="name_lwr" unique="suffix">level 4</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> + <entity name="ApiSubCategoryLevel4Test" type="category"> + <data key="name" unique="suffix">level 4 test</data> + <data key="name_lwr" unique="suffix">level 4 test</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> + <entity name="ApiSubCategoryLevel5" type="category"> + <data key="name" unique="suffix">level 5</data> + <data key="name_lwr" unique="suffix">level 5</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml index 22ec0048497fa..fe626dadb7866 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml @@ -21,10 +21,8 @@ <before> <!-- Login as admin --> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="DeleteDefaultCategoryChildrenActionGroup" stepKey="deleteRootCategoryChildren"/> </before> <after> - <actionGroup ref="DeleteDefaultCategoryChildrenActionGroup" stepKey="deleteRootCategoryChildren"/> <actionGroup ref="AdminChangeStorefrontThemeActionGroup" stepKey="changeThemeToDefault"> <argument name="theme" value="{{MagentoLumaTheme.name}}"/> </actionGroup> @@ -40,91 +38,51 @@ <!-- Open storefront --> <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStorefrontPage"/> - <!-- Assert no category - no menu --> - <dontSeeElement selector="{{StorefrontNavigationMenuSection.navigationMenu}}" stepKey="dontSeeMenu"/> - <!-- Assert single row - no hover state --> - <createData entity="ApiCategory" stepKey="createFirstCategoryBlank"> - <field key="name">Category A</field> - </createData> + <createData entity="ApiCategoryA" stepKey="createFirstCategoryBlank"/> <reloadPage stepKey="refreshPage"/> <waitForPageLoad stepKey="waitForBlankSingleRowAppear"/> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createFirstCategoryBlank.name$$)}}" stepKey="hoverFirstCategoryBlank"/> <dontSeeElement selector="{{StorefrontNavigationMenuSection.subItemLevelHover('level0')}}" stepKey="assertNoHoverState"/> <!-- Create categories --> - <createData entity="ApiCategory" stepKey="createSecondCategoryBlank"> - <field key="name">TEST</field> - </createData> - <createData entity="ApiCategory" stepKey="createThirdCategoryBlank"> - <field key="name">_test2</field> - </createData> - <createData entity="ApiCategory" stepKey="createFourthCategoryBlank"> - <field key="name">test 3</field> - </createData> - <createData entity="ApiCategory" stepKey="createFifthCategoryBlank"> - <field key="name">Category with several products</field> - </createData> - <createData entity="ApiCategory" stepKey="createSixthCategoryBlank"> - <field key="name">test 5</field> - </createData> - <createData entity="ApiCategory" stepKey="createSeventhCategoryBlank"> - <field key="name">test 8</field> - </createData> - <createData entity="ApiCategory" stepKey="createEighthCategoryBlank"> - <field key="name">This is a very very very very very looong title</field> - </createData> - <createData entity="ApiCategory" stepKey="createNinthCategoryBlank"> - <field key="name">test 6</field> - </createData> - <createData entity="ApiCategory" stepKey="createTenthCategoryBlank"> - <field key="name">test 7</field> - </createData> - <createData entity="ApiCategory" stepKey="createEleventhCategoryBlank"> - <field key="name">test 4</field> - </createData> - <createData entity="ApiCategory" stepKey="createTwelfthCategoryBlank"> - <field key="name">Category with image</field> - </createData> - <createData entity="ApiCategory" stepKey="createThirteenthCategoryBlank"> - <field key="name">test 0</field> - </createData> - <createData entity="ApiCategory" stepKey="createCategoryWithoutChildrenBlank"> - <field key="name">Category with description & custom title</field> - </createData> - <createData entity="ApiCategory" stepKey="createCategoryWithChildrenBlank"> - <field key="name">Category with children</field> - </createData> - <createData entity="SubCategoryWithParent" stepKey="createFirstCategoryLevelOneBlank"> - <field key="name">level 1 test category very very very long name</field> + <createData entity="ApiCategoryTest" stepKey="createSecondCategoryBlank"/> + <createData entity="ApiCategoryTest2" stepKey="createThirdCategoryBlank"/> + <createData entity="ApiCategoryTest3" stepKey="createFourthCategoryBlank"/> + <createData entity="ApiCategorySeveralProducts" stepKey="createFifthCategoryBlank"/> + <createData entity="ApiCategoryTest5" stepKey="createSixthCategoryBlank"/> + <createData entity="ApiCategoryTest8" stepKey="createSeventhCategoryBlank"/> + <createData entity="ApiCategoryLongTitle" stepKey="createEighthCategoryBlank"/> + <createData entity="ApiCategoryTest6" stepKey="createNinthCategoryBlank"/> + <createData entity="ApiCategoryTest7" stepKey="createTenthCategoryBlank"/> + <createData entity="ApiCategoryTest4" stepKey="createEleventhCategoryBlank"/> + <createData entity="ApiCategoryWithImage" stepKey="createTwelfthCategoryBlank"/> + <createData entity="ApiCategoryTest0" stepKey="createThirteenthCategoryBlank"/> + <createData entity="ApiCategoryWithDescription" stepKey="createCategoryWithoutChildrenBlank"/> + <createData entity="ApiCategoryWithChildren" stepKey="createCategoryWithChildrenBlank"/> + + <createData entity="ApiSubCategoryWithParentLongName" stepKey="createFirstCategoryLevelOneBlank"> <requiredEntity createDataKey="createCategoryWithChildrenBlank"/> </createData> - <createData entity="SubCategoryWithParent" stepKey="createSecondCategoryLevelOneBlank"> - <field key="name">level 1 test category name</field> + <createData entity="ApiSubCategoryWithParentLevel1" stepKey="createSecondCategoryLevelOneBlank"> <requiredEntity createDataKey="createCategoryWithChildrenBlank"/> </createData> - <createData entity="SubCategoryWithParent" stepKey="createThirdCategoryLevelOneBlank"> - <field key="name">level 1 with children</field> + <createData entity="ApiSubCategoryWithChildrenLevel1" stepKey="createThirdCategoryLevelOneBlank"> <requiredEntity createDataKey="createCategoryWithChildrenBlank"/> </createData> - <createData entity="SubCategoryWithParent" stepKey="createCategoryLevelTwoBlank"> - <field key="name">level 2 with children</field> + <createData entity="ApiSubCategoryWithChildrenLevel2" stepKey="createCategoryLevelTwoBlank"> <requiredEntity createDataKey="createThirdCategoryLevelOneBlank"/> </createData> - <createData entity="SubCategoryWithParent" stepKey="createCategoryLevelThreeBlank"> - <field key="name">level 3 test</field> + <createData entity="ApiSubCategoryLevel3" stepKey="createCategoryLevelThreeBlank"> <requiredEntity createDataKey="createCategoryLevelTwoBlank"/> </createData> - <createData entity="SubCategoryWithParent" stepKey="createFirstCategoryLevelFourBlank"> - <field key="name">level 4</field> + <createData entity="ApiSubCategoryLevel4" stepKey="createFirstCategoryLevelFourBlank"> <requiredEntity createDataKey="createCategoryLevelThreeBlank"/> </createData> - <createData entity="SubCategoryWithParent" stepKey="createSecondCategoryLevelFourBlank"> - <field key="name">level 4 test</field> + <createData entity="ApiSubCategoryLevel4Test" stepKey="createSecondCategoryLevelFourBlank"> <requiredEntity createDataKey="createCategoryLevelThreeBlank"/> </createData> - <createData entity="SubCategoryWithParent" stepKey="createCategoryLevelFiveBlank"> - <field key="name">level 5</field> + <createData entity="ApiSubCategoryLevel5" stepKey="createCategoryLevelFiveBlank"> <requiredEntity createDataKey="createSecondCategoryLevelFourBlank"/> </createData> @@ -158,18 +116,18 @@ <argument name="color" value="{{NavigationMenuColor.gray}}"/> </actionGroup> - <!-- Submenu appears rightward --> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level0')}}" stepKey="assertTopLevelMenuLeftDirection"/> + <!-- Submenu appears leftward --> + <seeElement selector="{{StorefrontNavigationMenuSection.submenu('level0')}}" stepKey="assertTopLevelMenu"/> <!-- Nested level 1 & 5 --> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelTwoBlank.name$$)}}" stepKey="hoverCategoryLevelTwo"/> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuLeftDirection('level1')}}" stepKey="seeLevelOneMenuLeftDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenu('level1')}}" stepKey="seeLevelOneMenu"/> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelThreeBlank.name$$)}}" stepKey="hoverCategoryLevelThree"/> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuLeftDirection('level2')}}" stepKey="seeLevelTwoMenuRightDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenu('level2')}}" stepKey="seeLevelTwoMenu"/> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createSecondCategoryLevelFourBlank.name$$)}}" stepKey="hoverCategoryLevelFour"/> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level3')}}" stepKey="seeLevelThreeMenuRightDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenu('level3')}}" stepKey="seeLevelThreeMenu"/> <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkSubcategoryHighlighted"> <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemLevelHover('level3')}}"/> @@ -202,9 +160,6 @@ <!-- Open storefront --> <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStorefront"/> - <!-- Assert no category - no menu --> - <dontSeeElement selector="{{StorefrontNavigationMenuSection.navigationMenu}}" stepKey="dontSeeMenuOnStorefront"/> - <!-- Create categories --> <createData entity="ApiCategory" stepKey="createFirstCategoryLuma"/> <createData entity="ApiCategory" stepKey="createSecondCategoryLuma"/> @@ -278,17 +233,17 @@ </actionGroup> <!-- Submenu appears rightward --> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level0')}}" stepKey="seeTopLevelRightDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenu('level0')}}" stepKey="seeTopLevel"/> <!-- Nested levels 1 & 5 --> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createSecondCategoryLevelTwoLuma.name$$)}}" stepKey="hoverThirdCategoryLevelTwo"/> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level1')}}" stepKey="seeFirstLevelRightDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenu('level1')}}" stepKey="seeFirstLevelMenu"/> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelThreeLuma.name$$)}}" stepKey="hoverOnCategoryLevelThree"/> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level2')}}" stepKey="seeSecondLevelRightDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenu('level2')}}" stepKey="seeSecondLevelMenu"/> <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelFourLuma.name$$)}}" stepKey="hoverOnCategoryLevelFour"/> - <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level3')}}" stepKey="seeThirdLevelRightDirection"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenu('level3')}}" stepKey="seeThirdLevelMenu"/> <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkSubcategoryHighlightedAfterHover"> <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemLevelHover('level3')}}"/> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml index 5741b50f877f6..d6432be2ef2ee 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml @@ -15,7 +15,6 @@ <element name="subItemByLevel" type="text" selector="li.{{itemLevel}}.parent ul.{{itemLevel}}" parameterized="true"/> <element name="itemActiveState" type="text" selector=".navigation .level0.active>.level-top"/> <element name="subItemActiveState" type="text" selector=".navigation .level0 .submenu .active>a"/> - <element name="submenuLeftDirection" type="text" selector="ul.{{itemLevel}}.submenu-reverse" parameterized="true"/> - <element name="submenuRightDirection" type="text" selector="ul.{{itemLevel}}:not(.submenu-reverse)" parameterized="true"/> + <element name="submenu" type="text" selector="ul.{{itemLevel}}" parameterized="true"/> </section> </sections> From 24cd5c2c7ddb6307d981c844f4a47d9dd69ce68b Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Fri, 21 Feb 2020 16:47:43 -0600 Subject: [PATCH 109/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Deleted DeleteDefaultCategoryChildrenActionGroup --- ...leteDefaultCategoryChildrenActionGroup.xml | 35 ------------------- 1 file changed, 35 deletions(-) delete mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml deleted file mode 100644 index 2fb4e0e05887a..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteDefaultCategoryChildrenActionGroup.xml +++ /dev/null @@ -1,35 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="DeleteDefaultCategoryChildrenActionGroup"> - <annotations> - <description>Deletes all children categories of Default Root Category.</description> - </annotations> - - <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToAdminCategoryPage"/> - <executeInSelenium function="function ($webdriver) use ($I) { - $children = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., - \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a')); - while (!empty($children)) { - $I->click('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., - \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a'); - $I->waitForPageLoad(30); - $I->click('#delete'); - $I->waitForElementVisible('aside.confirm .modal-footer button.action-accept'); - $I->click('aside.confirm .modal-footer button.action-accept'); - $I->waitForPageLoad(30); - $I->waitForElementVisible('#messages div.message-success', 30); - $I->see('You deleted the category.', '#messages div.message-success'); - $children = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., - \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a')); - } - }" stepKey="deleteAllChildCategories"/> - </actionGroup> -</actionGroups> From 26ff555d098c86bf539d1f37f3ffd26a07a6f4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Wed, 15 Jan 2020 12:09:07 +0100 Subject: [PATCH 110/369] Cleanup ObjectManager usage - Magento_Elasticsearch --- .../CategoryFieldsProvider.php | 18 +++--- .../Adapter/DataMapper/ProductDataMapper.php | 27 ++++---- .../FieldMapper/ProductFieldMapper.php | 32 +++++----- .../Elasticsearch5/SearchAdapter/Adapter.php | 39 +++++------ .../SearchAdapter/Query/Builder.php | 20 ++---- .../CategoryFieldsProvider.php | 18 +++--- .../BatchDataMapper/PriceFieldsProvider.php | 22 +++---- .../Model/Adapter/Elasticsearch.php | 61 ++++++++++-------- .../Product/FieldProvider/DynamicField.php | 16 ++--- .../FieldName/Resolver/CategoryName.php | 13 ++-- .../FieldName/Resolver/Position.php | 13 ++-- .../FieldName/Resolver/Price.php | 14 ++-- .../Product/FieldProvider/StaticField.php | 20 +++--- .../Model/DataProvider/Suggestions.php | 24 +++---- .../Elasticsearch/SearchAdapter/Adapter.php | 11 ++-- .../SearchAdapter/Aggregation/Builder.php | 22 ++++--- .../SearchAdapter/Filter/Builder/Term.php | 14 ++-- .../SearchAdapter/Query/Builder.php | 39 ++++++----- .../SearchAdapter/Query/Builder/Match.php | 34 +++++----- .../Query/Preprocessor/Stopwords.php | 64 +++++++++---------- 20 files changed, 247 insertions(+), 274 deletions(-) diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php index eb7874a936140..dd9a9d904ddfe 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php @@ -3,14 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper; -use Magento\Elasticsearch\Model\ResourceModel\Index; use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; +use Magento\Elasticsearch\Model\ResourceModel\Index; /** * Provide data mapping for categories fields @@ -34,19 +34,17 @@ class CategoryFieldsProvider implements AdditionalFieldsProviderInterface /** * @param Index $resourceIndex - * @param AttributeProvider|null $attributeAdapterProvider - * @param ResolverInterface|null $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param ResolverInterface $fieldNameResolver */ public function __construct( Index $resourceIndex, - AttributeProvider $attributeAdapterProvider = null, - ResolverInterface $fieldNameResolver = null + AttributeProvider $attributeAdapterProvider, + ResolverInterface $fieldNameResolver ) { $this->resourceIndex = $resourceIndex; - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver; } /** diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php index f0b7380397235..7007a8a6a8533 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php @@ -7,17 +7,16 @@ namespace Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; -use Magento\Elasticsearch\Model\Adapter\Container\Attribute as AttributeContainer; -use Magento\Elasticsearch\Model\Adapter\Document\Builder; -use Magento\Store\Model\StoreManagerInterface; use Magento\Customer\Api\Data\GroupInterface; -use Magento\Elasticsearch\Model\ResourceModel\Index; -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; +use Magento\Elasticsearch\Model\Adapter\Container\Attribute as AttributeContainer; use Magento\Elasticsearch\Model\Adapter\DataMapperInterface; -use Magento\Elasticsearch\Model\Adapter\FieldType\Date as DateFieldType; +use Magento\Elasticsearch\Model\Adapter\Document\Builder; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; +use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; +use Magento\Elasticsearch\Model\Adapter\FieldType\Date as DateFieldType; +use Magento\Elasticsearch\Model\ResourceModel\Index; +use Magento\Store\Model\StoreManagerInterface; /** * Don't use this product data mapper class. @@ -103,8 +102,8 @@ class ProductDataMapper implements DataMapperInterface * @param FieldMapperInterface $fieldMapper * @param StoreManagerInterface $storeManager * @param DateFieldType $dateFieldType - * @param AttributeProvider|null $attributeAdapterProvider - * @param ResolverInterface|null $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param ResolverInterface $fieldNameResolver */ public function __construct( Builder $builder, @@ -113,8 +112,8 @@ public function __construct( FieldMapperInterface $fieldMapper, StoreManagerInterface $storeManager, DateFieldType $dateFieldType, - AttributeProvider $attributeAdapterProvider = null, - ResolverInterface $fieldNameResolver = null + AttributeProvider $attributeAdapterProvider, + ResolverInterface $fieldNameResolver ) { $this->builder = $builder; $this->attributeContainer = $attributeContainer; @@ -122,10 +121,8 @@ public function __construct( $this->fieldMapper = $fieldMapper; $this->storeManager = $storeManager; $this->dateFieldType = $dateFieldType; - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver; $this->mediaGalleryRoles = [ self::MEDIA_ROLE_IMAGE, diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php index 5aea87e5e6ae1..86bfc8ea35b2b 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php @@ -3,22 +3,23 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper; +use Magento\Customer\Model\Session as CustomerSession; use Magento\Eav\Model\Config; +use Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldType; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; -use Magento\Framework\App\ObjectManager; -use Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldType; use Magento\Framework\Registry; use Magento\Store\Model\StoreManagerInterface as StoreManager; -use \Magento\Customer\Model\Session as CustomerSession; /** - * Class ProductFieldMapper + * Elasticsearch5 Product Field Mapper Adapter + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class ProductFieldMapper implements FieldMapperInterface { @@ -73,9 +74,9 @@ class ProductFieldMapper implements FieldMapperInterface * @param CustomerSession $customerSession * @param StoreManager $storeManager * @param Registry $coreRegistry - * @param ResolverInterface|null $fieldNameResolver - * @param AttributeProvider|null $attributeAdapterProvider - * @param FieldProviderInterface|null $fieldProvider + * @param ResolverInterface $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param FieldProviderInterface $fieldProvider */ public function __construct( Config $eavConfig, @@ -83,21 +84,18 @@ public function __construct( CustomerSession $customerSession, StoreManager $storeManager, Registry $coreRegistry, - ResolverInterface $fieldNameResolver = null, - AttributeProvider $attributeAdapterProvider = null, - FieldProviderInterface $fieldProvider = null + ResolverInterface $fieldNameResolver, + AttributeProvider $attributeAdapterProvider, + FieldProviderInterface $fieldProvider ) { $this->eavConfig = $eavConfig; $this->fieldType = $fieldType; $this->customerSession = $customerSession; $this->storeManager = $storeManager; $this->coreRegistry = $coreRegistry; - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldProvider = $fieldProvider ?: ObjectManager::getInstance() - ->get(FieldProviderInterface::class); + $this->fieldNameResolver = $fieldNameResolver; + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldProvider = $fieldProvider; } /** diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php index 0ae347d5791ad..3b40db4787767 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php @@ -5,13 +5,13 @@ */ namespace Magento\Elasticsearch\Elasticsearch5\SearchAdapter; -use Magento\Framework\App\ObjectManager; +use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder as AggregationBuilder; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; +use Magento\Elasticsearch\SearchAdapter\QueryContainerFactory; +use Magento\Elasticsearch\SearchAdapter\ResponseFactory; use Magento\Framework\Search\AdapterInterface; use Magento\Framework\Search\RequestInterface; use Magento\Framework\Search\Response\QueryResponse; -use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder as AggregationBuilder; -use Magento\Elasticsearch\SearchAdapter\ConnectionManager; -use \Magento\Elasticsearch\SearchAdapter\ResponseFactory; use Psr\Log\LoggerInterface; /** @@ -44,7 +44,7 @@ class Adapter implements AdapterInterface protected $aggregationBuilder; /** - * @var \Magento\Elasticsearch\SearchAdapter\QueryContainerFactory + * @var QueryContainerFactory */ private $queryContainerFactory; @@ -54,19 +54,15 @@ class Adapter implements AdapterInterface * @var array */ private static $emptyRawResponse = [ - "hits" => - [ - "hits" => [] - ], - "aggregations" => - [ - "price_bucket" => [], - "category_bucket" => - [ - "buckets" => [] - - ] + 'hits' => [ + 'hits' => [] + ], + 'aggregations' => [ + 'price_bucket' => [], + 'category_bucket' => [ + 'buckets' => [] ] + ] ]; /** @@ -79,7 +75,7 @@ class Adapter implements AdapterInterface * @param Mapper $mapper * @param ResponseFactory $responseFactory * @param AggregationBuilder $aggregationBuilder - * @param \Magento\Elasticsearch\SearchAdapter\QueryContainerFactory $queryContainerFactory + * @param QueryContainerFactory $queryContainerFactory * @param LoggerInterface $logger */ public function __construct( @@ -87,16 +83,15 @@ public function __construct( Mapper $mapper, ResponseFactory $responseFactory, AggregationBuilder $aggregationBuilder, - \Magento\Elasticsearch\SearchAdapter\QueryContainerFactory $queryContainerFactory, - LoggerInterface $logger = null + QueryContainerFactory $queryContainerFactory, + LoggerInterface $logger ) { $this->connectionManager = $connectionManager; $this->mapper = $mapper; $this->responseFactory = $responseFactory; $this->aggregationBuilder = $aggregationBuilder; $this->queryContainerFactory = $queryContainerFactory; - $this->logger = $logger ?: ObjectManager::getInstance() - ->get(LoggerInterface::class); + $this->logger = $logger; } /** diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php index 75c675663f03f..b75621191dae7 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php @@ -56,17 +56,20 @@ class Builder * @param SearchIndexNameResolver $searchIndexNameResolver * @param AggregationBuilder $aggregationBuilder * @param ScopeResolverInterface $scopeResolver + * @param Sort|null $sortBuilder */ public function __construct( Config $clientConfig, SearchIndexNameResolver $searchIndexNameResolver, AggregationBuilder $aggregationBuilder, - ScopeResolverInterface $scopeResolver + ScopeResolverInterface $scopeResolver, + ?Sort $sortBuilder = null ) { $this->clientConfig = $clientConfig; $this->searchIndexNameResolver = $searchIndexNameResolver; $this->aggregationBuilder = $aggregationBuilder; $this->scopeResolver = $scopeResolver; + $this->sortBuilder = $sortBuilder ?: ObjectManager::getInstance()->get(Sort::class); } /** @@ -88,7 +91,7 @@ public function initQuery(RequestInterface $request) 'from' => $request->getFrom(), 'size' => $request->getSize(), 'stored_fields' => ['_id', '_score'], - 'sort' => $this->getSortBuilder()->getSort($request), + 'sort' => $this->sortBuilder->getSort($request), 'query' => [], ], ]; @@ -109,17 +112,4 @@ public function initAggregations( ) { return $this->aggregationBuilder->build($request, $searchQuery); } - - /** - * Get sort builder instance. - * - * @return Sort - */ - private function getSortBuilder() - { - if (null === $this->sortBuilder) { - $this->sortBuilder = ObjectManager::getInstance()->get(Sort::class); - } - return $this->sortBuilder; - } } diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php index 0e130c24e79d3..8e9de47aa7951 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php @@ -3,14 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Elasticsearch\Model\Adapter\BatchDataMapper; -use Magento\Elasticsearch\Model\ResourceModel\Index; use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; +use Magento\Elasticsearch\Model\ResourceModel\Index; /** * Provide data mapping for categories fields @@ -34,19 +34,17 @@ class CategoryFieldsProvider implements AdditionalFieldsProviderInterface /** * @param Index $resourceIndex - * @param AttributeProvider|null $attributeAdapterProvider - * @param ResolverInterface|null $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param ResolverInterface $fieldNameResolver */ public function __construct( Index $resourceIndex, - AttributeProvider $attributeAdapterProvider = null, - ResolverInterface $fieldNameResolver = null + AttributeProvider $attributeAdapterProvider, + ResolverInterface $fieldNameResolver ) { $this->resourceIndex = $resourceIndex; - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php index 56c84593256be..33e0993daba8d 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php @@ -3,16 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Elasticsearch\Model\Adapter\BatchDataMapper; -use Magento\Elasticsearch\Model\ResourceModel\Index; -use Magento\Store\Model\StoreManagerInterface; use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface; use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; +use Magento\Elasticsearch\Model\ResourceModel\Index; +use Magento\Store\Model\StoreManagerInterface; /** * Provide data mapping for price fields @@ -48,23 +48,21 @@ class PriceFieldsProvider implements AdditionalFieldsProviderInterface * @param Index $resourceIndex * @param DataProvider $dataProvider * @param StoreManagerInterface $storeManager - * @param AttributeProvider|null $attributeAdapterProvider - * @param ResolverInterface|null $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param ResolverInterface $fieldNameResolver */ public function __construct( Index $resourceIndex, DataProvider $dataProvider, StoreManagerInterface $storeManager, - AttributeProvider $attributeAdapterProvider = null, - ResolverInterface $fieldNameResolver = null + AttributeProvider $attributeAdapterProvider, + ResolverInterface $fieldNameResolver ) { $this->resourceIndex = $resourceIndex; $this->dataProvider = $dataProvider; $this->storeManager = $storeManager; - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver; } /** @@ -73,7 +71,7 @@ public function __construct( public function getFields(array $productIds, $storeId) { $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId(); - + $priceData = $this->dataProvider->getSearchableAttribute('price') ? $this->resourceIndex->getPriceIndexData($productIds, $storeId) : []; diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php index fa193d86c03c7..d2ffbfdc34756 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php @@ -3,10 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Elasticsearch\Model\Adapter; -use Magento\Framework\App\ObjectManager; +use Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface; +use Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver; +use Magento\Elasticsearch\Model\Config; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; +use Magento\Framework\Exception\LocalizedException; +use Psr\Log\LoggerInterface; /** * Elasticsearch adapter @@ -37,18 +43,13 @@ class Elasticsearch */ protected $documentDataMapper; - /** - * @var \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver - */ - protected $indexNameResolver; - /** * @var FieldMapperInterface */ protected $fieldMapper; /** - * @var \Magento\Elasticsearch\Model\Config + * @var Config */ protected $clientConfig; @@ -58,15 +59,20 @@ class Elasticsearch protected $client; /** - * @var \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface + * @var BuilderInterface */ protected $indexBuilder; /** - * @var \Psr\Log\LoggerInterface + * @var LoggerInterface */ protected $logger; + /** + * @var IndexNameResolver + */ + protected $indexNameResolver; + /** * @var array */ @@ -80,27 +86,27 @@ class Elasticsearch /** * Constructor for Elasticsearch adapter. * - * @param \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager + * @param ConnectionManager $connectionManager * @param DataMapperInterface $documentDataMapper * @param FieldMapperInterface $fieldMapper - * @param \Magento\Elasticsearch\Model\Config $clientConfig - * @param \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface $indexBuilder - * @param \Psr\Log\LoggerInterface $logger - * @param \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver $indexNameResolver - * @param array $options + * @param Config $clientConfig + * @param BuilderInterface $indexBuilder + * @param LoggerInterface $logger + * @param IndexNameResolver $indexNameResolver * @param BatchDataMapperInterface $batchDocumentDataMapper - * @throws \Magento\Framework\Exception\LocalizedException + * @param array $options + * @throws LocalizedException */ public function __construct( - \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager, + ConnectionManager $connectionManager, DataMapperInterface $documentDataMapper, FieldMapperInterface $fieldMapper, - \Magento\Elasticsearch\Model\Config $clientConfig, - \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface $indexBuilder, - \Psr\Log\LoggerInterface $logger, - \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver $indexNameResolver, - $options = [], - BatchDataMapperInterface $batchDocumentDataMapper = null + Config $clientConfig, + BuilderInterface $indexBuilder, + LoggerInterface $logger, + IndexNameResolver $indexNameResolver, + BatchDataMapperInterface $batchDocumentDataMapper, + $options = [] ) { $this->connectionManager = $connectionManager; $this->documentDataMapper = $documentDataMapper; @@ -109,14 +115,13 @@ public function __construct( $this->indexBuilder = $indexBuilder; $this->logger = $logger; $this->indexNameResolver = $indexNameResolver; - $this->batchDocumentDataMapper = $batchDocumentDataMapper ?: - ObjectManager::getInstance()->get(BatchDataMapperInterface::class); + $this->batchDocumentDataMapper = $batchDocumentDataMapper; try { $this->client = $this->connectionManager->getConnection($options); } catch (\Exception $e) { $this->logger->critical($e); - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('The search failed because of a search engine misconfiguration.') ); } @@ -126,14 +131,14 @@ public function __construct( * Retrieve Elasticsearch server status * * @return bool - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function ping() { try { $response = $this->client->ping(); } catch (\Exception $e) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Could not ping search engine: %1', $e->getMessage()) ); } diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php index 76bc7a15e47a7..16131a281c231 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php @@ -8,18 +8,17 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider; use Magento\Catalog\Api\CategoryListInterface; +use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface - as FieldTypeConverterInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ConverterInterface as IndexTypeConverterInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface as FieldNameResolver; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface + as FieldTypeConverterInterface; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Catalog\Model\ResourceModel\Category\Collection; -use Magento\Framework\App\ObjectManager; /** * Provide dynamic fields for product. @@ -83,7 +82,7 @@ class DynamicField implements FieldProviderInterface * @param CategoryListInterface $categoryList * @param FieldNameResolver $fieldNameResolver * @param AttributeProvider $attributeAdapterProvider - * @param Collection|null $categoryCollection + * @param Collection $categoryCollection */ public function __construct( FieldTypeConverterInterface $fieldTypeConverter, @@ -93,7 +92,7 @@ public function __construct( CategoryListInterface $categoryList, FieldNameResolver $fieldNameResolver, AttributeProvider $attributeAdapterProvider, - Collection $categoryCollection = null + Collection $categoryCollection ) { $this->groupRepository = $groupRepository; $this->searchCriteriaBuilder = $searchCriteriaBuilder; @@ -102,8 +101,7 @@ public function __construct( $this->categoryList = $categoryList; $this->fieldNameResolver = $fieldNameResolver; $this->attributeAdapterProvider = $attributeAdapterProvider; - $this->categoryCollection = $categoryCollection ?: - ObjectManager::getInstance()->get(Collection::class); + $this->categoryCollection = $categoryCollection; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CategoryName.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CategoryName.php index 5824aca6cdd54..03240034b959d 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CategoryName.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CategoryName.php @@ -8,10 +8,9 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; use Magento\Framework\Registry; use Magento\Store\Model\StoreManagerInterface as StoreManager; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; -use Magento\Framework\App\ObjectManager; /** * Resolver field name for Category name attribute. @@ -33,13 +32,11 @@ class CategoryName implements ResolverInterface * @param Registry $coreRegistry */ public function __construct( - StoreManager $storeManager = null, - Registry $coreRegistry = null + StoreManager $storeManager, + Registry $coreRegistry ) { - $this->storeManager = $storeManager ?: ObjectManager::getInstance() - ->get(StoreManager::class); - $this->coreRegistry = $coreRegistry ?: ObjectManager::getInstance() - ->get(Registry::class); + $this->storeManager = $storeManager; + $this->coreRegistry = $coreRegistry; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Position.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Position.php index 044d5d8da9a6c..9f91e82275434 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Position.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Position.php @@ -8,10 +8,9 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; use Magento\Framework\Registry; use Magento\Store\Model\StoreManagerInterface as StoreManager; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; -use Magento\Framework\App\ObjectManager; /** * Resolver field name for position attribute. @@ -33,13 +32,11 @@ class Position implements ResolverInterface * @param Registry $coreRegistry */ public function __construct( - StoreManager $storeManager = null, - Registry $coreRegistry = null + StoreManager $storeManager, + Registry $coreRegistry ) { - $this->storeManager = $storeManager ?: ObjectManager::getInstance() - ->get(StoreManager::class); - $this->coreRegistry = $coreRegistry ?: ObjectManager::getInstance() - ->get(Registry::class); + $this->storeManager = $storeManager; + $this->coreRegistry = $coreRegistry; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Price.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Price.php index 12e53ca2bd714..b55707ea7009f 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Price.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Price.php @@ -7,14 +7,14 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver; -use Magento\Framework\App\ObjectManager; use Magento\Customer\Model\Session as CustomerSession; -use Magento\Store\Model\StoreManagerInterface as StoreManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; +use Magento\Store\Model\StoreManagerInterface as StoreManager; /** * Resolver field name for price attribute. + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Price implements ResolverInterface { @@ -33,13 +33,11 @@ class Price implements ResolverInterface * @param StoreManager $storeManager */ public function __construct( - CustomerSession $customerSession = null, - StoreManager $storeManager = null + CustomerSession $customerSession, + StoreManager $storeManager ) { - $this->storeManager = $storeManager ?: ObjectManager::getInstance() - ->get(StoreManager::class); - $this->customerSession = $customerSession ?: ObjectManager::getInstance() - ->get(CustomerSession::class); + $this->storeManager = $storeManager; + $this->customerSession = $customerSession; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php index 7a5d6fcdcc1b1..d20c898c6c0e2 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php @@ -7,19 +7,18 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider; -use Magento\Framework\App\ObjectManager; -use Magento\Eav\Model\Config; use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Eav\Model\Config; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface - as FieldTypeConverterInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ConverterInterface as IndexTypeConverterInterface; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface - as FieldTypeResolver; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ResolverInterface as FieldIndexResolver; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface + as FieldTypeConverterInterface; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface + as FieldTypeResolver; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; /** @@ -74,7 +73,7 @@ class StaticField implements FieldProviderInterface * @param FieldTypeResolver $fieldTypeResolver * @param FieldIndexResolver $fieldIndexResolver * @param AttributeProvider $attributeAdapterProvider - * @param FieldName\ResolverInterface|null $fieldNameResolver + * @param FieldName\ResolverInterface $fieldNameResolver * @param array $excludedAttributes */ public function __construct( @@ -84,7 +83,7 @@ public function __construct( FieldTypeResolver $fieldTypeResolver, FieldIndexResolver $fieldIndexResolver, AttributeProvider $attributeAdapterProvider, - FieldName\ResolverInterface $fieldNameResolver = null, + FieldName\ResolverInterface $fieldNameResolver, array $excludedAttributes = [] ) { $this->eavConfig = $eavConfig; @@ -93,8 +92,7 @@ public function __construct( $this->fieldTypeResolver = $fieldTypeResolver; $this->fieldIndexResolver = $fieldIndexResolver; $this->attributeAdapterProvider = $attributeAdapterProvider; - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(FieldName\ResolverInterface::class); + $this->fieldNameResolver = $fieldNameResolver; $this->excludedAttributes = $excludedAttributes; } diff --git a/app/code/Magento/Elasticsearch/Model/DataProvider/Suggestions.php b/app/code/Magento/Elasticsearch/Model/DataProvider/Suggestions.php index c4fab39dfde61..45669ba345183 100644 --- a/app/code/Magento/Elasticsearch/Model/DataProvider/Suggestions.php +++ b/app/code/Magento/Elasticsearch/Model/DataProvider/Suggestions.php @@ -3,37 +3,39 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Elasticsearch\Model\DataProvider; -use Magento\Store\Model\ScopeInterface; -use Magento\Search\Model\QueryInterface; use Magento\AdvancedSearch\Model\SuggestedQueriesInterface; use Magento\Elasticsearch\Model\Config; use Magento\Elasticsearch\SearchAdapter\ConnectionManager; -use Magento\Search\Model\QueryResultFactory; -use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Search\Model\QueryInterface; +use Magento\Search\Model\QueryResultFactory; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface as StoreManager; /** - * Class Suggestions + * Elasticsearch Suggestions Data Provider */ class Suggestions implements SuggestedQueriesInterface { /** - * @deprecated + * @deprecated this constant is no longer used * @see SuggestedQueriesInterface::SEARCH_SUGGESTION_COUNT */ const CONFIG_SUGGESTION_COUNT = 'catalog/search/search_suggestion_count'; /** - * @deprecated + * @deprecated this constant is no longer used * @see SuggestedQueriesInterface::SEARCH_SUGGESTION_COUNT_RESULTS_ENABLED */ const CONFIG_SUGGESTION_COUNT_RESULTS_ENABLED = 'catalog/search/search_suggestion_count_results_enabled'; /** - * @deprecated + * @deprecated this constant is no longer used * @see SuggestedQueriesInterface::SEARCH_SUGGESTION_ENABLED */ const CONFIG_SUGGESTION_ENABLED = 'catalog/search/search_suggestion_enabled'; @@ -126,7 +128,7 @@ public function getItems(QueryInterface $query) public function isResultsCountEnabled() { return $this->scopeConfig->isSetFlag( - self::CONFIG_SUGGESTION_COUNT_RESULTS_ENABLED, + self::SEARCH_SUGGESTION_COUNT_RESULTS_ENABLED, ScopeInterface::SCOPE_STORE ); } @@ -203,7 +205,7 @@ private function fetchQuery(array $query) private function getSearchSuggestionsCount() { return (int)$this->scopeConfig->getValue( - self::CONFIG_SUGGESTION_COUNT, + self::SEARCH_SUGGESTION_COUNT, ScopeInterface::SCOPE_STORE ); } @@ -216,7 +218,7 @@ private function getSearchSuggestionsCount() private function isSuggestionsAllowed() { $isSuggestionsEnabled = $this->scopeConfig->isSetFlag( - self::CONFIG_SUGGESTION_ENABLED, + self::SEARCH_SUGGESTION_ENABLED, ScopeInterface::SCOPE_STORE ); $isEnabled = $this->config->isElasticsearchEnabled(); diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php index 6f9ef552351fd..08f57a3c60ac0 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php @@ -3,13 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Elasticsearch\SearchAdapter; -use Magento\Framework\App\ObjectManager; +use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder as AggregationBuilder; use Magento\Framework\Search\AdapterInterface; use Magento\Framework\Search\RequestInterface; -use Magento\Framework\Search\Response\QueryResponse; -use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder as AggregationBuilder; /** * Elasticsearch Search Adapter @@ -57,14 +57,13 @@ public function __construct( Mapper $mapper, ResponseFactory $responseFactory, AggregationBuilder $aggregationBuilder, - QueryContainerFactory $queryContainerFactory = null + QueryContainerFactory $queryContainerFactory ) { $this->connectionManager = $connectionManager; $this->mapper = $mapper; $this->responseFactory = $responseFactory; $this->aggregationBuilder = $aggregationBuilder; - $this->queryContainerFactory = $queryContainerFactory - ?: ObjectManager::getInstance()->get(QueryContainerFactory::class); + $this->queryContainerFactory = $queryContainerFactory; } /** diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php index 1e9b60da74a5b..3a9e3ca036597 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php @@ -3,15 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Elasticsearch\SearchAdapter\Aggregation; +use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder\BucketBuilderInterface; use Magento\Elasticsearch\SearchAdapter\QueryContainer; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Search\RequestInterface; use Magento\Framework\Search\Dynamic\DataProviderInterface; -use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder\BucketBuilderInterface; +use Magento\Framework\Search\RequestInterface; +/** + * Elasticsearch aggregation builder + */ class Builder { /** @@ -32,32 +35,31 @@ class Builder /** * @var QueryContainer */ - private $query = null; + private $query; /** * @param DataProviderInterface[] $dataProviderContainer * @param BucketBuilderInterface[] $aggregationContainer - * @param DataProviderFactory|null $dataProviderFactory + * @param DataProviderFactory $dataProviderFactory */ public function __construct( array $dataProviderContainer, array $aggregationContainer, - DataProviderFactory $dataProviderFactory = null + DataProviderFactory $dataProviderFactory ) { $this->dataProviderContainer = array_map( - function (DataProviderInterface $dataProvider) { + static function (DataProviderInterface $dataProvider) { return $dataProvider; }, $dataProviderContainer ); $this->aggregationContainer = array_map( - function (BucketBuilderInterface $bucketBuilder) { + static function (BucketBuilderInterface $bucketBuilder) { return $bucketBuilder; }, $aggregationContainer ); - $this->dataProviderFactory = $dataProviderFactory - ?: ObjectManager::getInstance()->get(DataProviderFactory::class); + $this->dataProviderFactory = $dataProviderFactory; } /** diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php index d88c7e53d813a..a4f9e7bcea71d 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php @@ -3,15 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Elasticsearch\SearchAdapter\Filter\Builder; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Search\Request\Filter\Term as TermFilterRequest; -use Magento\Framework\Search\Request\FilterInterface as RequestFilterInterface; -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface as FieldTypeConverterInterface; +use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; +use Magento\Framework\Search\Request\Filter\Term as TermFilterRequest; +use Magento\Framework\Search\Request\FilterInterface as RequestFilterInterface; /** * Term filter builder @@ -41,12 +42,11 @@ class Term implements FilterInterface */ public function __construct( FieldMapperInterface $fieldMapper, - AttributeProvider $attributeAdapterProvider = null, + AttributeProvider $attributeAdapterProvider, array $integerTypeAttributes = [] ) { $this->fieldMapper = $fieldMapper; - $this->attributeAdapterProvider = $attributeAdapterProvider - ?? ObjectManager::getInstance()->get(AttributeProvider::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; $this->integerTypeAttributes = array_merge($this->integerTypeAttributes, $integerTypeAttributes); } diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php index 0bea8683692f2..778e73e6518e8 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php @@ -3,11 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Elasticsearch\SearchAdapter\Query; +use Magento\Elasticsearch\Model\Config; +use Magento\Elasticsearch\SearchAdapter\Query\Builder\Aggregation as AggregationBuilder; use Magento\Elasticsearch\SearchAdapter\Query\Builder\Sort; -use Magento\Framework\App\ObjectManager; +use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; +use Magento\Framework\App\ScopeResolverInterface; use Magento\Framework\Search\RequestInterface; use Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Query\Builder as Elasticsearch5Builder; @@ -24,6 +28,24 @@ class Builder extends Elasticsearch5Builder */ private $sortBuilder; + /** + * @param Config $clientConfig + * @param SearchIndexNameResolver $searchIndexNameResolver + * @param AggregationBuilder $aggregationBuilder + * @param ScopeResolverInterface $scopeResolver + * @param Sort $sortBuilder + */ + public function __construct( + Config $clientConfig, + SearchIndexNameResolver $searchIndexNameResolver, + AggregationBuilder $aggregationBuilder, + ScopeResolverInterface $scopeResolver, + Sort $sortBuilder + ) { + parent::__construct($clientConfig, $searchIndexNameResolver, $aggregationBuilder, $scopeResolver); + $this->sortBuilder = $sortBuilder; + } + /** * Set initial settings for query. * @@ -42,23 +64,10 @@ public function initQuery(RequestInterface $request) 'from' => $request->getFrom(), 'size' => $request->getSize(), 'fields' => ['_id', '_score'], - 'sort' => $this->getSortBuilder()->getSort($request), + 'sort' => $this->sortBuilder->getSort($request), 'query' => [], ], ]; return $searchQuery; } - - /** - * Get sort builder instance. - * - * @return Sort - */ - private function getSortBuilder() - { - if (null === $this->sortBuilder) { - $this->sortBuilder = ObjectManager::getInstance()->get(Sort::class); - } - return $this->sortBuilder; - } } diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php index 8a44b58d35fb8..0afbbfd849e16 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php @@ -3,17 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Elasticsearch\SearchAdapter\Query\Builder; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver; +use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; use Magento\Elasticsearch\Model\Config; use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool; -use Magento\Framework\App\ObjectManager; +use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface; use Magento\Framework\Search\Request\Query\BoolExpression; use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface; -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; -use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface; /** * Builder for match query. @@ -59,28 +60,25 @@ class Match implements QueryInterface /** * @param FieldMapperInterface $fieldMapper * @param PreprocessorInterface[] $preprocessorContainer - * @param AttributeProvider|null $attributeProvider - * @param TypeResolver|null $fieldTypeResolver - * @param ValueTransformerPool|null $valueTransformerPool - * @param Config|null $config + * @param AttributeProvider $attributeProvider + * @param TypeResolver $fieldTypeResolver + * @param ValueTransformerPool $valueTransformerPool + * @param Config $config */ public function __construct( FieldMapperInterface $fieldMapper, array $preprocessorContainer, - AttributeProvider $attributeProvider = null, - TypeResolver $fieldTypeResolver = null, - ValueTransformerPool $valueTransformerPool = null, - Config $config = null + AttributeProvider $attributeProvider, + TypeResolver $fieldTypeResolver, + ValueTransformerPool $valueTransformerPool, + Config $config ) { $this->fieldMapper = $fieldMapper; $this->preprocessorContainer = $preprocessorContainer; - $this->attributeProvider = $attributeProvider ?? ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldTypeResolver = $fieldTypeResolver ?? ObjectManager::getInstance() - ->get(TypeResolver::class); - $this->valueTransformerPool = $valueTransformerPool ?? ObjectManager::getInstance() - ->get(ValueTransformerPool::class); - $this->config = $config ?? ObjectManager::getInstance()->get(Config::class); + $this->attributeProvider = $attributeProvider; + $this->fieldTypeResolver = $fieldTypeResolver; + $this->valueTransformerPool = $valueTransformerPool; + $this->config = $config; } /** diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Preprocessor/Stopwords.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Preprocessor/Stopwords.php index d8d1a071611c1..e21baa231b14b 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Preprocessor/Stopwords.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Preprocessor/Stopwords.php @@ -5,12 +5,20 @@ */ namespace Magento\Elasticsearch\SearchAdapter\Query\Preprocessor; -use Magento\Framework\Filesystem\Directory\ReadFactory; use Magento\Elasticsearch\Model\Adapter\Index\Config\EsConfigInterface; -use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface; +use Magento\Framework\App\Cache\Type\Config; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Filesystem\Directory\ReadFactory; +use Magento\Framework\Locale\Resolver; use Magento\Framework\Module\Dir; +use Magento\Framework\Module\Dir\Reader; +use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Store\Model\StoreManagerInterface; /** + * Elasticsearch stopwords preprocessor + * * @api * @since 100.1.0 */ @@ -27,13 +35,13 @@ class Stopwords implements PreprocessorInterface const STOPWORDS_FILE_MODIFICATION_TIME_GAP = 900; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface * @since 100.1.0 */ protected $storeManager; /** - * @var \Magento\Framework\Locale\Resolver + * @var Resolver * @since 100.1.0 */ protected $localeResolver; @@ -45,7 +53,7 @@ class Stopwords implements PreprocessorInterface protected $readFactory; /** - * @var \Magento\Framework\App\Cache\Type\Config + * @var Config * @since 100.1.0 */ protected $configCache; @@ -57,7 +65,7 @@ class Stopwords implements PreprocessorInterface protected $esConfig; /** - * @var \Magento\Framework\Module\Dir\Reader + * @var Reader * @since 100.1.0 */ protected $moduleDirReader; @@ -73,31 +81,33 @@ class Stopwords implements PreprocessorInterface private $stopwordsDirectory; /** - * @var \Magento\Framework\Serialize\SerializerInterface + * @var SerializerInterface */ private $serializer; /** * Initialize dependencies. * - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Framework\Locale\Resolver $localeResolver + * @param StoreManagerInterface $storeManager + * @param Resolver $localeResolver * @param ReadFactory $readFactory - * @param \Magento\Framework\App\Cache\Type\Config $configCache + * @param Config $configCache * @param EsConfigInterface $esConfig - * @param \Magento\Framework\Module\Dir\Reader $moduleDirReader + * @param Reader $moduleDirReader * @param string $stopwordsModule * @param string $stopwordsDirectory + * @param SerializerInterface|null $serializer */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\Locale\Resolver $localeResolver, + StoreManagerInterface $storeManager, + Resolver $localeResolver, ReadFactory $readFactory, - \Magento\Framework\App\Cache\Type\Config $configCache, + Config $configCache, EsConfigInterface $esConfig, - \Magento\Framework\Module\Dir\Reader $moduleDirReader, + Reader $moduleDirReader, $stopwordsModule = '', - $stopwordsDirectory = '' + $stopwordsDirectory = '', + ?SerializerInterface $serializer = null ) { $this->storeManager = $storeManager; $this->localeResolver = $localeResolver; @@ -107,10 +117,11 @@ public function __construct( $this->moduleDirReader = $moduleDirReader; $this->stopwordsModule = $stopwordsModule; $this->stopwordsDirectory = $stopwordsDirectory; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); } /** - * {@inheritdoc} + * @inheritDoc * @since 100.1.0 */ public function process($query) @@ -136,11 +147,11 @@ protected function getStopwordsList() $fileStats = $source->stat($filename); if (((time() - $fileStats['mtime']) > self::STOPWORDS_FILE_MODIFICATION_TIME_GAP) && ($cachedValue = $this->configCache->load(self::CACHE_ID))) { - $stopwords = $this->getSerializer()->unserialize($cachedValue); + $stopwords = $this->serializer->unserialize($cachedValue); } else { $fileContent = $source->readFile($filename); $stopwords = explode("\n", $fileContent); - $this->configCache->save($this->getSerializer()->serialize($stopwords), self::CACHE_ID); + $this->configCache->save($this->serializer->serialize($stopwords), self::CACHE_ID); } return $stopwords; } @@ -160,19 +171,4 @@ protected function getStopwordsFile() $stopwordsFile = isset($stopwordsInfo[$locale]) ? $stopwordsInfo[$locale] : $stopwordsInfo['default']; return $stopwordsFile; } - - /** - * Get serializer - * - * @return \Magento\Framework\Serialize\SerializerInterface - * @deprecated 100.2.0 - */ - private function getSerializer() - { - if (null === $this->serializer) { - $this->serializer = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Serialize\SerializerInterface::class); - } - return $this->serializer; - } } From afac7ead70c7c11329c56d3264ee51df655dc995 Mon Sep 17 00:00:00 2001 From: alexander-aleman <35915533+alexander-aleman@users.noreply.github.com> Date: Sat, 22 Feb 2020 10:36:36 +0100 Subject: [PATCH 111/369] Update expectation for return type of getCustomAttributes --- .../Magento/Swatches/Block/Product/ListProductTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php index 460e4559a0e84..e6f566ac156db 100644 --- a/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Swatches/Block/Product/ListProductTest.php @@ -166,7 +166,7 @@ private function assertProductImage(array $images, string $area, array $expectat $this->updateProductImages($images); $productImage = $this->listingBlock->getImage($this->productRepository->get('configurable'), $area); $this->assertInstanceOf(Image::class, $productImage); - $this->assertEquals($productImage->getCustomAttributes(), ''); + $this->assertEquals($productImage->getCustomAttributes(), []); $this->assertEquals($productImage->getClass(), 'product-image-photo'); $this->assertEquals($productImage->getRatio(), 1.25); $this->assertEquals($productImage->getLabel(), $expectation['label']); From 5c29e1998e399696046e8562f8ec1a43bfd2ab42 Mon Sep 17 00:00:00 2001 From: Tu Nguyen <ladiesman9x@gmail.com> Date: Mon, 24 Feb 2020 01:11:07 +0700 Subject: [PATCH 112/369] Remove unused requirejs alias resources defined --- app/code/Magento/Theme/view/frontend/requirejs-config.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/code/Magento/Theme/view/frontend/requirejs-config.js b/app/code/Magento/Theme/view/frontend/requirejs-config.js index b5ffd358c893b..e14c93d329a07 100644 --- a/app/code/Magento/Theme/view/frontend/requirejs-config.js +++ b/app/code/Magento/Theme/view/frontend/requirejs-config.js @@ -27,9 +27,7 @@ var config = { 'menu': 'mage/menu', 'popupWindow': 'mage/popup-window', 'validation': 'mage/validation/validation', - 'welcome': 'Magento_Theme/js/view/welcome', 'breadcrumbs': 'Magento_Theme/js/view/breadcrumbs', - 'criticalCssLoader': 'Magento_Theme/js/view/critical-css-loader', 'jquery/ui': 'jquery/compat', 'cookieStatus': 'Magento_Theme/js/cookie-status' } From d00b206e048d04c72382dd592bfed550fa891e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Fri, 24 Jan 2020 10:23:22 +0100 Subject: [PATCH 113/369] Improve dashboard charts - migrate to chart.js --- app/code/Magento/Backend/Block/Dashboard.php | 45 +- .../Backend/Block/Dashboard/Diagrams.php | 6 + .../Magento/Backend/Block/Dashboard/Graph.php | 2 + .../Magento/Backend/Block/Dashboard/Grids.php | 12 +- .../Backend/Block/Dashboard/Orders/Grid.php | 26 +- .../Magento/Backend/Block/Dashboard/Sales.php | 23 +- .../Backend/Block/Dashboard/Tab/Amounts.php | 6 +- .../Backend/Block/Dashboard/Tab/Orders.php | 6 +- .../Backend/Block/Dashboard/Totals.php | 37 +- .../Adminhtml/Dashboard/AjaxBlock.php | 50 +- .../Adminhtml/Dashboard/Chart/Amounts.php | 64 + .../Adminhtml/Dashboard/Chart/Orders.php | 64 + .../Controller/Adminhtml/Dashboard/Tunnel.php | 10 +- .../Magento/Backend/Model/Dashboard/Chart.php | 97 + .../Backend/Model/Dashboard/Chart/Date.php | 94 + ...tOrderGraphImageOnDashboardActionGroup.xml | 2 +- .../Mftf/Section/AdminDashboardSection.xml | 2 +- .../Adminhtml/Dashboard/TunnelTest.php | 196 - .../Test/Unit/Model/Dashboard/ChartTest.php | 226 + .../Backend/ViewModel/ChartDisabled.php | 72 + .../Backend/ViewModel/ChartsPeriod.php | 40 + .../layout/adminhtml_dashboard_index.xml | 60 +- .../adminhtml/templates/dashboard/chart.phtml | 36 + .../templates/dashboard/chart/disabled.phtml | 22 + .../templates/dashboard/chart/period.phtml | 32 + .../adminhtml/templates/dashboard/graph.phtml | 5 + .../templates/dashboard/graph/disabled.phtml | 5 + .../adminhtml/templates/dashboard/index.phtml | 75 +- .../templates/dashboard/totalbar.phtml | 37 +- .../templates/dashboard/totalbar/script.phtml | 27 + .../view/adminhtml/web/js/dashboard/chart.js | 157 + .../view/adminhtml/web/js/dashboard/totals.js | 60 + .../Magento/Ui/view/base/requirejs-config.js | 2 + .../css/source/module/pages/_dashboard.less | 13 +- .../Backend/Block/Dashboard/GraphTest.php | 34 - .../Adminhtml/Dashboard/AjaxBlockTest.php | 15 +- .../Controller/Adminhtml/DashboardTest.php | 70 - lib/web/chartjs/Chart.bundle.js | 20755 ++++++++++++++++ lib/web/chartjs/Chart.bundle.min.js | 7 + lib/web/chartjs/Chart.css | 47 + lib/web/chartjs/Chart.js | 16151 ++++++++++++ lib/web/chartjs/Chart.min.css | 1 + lib/web/chartjs/Chart.min.js | 7 + 43 files changed, 38199 insertions(+), 499 deletions(-) create mode 100644 app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Amounts.php create mode 100644 app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Orders.php create mode 100644 app/code/Magento/Backend/Model/Dashboard/Chart.php create mode 100644 app/code/Magento/Backend/Model/Dashboard/Chart/Date.php delete mode 100644 app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/TunnelTest.php create mode 100644 app/code/Magento/Backend/Test/Unit/Model/Dashboard/ChartTest.php create mode 100644 app/code/Magento/Backend/ViewModel/ChartDisabled.php create mode 100644 app/code/Magento/Backend/ViewModel/ChartsPeriod.php create mode 100644 app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart.phtml create mode 100644 app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart/disabled.phtml create mode 100644 app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart/period.phtml create mode 100644 app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar/script.phtml create mode 100644 app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js create mode 100644 app/code/Magento/Backend/view/adminhtml/web/js/dashboard/totals.js delete mode 100644 dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/GraphTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php create mode 100644 lib/web/chartjs/Chart.bundle.js create mode 100644 lib/web/chartjs/Chart.bundle.min.js create mode 100644 lib/web/chartjs/Chart.css create mode 100644 lib/web/chartjs/Chart.js create mode 100644 lib/web/chartjs/Chart.min.css create mode 100644 lib/web/chartjs/Chart.min.js diff --git a/app/code/Magento/Backend/Block/Dashboard.php b/app/code/Magento/Backend/Block/Dashboard.php index e1e87d8d4c5a3..28d3eeae9a1c6 100644 --- a/app/code/Magento/Backend/Block/Dashboard.php +++ b/app/code/Magento/Backend/Block/Dashboard.php @@ -3,14 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Backend\Block; /** + * Class used to initialize layout for MBO Dashboard + * @deprecated dashboard graphs were migrated to dynamic chart.js solution + * @see dashboard in adminhtml_dashboard_index.xml + * * @api * @since 100.0.2 */ -class Dashboard extends \Magento\Backend\Block\Template +class Dashboard extends Template { /** * Location of the "Enable Chart" config param @@ -23,42 +28,8 @@ class Dashboard extends \Magento\Backend\Block\Template protected $_template = 'Magento_Backend::dashboard/index.phtml'; /** - * @return void - */ - protected function _prepareLayout() - { - $this->addChild('lastOrders', \Magento\Backend\Block\Dashboard\Orders\Grid::class); - - $this->addChild('totals', \Magento\Backend\Block\Dashboard\Totals::class); - - $this->addChild('sales', \Magento\Backend\Block\Dashboard\Sales::class); - - $isChartEnabled = $this->_scopeConfig->getValue( - self::XML_PATH_ENABLE_CHARTS, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ); - if ($isChartEnabled) { - $block = $this->getLayout()->createBlock(\Magento\Backend\Block\Dashboard\Diagrams::class); - } else { - $block = $this->getLayout()->createBlock( - \Magento\Backend\Block\Template::class - )->setTemplate( - 'dashboard/graph/disabled.phtml' - )->setConfigUrl( - $this->getUrl( - 'adminhtml/system_config/edit', - ['section' => 'admin', '_fragment' => 'admin_dashboard-link'] - ) - ); - } - $this->setChild('diagrams', $block); - - $this->addChild('grids', \Magento\Backend\Block\Dashboard\Grids::class); - - parent::_prepareLayout(); - } - - /** + * Get url for switch action + * * @return string */ public function getSwitchUrl() diff --git a/app/code/Magento/Backend/Block/Dashboard/Diagrams.php b/app/code/Magento/Backend/Block/Dashboard/Diagrams.php index 12770bc12268d..7ebb81e3e2fbf 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Diagrams.php +++ b/app/code/Magento/Backend/Block/Dashboard/Diagrams.php @@ -7,6 +7,8 @@ /** * Adminhtml dashboard diagram tabs + * @deprecated dashboard graphs were migrated to dynamic chart.js solution + * @see dashboard.diagrams in adminhtml_dashboard_index.xml * * @author Magento Core Team <core@magentocommerce.com> */ @@ -18,6 +20,8 @@ class Diagrams extends \Magento\Backend\Block\Widget\Tabs protected $_template = 'Magento_Backend::widget/tabshoriz.phtml'; /** + * Internal constructor, that is called from real constructor + * * @return void */ protected function _construct() @@ -28,6 +32,8 @@ protected function _construct() } /** + * Preparing global layout + * * @return $this */ protected function _prepareLayout() diff --git a/app/code/Magento/Backend/Block/Dashboard/Graph.php b/app/code/Magento/Backend/Block/Dashboard/Graph.php index 527bb2136b4c5..db95a64636c3a 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Graph.php +++ b/app/code/Magento/Backend/Block/Dashboard/Graph.php @@ -9,6 +9,8 @@ /** * Adminhtml dashboard google chart block + * @deprecated dashboard graphs were migrated to dynamic chart.js solution + * @see dashboard.chart.amounts and dashboard.chart.orders in adminhtml_dashboard_index.xml * * @author Magento Core Team <core@magentocommerce.com> */ diff --git a/app/code/Magento/Backend/Block/Dashboard/Grids.php b/app/code/Magento/Backend/Block/Dashboard/Grids.php index 986854da93cf8..f40aaaf33fed7 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Grids.php +++ b/app/code/Magento/Backend/Block/Dashboard/Grids.php @@ -3,14 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Block\Dashboard; +use Magento\Backend\Block\Dashboard\Tab\Products\Ordered; +use Magento\Backend\Block\Widget\Tabs; + /** * Adminhtml dashboard bottom tabs * + * @api * @author Magento Core Team <core@magentocommerce.com> */ -class Grids extends \Magento\Backend\Block\Widget\Tabs +class Grids extends Tabs { /** * @var string @@ -18,6 +24,8 @@ class Grids extends \Magento\Backend\Block\Widget\Tabs protected $_template = 'Magento_Backend::widget/tabshoriz.phtml'; /** + * Internal constructor, that is called from real constructor + * * @return void */ protected function _construct() @@ -49,7 +57,7 @@ protected function _prepareLayout() [ 'label' => __('Bestsellers'), 'content' => $this->getLayout()->createBlock( - \Magento\Backend\Block\Dashboard\Tab\Products\Ordered::class + Ordered::class )->toHtml(), 'active' => true ] diff --git a/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php b/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php index 0a73430aad0f3..8b3574e223236 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php +++ b/app/code/Magento/Backend/Block/Dashboard/Orders/Grid.php @@ -5,36 +5,42 @@ */ namespace Magento\Backend\Block\Dashboard\Orders; +use Magento\Backend\Block\Template\Context; +use Magento\Backend\Helper\Data; +use Magento\Framework\Module\Manager; +use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; + /** * Adminhtml dashboard recent orders grid * + * @api * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.DepthOfInheritance) */ class Grid extends \Magento\Backend\Block\Dashboard\Grid { /** - * @var \Magento\Reports\Model\ResourceModel\Order\CollectionFactory + * @var CollectionFactory */ protected $_collectionFactory; /** - * @var \Magento\Framework\Module\Manager + * @var Manager */ protected $_moduleManager; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Backend\Helper\Data $backendHelper - * @param \Magento\Framework\Module\Manager $moduleManager - * @param \Magento\Reports\Model\ResourceModel\Order\CollectionFactory $collectionFactory + * @param Context $context + * @param Data $backendHelper + * @param Manager $moduleManager + * @param CollectionFactory $collectionFactory * @param array $data */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Backend\Helper\Data $backendHelper, - \Magento\Framework\Module\Manager $moduleManager, - \Magento\Reports\Model\ResourceModel\Order\CollectionFactory $collectionFactory, + Context $context, + Data $backendHelper, + Manager $moduleManager, + CollectionFactory $collectionFactory, array $data = [] ) { $this->_moduleManager = $moduleManager; diff --git a/app/code/Magento/Backend/Block/Dashboard/Sales.php b/app/code/Magento/Backend/Block/Dashboard/Sales.php index b388339460102..ebe0932c3fa3b 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Sales.php +++ b/app/code/Magento/Backend/Block/Dashboard/Sales.php @@ -3,14 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Block\Dashboard; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\Module\Manager; +use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; + /** * Adminhtml dashboard sales statistics bar * + * @api * @author Magento Core Team <core@magentocommerce.com> */ -class Sales extends \Magento\Backend\Block\Dashboard\Bar +class Sales extends Bar { /** * @var string @@ -18,20 +25,20 @@ class Sales extends \Magento\Backend\Block\Dashboard\Bar protected $_template = 'Magento_Backend::dashboard/salebar.phtml'; /** - * @var \Magento\Framework\Module\Manager + * @var Manager */ protected $_moduleManager; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Reports\Model\ResourceModel\Order\CollectionFactory $collectionFactory - * @param \Magento\Framework\Module\Manager $moduleManager + * @param Context $context + * @param CollectionFactory $collectionFactory + * @param Manager $moduleManager * @param array $data */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Reports\Model\ResourceModel\Order\CollectionFactory $collectionFactory, - \Magento\Framework\Module\Manager $moduleManager, + Context $context, + CollectionFactory $collectionFactory, + Manager $moduleManager, array $data = [] ) { $this->_moduleManager = $moduleManager; diff --git a/app/code/Magento/Backend/Block/Dashboard/Tab/Amounts.php b/app/code/Magento/Backend/Block/Dashboard/Tab/Amounts.php index 715d399fa1c7a..26243891fa007 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Tab/Amounts.php +++ b/app/code/Magento/Backend/Block/Dashboard/Tab/Amounts.php @@ -4,13 +4,15 @@ * See COPYING.txt for license details. */ +namespace Magento\Backend\Block\Dashboard\Tab; + /** * Adminhtml dashboard order amounts diagram + * @deprecated dashboard graphs were migrated to dynamic chart.js solution + * @see dashboard.chart.amounts in adminhtml_dashboard_index.xml * * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Backend\Block\Dashboard\Tab; - class Amounts extends \Magento\Backend\Block\Dashboard\Graph { /** diff --git a/app/code/Magento/Backend/Block/Dashboard/Tab/Orders.php b/app/code/Magento/Backend/Block/Dashboard/Tab/Orders.php index 72863f573c3bc..f88e6bb694671 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Tab/Orders.php +++ b/app/code/Magento/Backend/Block/Dashboard/Tab/Orders.php @@ -4,13 +4,15 @@ * See COPYING.txt for license details. */ +namespace Magento\Backend\Block\Dashboard\Tab; + /** * Adminhtml dashboard orders diagram + * @deprecated dashboard graphs were migrated to dynamic chart.js solution + * @see dashboard.chart.orders in adminhtml_dashboard_index.xml * * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Backend\Block\Dashboard\Tab; - class Orders extends \Magento\Backend\Block\Dashboard\Graph { /** diff --git a/app/code/Magento/Backend/Block/Dashboard/Totals.php b/app/code/Magento/Backend/Block/Dashboard/Totals.php index 20bcfebe31a8d..3921148acd33a 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Totals.php +++ b/app/code/Magento/Backend/Block/Dashboard/Totals.php @@ -3,18 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -/** - * Adminhtml dashboard totals bar - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Backend\Block\Dashboard; +use Magento\Backend\Block\Template\Context; +use Magento\Framework\Module\Manager; +use Magento\Reports\Model\ResourceModel\Order\Collection; +use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; +use Magento\Store\Model\Store; + /** - * Totals block. + * Adminhtml dashboard totals bar + * @api */ -class Totals extends \Magento\Backend\Block\Dashboard\Bar +class Totals extends Bar { /** * @var string @@ -22,20 +25,20 @@ class Totals extends \Magento\Backend\Block\Dashboard\Bar protected $_template = 'Magento_Backend::dashboard/totalbar.phtml'; /** - * @var \Magento\Framework\Module\Manager + * @var Manager */ protected $_moduleManager; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Reports\Model\ResourceModel\Order\CollectionFactory $collectionFactory - * @param \Magento\Framework\Module\Manager $moduleManager + * @param Context $context + * @param CollectionFactory $collectionFactory + * @param Manager $moduleManager * @param array $data */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Reports\Model\ResourceModel\Order\CollectionFactory $collectionFactory, - \Magento\Framework\Module\Manager $moduleManager, + Context $context, + CollectionFactory $collectionFactory, + Manager $moduleManager, array $data = [] ) { $this->_moduleManager = $moduleManager; @@ -60,7 +63,7 @@ protected function _prepareLayout() ); $period = $this->getRequest()->getParam('period', '24h'); - /* @var $collection \Magento\Reports\Model\ResourceModel\Order\Collection */ + /* @var $collection Collection */ $collection = $this->_collectionFactory->create()->addCreateAtPeriodFilter( $period )->calculateTotals( @@ -80,7 +83,7 @@ protected function _prepareLayout() } elseif (!$collection->isLive()) { $collection->addFieldToFilter( 'store_id', - ['eq' => $this->_storeManager->getStore(\Magento\Store\Model\Store::ADMIN_CODE)->getId()] + ['eq' => $this->_storeManager->getStore(Store::ADMIN_CODE)->getId()] ); } } @@ -94,5 +97,7 @@ protected function _prepareLayout() $this->addTotal(__('Tax'), $totals->getTax()); $this->addTotal(__('Shipping'), $totals->getShipping()); $this->addTotal(__('Quantity'), $totals->getQuantity() * 1, true); + + return $this; } } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/AjaxBlock.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/AjaxBlock.php index 0b0b707557035..3ad0ab592f445 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/AjaxBlock.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/AjaxBlock.php @@ -1,32 +1,45 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Controller\Adminhtml\Dashboard; -class AjaxBlock extends \Magento\Backend\Controller\Adminhtml\Dashboard +use Magento\Backend\App\Action\Context; +use Magento\Backend\Block\Dashboard\Totals; +use Magento\Backend\Controller\Adminhtml\Dashboard; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Raw; +use Magento\Framework\Controller\Result\RawFactory; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\View\LayoutFactory; + +/** + * Class used to retrieve content of dashboard totals block via ajax + */ +class AjaxBlock extends Dashboard implements HttpPostActionInterface { /** - * @var \Magento\Framework\Controller\Result\RawFactory + * @var RawFactory */ protected $resultRawFactory; /** - * @var \Magento\Framework\View\LayoutFactory + * @var LayoutFactory */ protected $layoutFactory; /** - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory - * @param \Magento\Framework\View\LayoutFactory $layoutFactory + * @param Context $context + * @param RawFactory $resultRawFactory + * @param LayoutFactory $layoutFactory */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, - \Magento\Framework\View\LayoutFactory $layoutFactory + Context $context, + RawFactory $resultRawFactory, + LayoutFactory $layoutFactory ) { parent::__construct($context); $this->resultRawFactory = $resultRawFactory; @@ -34,23 +47,22 @@ public function __construct( } /** - * @return \Magento\Framework\Controller\Result\Raw + * Retrieve block content via ajax + * + * @return Raw */ public function execute() { $output = ''; $blockTab = $this->getRequest()->getParam('block'); - $blockClassSuffix = str_replace( - ' ', - '\\', - ucwords(str_replace('_', ' ', $blockTab)) - ); - if (in_array($blockTab, ['tab_orders', 'tab_amounts', 'totals'])) { + + if ($blockTab === 'totals') { $output = $this->layoutFactory->create() - ->createBlock('Magento\\Backend\\Block\\Dashboard\\' . $blockClassSuffix) + ->createBlock(Totals::class) ->toHtml(); } - /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */ + + /** @var Raw $resultRaw */ $resultRaw = $this->resultRawFactory->create(); return $resultRaw->setContents($output); } diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Amounts.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Amounts.php new file mode 100644 index 0000000000000..f46ab3a3a7d26 --- /dev/null +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Amounts.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Controller\Adminhtml\Dashboard\Chart; + +use Magento\Backend\App\Action\Context; +use Magento\Backend\Controller\Adminhtml\Dashboard; +use Magento\Backend\Model\Dashboard\Chart; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\JsonFactory; + +/** + * Get order amounts chart data controller + */ +class Amounts extends Dashboard implements HttpPostActionInterface +{ + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var Chart + */ + private $chart; + + /** + * Amounts constructor. + * + * @param Context $context + * @param JsonFactory $resultJsonFactory + * @param Chart $chart + */ + public function __construct( + Context $context, + JsonFactory $resultJsonFactory, + Chart $chart + ) { + parent::__construct($context); + $this->resultJsonFactory = $resultJsonFactory; + $this->chart = $chart; + } + + /** + * Get chart data + * + * @return Json + */ + public function execute(): Json + { + $data = [ + 'data' => $this->chart->getByPeriod($this->_request->getParam('period'), 'revenue'), + 'label' => __('Revenue') + ]; + + return $this->resultJsonFactory->create() + ->setData($data); + } +} diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Orders.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Orders.php new file mode 100644 index 0000000000000..11d3d875f1626 --- /dev/null +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Orders.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Controller\Adminhtml\Dashboard\Chart; + +use Magento\Backend\App\Action\Context; +use Magento\Backend\Controller\Adminhtml\Dashboard; +use Magento\Backend\Model\Dashboard\Chart; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\Result\JsonFactory; + +/** + * Get order quantities chart data controller + */ +class Orders extends Dashboard implements HttpPostActionInterface +{ + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var Chart + */ + private $chart; + + /** + * Orders constructor. + * + * @param Context $context + * @param JsonFactory $resultJsonFactory + * @param Chart $chart + */ + public function __construct( + Context $context, + JsonFactory $resultJsonFactory, + Chart $chart + ) { + parent::__construct($context); + $this->resultJsonFactory = $resultJsonFactory; + $this->chart = $chart; + } + + /** + * Get chart data + * + * @return Json + */ + public function execute(): Json + { + $data = [ + 'data' => $this->chart->getByPeriod($this->_request->getParam('period'), 'quantity'), + 'label' => __('Quantity') + ]; + + return $this->resultJsonFactory->create() + ->setData($data); + } +} diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Tunnel.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Tunnel.php index eb991baf13f41..2e0ed47a97bbf 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Tunnel.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Tunnel.php @@ -1,16 +1,21 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Backend\Controller\Adminhtml\Dashboard; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Controller\Result; use Magento\Framework\Encryption\Helper\Security; -class Tunnel extends \Magento\Backend\Controller\Adminhtml\Dashboard +/** + * Dashboard graph image tunnel + * @deprecated dashboard graphs were migrated to dynamic chart.js solution + * @see dashboard.chart.amounts and dashboard.chart.orders in adminhtml_dashboard_index.xml + */ +class Tunnel extends \Magento\Backend\Controller\Adminhtml\Dashboard implements HttpGetActionInterface { /** * @var \Magento\Framework\Controller\Result\RawFactory @@ -50,6 +55,7 @@ public function execute() $newHash = $helper->getChartDataHash($gaData); if (Security::compareStrings($newHash, $gaHash)) { $params = null; + // phpcs:ignore Magento2.Functions.DiscouragedFunction $paramsJson = base64_decode(urldecode($gaData)); if ($paramsJson) { $params = json_decode($paramsJson, true); diff --git a/app/code/Magento/Backend/Model/Dashboard/Chart.php b/app/code/Magento/Backend/Model/Dashboard/Chart.php new file mode 100644 index 0000000000000..d986a7b8f7063 --- /dev/null +++ b/app/code/Magento/Backend/Model/Dashboard/Chart.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Model\Dashboard; + +use Magento\Backend\Helper\Dashboard\Data as DataHelper; +use Magento\Backend\Helper\Dashboard\Order as OrderHelper; +use Magento\Backend\Model\Dashboard\Chart\Date; +use Magento\Framework\App\RequestInterface; + +/** + * Dashboard chart data retriever + */ +class Chart +{ + /** + * @var RequestInterface + */ + private $request; + + /** + * @var Date + */ + private $dateRetriever; + + /** + * @var OrderHelper + */ + private $orderHelper; + + /** + * @var DataHelper + */ + private $dataHelper; + + /** + * Chart constructor. + * @param RequestInterface $request + * @param Date $dateRetriever + * @param OrderHelper $orderHelper + * @param DataHelper $dataHelper + */ + public function __construct( + RequestInterface $request, + Date $dateRetriever, + OrderHelper $orderHelper, + DataHelper $dataHelper + ) { + $this->request = $request; + $this->dateRetriever = $dateRetriever; + $this->orderHelper = $orderHelper; + $this->dataHelper = $dataHelper; + } + + /** + * Get chart data by period and chart type parameter + * + * @param string $period + * @param string $chartParam + * + * @return array + */ + public function getByPeriod($period, $chartParam): array + { + $this->orderHelper->setParam('store', $this->request->getParam('store')); + $this->orderHelper->setParam('website', $this->request->getParam('website')); + $this->orderHelper->setParam('group', $this->request->getParam('group')); + + $availablePeriods = array_keys($this->dataHelper->getDatePeriods()); + $this->orderHelper->setParam( + 'period', + $period && in_array($period, $availablePeriods, false) ? $period : '24h' + ); + + $dates = $this->dateRetriever->getByPeriod($period); + $collection = $this->orderHelper->getCollection(); + + $data = []; + + if ($collection->count() > 0) { + foreach ($dates as $date) { + $item = $collection->getItemByColumnValue('range', $date); + + $data[] = [ + 'x' => $date, + 'y' => $item ? (float)$item->getData($chartParam) : 0 + ]; + } + } + + return $data; + } +} diff --git a/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php b/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php new file mode 100644 index 0000000000000..c91c42f940228 --- /dev/null +++ b/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Model\Dashboard\Chart; + +use DateTimeZone; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; + +/** + * Dashboard chart dates retriever + */ +class Date +{ + /** + * @var TimezoneInterface + */ + private $localeDate; + + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * Date constructor. + * @param CollectionFactory $collectionFactory + * @param TimezoneInterface $localeDate + */ + public function __construct( + CollectionFactory $collectionFactory, + TimezoneInterface $localeDate + ) { + $this->localeDate = $localeDate; + $this->collectionFactory = $collectionFactory; + } + + /** + * Get chart dates data by period + * + * @param string $period + * + * @return array + */ + public function getByPeriod($period): array + { + [$dateStart, $dateEnd] = $this->collectionFactory->create()->getDateRange( + $period, + '', + '', + true + ); + + $timezoneLocal = $this->localeDate->getConfigTimezone(); + + $dateStart->setTimezone(new DateTimeZone($timezoneLocal)); + $dateEnd->setTimezone(new DateTimeZone($timezoneLocal)); + + if ($period === '24h') { + $dateEnd->modify('-1 hour'); + } else { + $dateEnd->setTime(23, 59, 59); + $dateStart->setTime(0, 0, 0); + } + + $dates = []; + + while ($dateStart <= $dateEnd) { + switch ($period) { + case '7d': + case '1m': + $d = $dateStart->format('Y-m-d'); + $dateStart->modify('+1 day'); + break; + case '1y': + case '2y': + $d = $dateStart->format('Y-m'); + $dateStart->modify('first day of next month'); + break; + default: + $d = $dateStart->format('Y-m-d H:00'); + $dateStart->modify('+1 hour'); + } + + $dates[] = $d; + } + + return $dates; + } +} diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertOrderGraphImageOnDashboardActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertOrderGraphImageOnDashboardActionGroup.xml index 3e3b0bc6a8a43..4be01fda862f8 100644 --- a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertOrderGraphImageOnDashboardActionGroup.xml +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertOrderGraphImageOnDashboardActionGroup.xml @@ -10,6 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AssertOrderGraphImageOnDashboardActionGroup"> <click selector="{{AdminDashboardSection.ordersTab}}" stepKey="clickOrdersBtn"/> - <seeElement selector="{{AdminDashboardSection.ordersChart}}" stepKey="seeGraphImage"/> + <seeElement selector="{{AdminDashboardSection.ordersChart}}" stepKey="seeOrdersChart"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml index 61fe7ffa48e23..e67025cfa68d5 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminDashboardSection.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminDashboardSection"> <element name="ordersTab" type="button" selector="#diagram_tab_orders"/> - <element name="ordersChart" type="button" selector="#diagram_tab_orders_content .dashboard-diagram-image img"/> + <element name="ordersChart" type="button" selector="#diagram_tab_orders_content #chart_orders_period"/> <element name="dashboardDiagramContent" type="button" selector="#diagram_tab_content"/> <element name="dashboardDiagramOrderContentTab" type="block" selector="#diagram_tab_orders_content"/> <element name="dashboardDiagramAmounts" type="button" selector="#diagram_tab_amounts"/> diff --git a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/TunnelTest.php b/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/TunnelTest.php deleted file mode 100644 index b7713078863cb..0000000000000 --- a/app/code/Magento/Backend/Test/Unit/Controller/Adminhtml/Dashboard/TunnelTest.php +++ /dev/null @@ -1,196 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Backend\Test\Unit\Controller\Adminhtml\Dashboard; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class TunnelTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_request; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_response; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_objectManager; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRaw; - - protected function setUp() - { - $this->_request = $this->createMock(\Magento\Framework\App\Request\Http::class); - $this->_response = $this->createMock(\Magento\Framework\App\Response\Http::class); - $this->_objectManager = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); - } - - protected function tearDown() - { - $this->_request = null; - $this->_response = null; - $this->_objectManager = null; - } - - public function testTunnelAction() - { - $fixture = uniqid(); - $this->_request->expects($this->at(0)) - ->method('getParam') - ->with('ga') - ->will($this->returnValue(urlencode(base64_encode(json_encode([1]))))); - $this->_request->expects($this->at(1))->method('getParam')->with('h')->will($this->returnValue($fixture)); - $tunnelResponse = $this->createMock(\Magento\Framework\App\Response\Http::class); - $httpClient = $this->createPartialMock( - \Magento\Framework\HTTP\ZendClient::class, - ['setUri', 'setParameterGet', 'setConfig', 'request', 'getHeaders'] - ); - /** @var $helper \Magento\Backend\Helper\Dashboard\Data|\PHPUnit_Framework_MockObject_MockObject */ - $helper = $this->createPartialMock(\Magento\Backend\Helper\Dashboard\Data::class, ['getChartDataHash']); - $helper->expects($this->any())->method('getChartDataHash')->will($this->returnValue($fixture)); - - $this->_objectManager->expects($this->at(0)) - ->method('get') - ->with(\Magento\Backend\Helper\Dashboard\Data::class) - ->will($this->returnValue($helper)); - $this->_objectManager->expects($this->at(1)) - ->method('create') - ->with(\Magento\Framework\HTTP\ZendClient::class) - ->will($this->returnValue($httpClient)); - $httpClient->expects($this->once())->method('setUri')->will($this->returnValue($httpClient)); - $httpClient->expects($this->once())->method('setParameterGet')->will($this->returnValue($httpClient)); - $httpClient->expects($this->once())->method('setConfig')->will($this->returnValue($httpClient)); - $httpClient->expects($this->once())->method('request')->with('GET')->will($this->returnValue($tunnelResponse)); - $tunnelResponse->expects($this->any())->method('getHeaders') - ->will($this->returnValue(['Content-type' => 'test_header'])); - $tunnelResponse->expects($this->any())->method('getBody')->will($this->returnValue('success_msg')); - $this->_response->expects($this->any())->method('getBody')->will($this->returnValue('success_msg')); - - $controller = $this->_factory($this->_request, $this->_response); - $this->resultRaw->expects($this->once()) - ->method('setHeader') - ->with('Content-type', 'test_header') - ->willReturnSelf(); - $this->resultRaw->expects($this->once()) - ->method('setContents') - ->with('success_msg') - ->willReturnSelf(); - - $controller->execute(); - $this->assertEquals('success_msg', $controller->getResponse()->getBody()); - } - - public function testTunnelAction400() - { - $controller = $this->_factory($this->_request, $this->_response); - - $this->resultRaw->expects($this->once()) - ->method('setHeader') - ->willReturnSelf(); - $this->resultRaw->expects($this->once()) - ->method('setHttpResponseCode') - ->with(400) - ->willReturnSelf(); - $this->resultRaw->expects($this->once()) - ->method('setContents') - ->with('Service unavailable: invalid request') - ->willReturnSelf(); - - $controller->execute(); - } - - public function testTunnelAction503() - { - $fixture = uniqid(); - $this->_request->expects($this->at(0)) - ->method('getParam') - ->with('ga') - ->will($this->returnValue(urlencode(base64_encode(json_encode([1]))))); - $this->_request->expects($this->at(1))->method('getParam')->with('h')->will($this->returnValue($fixture)); - /** @var $helper \Magento\Backend\Helper\Dashboard\Data|\PHPUnit_Framework_MockObject_MockObject */ - $helper = $this->createPartialMock(\Magento\Backend\Helper\Dashboard\Data::class, ['getChartDataHash']); - $helper->expects($this->any())->method('getChartDataHash')->will($this->returnValue($fixture)); - - $this->_objectManager->expects($this->at(0)) - ->method('get') - ->with(\Magento\Backend\Helper\Dashboard\Data::class) - ->will($this->returnValue($helper)); - $exceptionMock = new \Exception(); - $this->_objectManager->expects($this->at(1)) - ->method('create') - ->with(\Magento\Framework\HTTP\ZendClient::class) - ->will($this->throwException($exceptionMock)); - $loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); - $loggerMock->expects($this->once())->method('critical')->with($exceptionMock); - $this->_objectManager->expects($this->at(2)) - ->method('get') - ->with(\Psr\Log\LoggerInterface::class) - ->will($this->returnValue($loggerMock)); - - $controller = $this->_factory($this->_request, $this->_response); - - $this->resultRaw->expects($this->once()) - ->method('setHeader') - ->willReturnSelf(); - $this->resultRaw->expects($this->once()) - ->method('setHttpResponseCode') - ->with(503) - ->willReturnSelf(); - $this->resultRaw->expects($this->once()) - ->method('setContents') - ->with('Service unavailable: see error log for details') - ->willReturnSelf(); - - $controller->execute(); - } - - /** - * Create the tested object - * - * @param \Magento\Framework\App\Request\Http $request - * @param \Magento\Framework\App\Response\Http|null $response - * @return \Magento\Backend\Controller\Adminhtml\Dashboard|\PHPUnit_Framework_MockObject_MockObject - */ - protected function _factory($request, $response = null) - { - if (!$response) { - /** @var $response \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject */ - $response = $this->createMock(\Magento\Framework\App\Response\Http::class); - $response->headersSentThrowsException = false; - } - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $varienFront = $helper->getObject(\Magento\Framework\App\FrontController::class); - - $arguments = [ - 'request' => $request, - 'response' => $response, - 'objectManager' => $this->_objectManager, - 'frontController' => $varienFront, - ]; - $this->resultRaw = $this->getMockBuilder(\Magento\Framework\Controller\Result\Raw::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultRawFactory = $this->getMockBuilder(\Magento\Framework\Controller\Result\RawFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $resultRawFactory->expects($this->atLeastOnce()) - ->method('create') - ->willReturn($this->resultRaw); - $context = $helper->getObject(\Magento\Backend\App\Action\Context::class, $arguments); - return new \Magento\Backend\Controller\Adminhtml\Dashboard\Tunnel($context, $resultRawFactory); - } -} diff --git a/app/code/Magento/Backend/Test/Unit/Model/Dashboard/ChartTest.php b/app/code/Magento/Backend/Test/Unit/Model/Dashboard/ChartTest.php new file mode 100644 index 0000000000000..5aedb953dab37 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Model/Dashboard/ChartTest.php @@ -0,0 +1,226 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Backend\Test\Unit\Model\Dashboard; + +use Magento\Backend\Helper\Dashboard\Data as DataHelper; +use Magento\Backend\Helper\Dashboard\Order as OrderHelper; +use Magento\Backend\Model\Dashboard\Chart; +use Magento\Backend\Model\Dashboard\Chart\Date as DateRetriever; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\DataObject; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Reports\Model\ResourceModel\Order\Collection; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ChartTest extends TestCase +{ + /** + * @var Chart + */ + private $model; + + /** + * @var ObjectManager + */ + private $objectManagerHelper; + + /** + * @var RequestInterface|MockObject + */ + private $requestMock; + + /** + * @var DateRetriever|MockObject + */ + private $dateRetrieverMock; + + /** + * @var DataHelper|MockObject + */ + private $dataHelperMock; + + /** + * @var OrderHelper|MockObject + */ + private $orderHelperMock; + + /** + * @var Collection|MockObject + */ + private $collectionMock; + + protected function setUp() + { + $this->objectManagerHelper = new ObjectManager($this); + + $this->requestMock = $this->getMockForAbstractClass(RequestInterface::class); + $this->requestMock->method('getParam')->willReturn(null); + + $this->dateRetrieverMock = $this->getMockBuilder(DateRetriever::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->dataHelperMock = $this->getMockBuilder(DataHelper::class) + ->disableOriginalConstructor() + ->getMock(); + $this->dataHelperMock->method('getDatePeriods') + ->willReturn([ + '24h' => __('Last 24 Hours'), + '7d' => __('Last 7 Days'), + '1m' => __('Current Month'), + '1y' => __('YTD'), + '2y' => __('2YTD') + ]); + + $this->orderHelperMock = $this->getMockBuilder(OrderHelper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderHelperMock->method('getCollection') + ->willReturn($this->collectionMock); + + $this->model = $this->objectManagerHelper->getObject( + Chart::class, + [ + 'request' => $this->requestMock, + 'dateRetriever' => $this->dateRetrieverMock, + 'dataHelper' => $this->dataHelperMock, + 'orderHelper' => $this->orderHelperMock + ] + ); + } + + /** + * @param string $period + * @param string $chartParam + * @param array $result + * @dataProvider getByPeriodDataProvider + */ + public function testGetByPeriod($period, $chartParam, $result) + { + $this->orderHelperMock->expects($this->at(0)) + ->method('setParam') + ->with('store', null); + $this->orderHelperMock->expects($this->at(1)) + ->method('setParam') + ->with('website', null); + $this->orderHelperMock->expects($this->at(2)) + ->method('setParam') + ->with('group', null); + $this->orderHelperMock->expects($this->at(3)) + ->method('setParam') + ->with('period', $period); + + $this->dateRetrieverMock->expects($this->once()) + ->method('getByPeriod') + ->with($period) + ->willReturn(array_map(static function ($item) { + return $item['x']; + }, $result)); + + $this->collectionMock->method('count') + ->willReturn(2); + + $valueMap = []; + foreach ($result as $resultItem) { + $dataObjectMock = $this->getMockBuilder(DataObject::class) + ->disableOriginalConstructor() + ->getMock(); + $dataObjectMock->method('getData') + ->with($chartParam) + ->willReturn($resultItem['y']); + + $valueMap[] = [ + 'range', + $resultItem['x'], + $dataObjectMock + ]; + } + $this->collectionMock->method('getItemByColumnValue') + ->willReturnMap($valueMap); + + $this->assertEquals( + $result, + $this->model->getByPeriod($period, $chartParam) + ); + } + + public function getByPeriodDataProvider(): array + { + return [ + [ + '7d', + 'revenue', + [ + [ + 'x' => '2020-01-21', + 'y' => 0 + ], + [ + 'x' => '2020-01-22', + 'y' => 2 + ], + [ + 'x' => '2020-01-23', + 'y' => 0 + ], + [ + 'x' => '2020-01-24', + 'y' => 7 + ] + ] + ], + [ + '1m', + 'quantity', + [ + [ + 'x' => '2020-01-21', + 'y' => 0 + ], + [ + 'x' => '2020-01-22', + 'y' => 2 + ], + [ + 'x' => '2020-01-23', + 'y' => 0 + ], + [ + 'x' => '2020-01-24', + 'y' => 7 + ] + ] + ], + [ + '1y', + 'quantity', + [ + [ + 'x' => '2020-01', + 'y' => 0 + ], + [ + 'x' => '2020-02', + 'y' => 2 + ], + [ + 'x' => '2020-03', + 'y' => 0 + ], + [ + 'x' => '2020-04', + 'y' => 7 + ] + ] + ] + ]; + } +} diff --git a/app/code/Magento/Backend/ViewModel/ChartDisabled.php b/app/code/Magento/Backend/ViewModel/ChartDisabled.php new file mode 100644 index 0000000000000..f40e757ee7b64 --- /dev/null +++ b/app/code/Magento/Backend/ViewModel/ChartDisabled.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\ViewModel; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * View model for dashboard chart disabled notice + */ +class ChartDisabled implements ArgumentInterface +{ + /** + * Location of the "Enable Chart" config param + */ + private const XML_PATH_ENABLE_CHARTS = 'admin/dashboard/enable_charts'; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param UrlInterface $urlBuilder + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + UrlInterface $urlBuilder, + ScopeConfigInterface $scopeConfig + ) { + $this->urlBuilder = $urlBuilder; + $this->scopeConfig = $scopeConfig; + } + + /** + * Get url to dashboard chart configuration + * + * @return string + */ + public function getConfigUrl(): string + { + return $this->urlBuilder->getUrl( + 'adminhtml/system_config/edit', + ['section' => 'admin', '_fragment' => 'admin_dashboard-link'] + ); + } + + /** + * Check if dashboard chart is enabled + * + * @return bool + */ + public function isChartEnabled(): bool + { + return $this->scopeConfig->isSetFlag( + static::XML_PATH_ENABLE_CHARTS, + ScopeInterface::SCOPE_STORE + ); + } +} diff --git a/app/code/Magento/Backend/ViewModel/ChartsPeriod.php b/app/code/Magento/Backend/ViewModel/ChartsPeriod.php new file mode 100644 index 0000000000000..c118cb7c02a5f --- /dev/null +++ b/app/code/Magento/Backend/ViewModel/ChartsPeriod.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\ViewModel; + +use Magento\Backend\Helper\Dashboard\Data; +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * View model for dashboard charts period select + */ +class ChartsPeriod implements ArgumentInterface +{ + /** + * @var Data + */ + private $dataHelper; + + /** + * @param Data $dataHelper + */ + public function __construct(Data $dataHelper) + { + $this->dataHelper = $dataHelper; + } + + /** + * Get chart date periods + * + * @return array + */ + public function getDatePeriods(): array + { + return $this->dataHelper->getDatePeriods(); + } +} diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml index 803eb1dbe903b..86909162abb7c 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml @@ -17,7 +17,65 @@ <block class="Magento\Backend\Block\Template" name="refresh_statistics" after="store_switcher" template="Magento_Backend::dashboard/totalbar/refreshstatistics.phtml"/> </referenceContainer> <referenceContainer name="content"> - <block class="Magento\Backend\Block\Dashboard" name="dashboard"/> + <block name="dashboard" template="Magento_Backend::dashboard/index.phtml"> + <block class="Magento\Backend\Block\Dashboard\Orders\Grid" name="dashboard.lastOrders" as="lastOrders"/> + <block class="Magento\Backend\Block\Dashboard\Totals" name="dashboard.totals" as="totals"> + <block template="Magento_Backend::dashboard/totalbar/script.phtml" name="dashboard.totals.script"/> + </block> + <block class="Magento\Backend\Block\Dashboard\Sales" name="dashboard.sales" as="sales"/> + <block class="Magento\Backend\Block\Dashboard\Grids" name="dashboard.grids" as="grids"/> + <block name="dashboard.chart.disabled" + as="chartDisabled" + template="Magento_Backend::dashboard/chart/disabled.phtml"> + <arguments> + <argument name="view_model" xsi:type="object">Magento\Backend\ViewModel\ChartDisabled</argument> + </arguments> + </block> + <block class="Magento\Backend\Block\Widget\Tabs" + name="dashboard.diagrams" + as="diagrams" + template="Magento_Backend::widget/tabshoriz.phtml" + ifconfig="admin/dashboard/enable_charts"> + <action method="setId"> + <argument name="id" xsi:type="string">diagram_tab</argument> + </action> + <action method="setDestElementId"> + <argument name="dest_element_id" xsi:type="string">diagram_tab_content</argument> + </action> + <action method="addTab"> + <argument name="name" xsi:type="string">orders</argument> + <argument name="block" xsi:type="string">orders</argument> + </action> + <action method="addTab"> + <argument name="name" xsi:type="string">amounts</argument> + <argument name="block" xsi:type="string">amounts</argument> + </action> + <block class="Magento\Backend\Block\Widget\Tab" name="dashboard.chart.orders" as="orders" template="Magento_Backend::dashboard/chart.phtml"> + <arguments> + <argument name="html_id" xsi:type="string">orders</argument> + <argument name="label" xsi:type="string" translate="true">Orders</argument> + <argument name="title" xsi:type="string" translate="true">Orders</argument> + <argument name="update_url" xsi:type="string">adminhtml/dashboard_chart/orders</argument> + </arguments> + </block> + <block class="Magento\Backend\Block\Widget\Tab" name="dashboard.chart.amounts" as="amounts" template="Magento_Backend::dashboard/chart.phtml"> + <arguments> + <argument name="html_id" xsi:type="string">amounts</argument> + <argument name="label" xsi:type="string" translate="true">Amounts</argument> + <argument name="title" xsi:type="string" translate="true">Amounts</argument> + <argument name="update_url" xsi:type="string">adminhtml/dashboard_chart/amounts</argument> + </arguments> + </block> + </block> + <block name="dashboard.diagrams.period" + as="diagramsPeriod" + template="Magento_Backend::dashboard/chart/period.phtml" + ifconfig="admin/dashboard/enable_charts"> + <arguments> + <argument name="view_model" xsi:type="object">Magento\Backend\ViewModel\ChartsPeriod</argument> + </arguments> + </block> + </block> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart.phtml new file mode 100644 index 0000000000000..f289246157c47 --- /dev/null +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart.phtml @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Framework\Escaper; +use Magento\Framework\View\Element\Template; + +/** + * @var Template $block + * @var Escaper $escaper + */ +?> +<div class="dashboard-diagram"> + <div class="dashboard-diagram-graph"> + <canvas id="chart_<?= $escaper->escapeHtmlAttr($block->getData('html_id')) ?>_period" + style="display: none;"></canvas> + <div class="dashboard-diagram-nodata"> + <span><?= $escaper->escapeHtml(__('No Data Found')) ?></span> + </div> + </div> + <script type="text/x-magento-init"> + { + "#chart_<?= $escaper->escapeJs($block->getData('html_id')) ?>_period": { + "Magento_Backend/js/dashboard/chart": { + "updateUrl": "<?= $escaper->escapeUrl($block->getUrl($block->getData('update_url'), [ + '_current' => true + ])) ?>", + "periodSelect": "#dashboard_chart_period", + "type": "<?= $escaper->escapeJs($block->getData('html_id')) ?>" + } + } + } + </script> +</div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart/disabled.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart/disabled.phtml new file mode 100644 index 0000000000000..ac600c67e662f --- /dev/null +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart/disabled.phtml @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Backend\ViewModel\ChartDisabled; +use Magento\Framework\Escaper; +use Magento\Framework\View\Element\Template; + +/** + * @var Template $block + * @var Escaper $escaper + * @var ChartDisabled $viewModel + * @phpcs:ignoreFile + */ +$viewModel = $block->getViewModel(); +?> +<?php if (!$viewModel->isChartEnabled()): ?> + <div class="dashboard-diagram-disabled"> + <?= $escaper->escapeHtml(__('Chart is disabled. To enable the chart, click <a href="%1">here</a>.', $escaper->escapeUrl($viewModel->getConfigUrl())), ['a']) ?> + </div> +<?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart/period.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart/period.phtml new file mode 100644 index 0000000000000..f30bfc7a57fb9 --- /dev/null +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart/period.phtml @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Backend\ViewModel\ChartsPeriod; +use Magento\Framework\Escaper; +use Magento\Framework\View\Element\Template; + +/** + * @var Template $block + * @var Escaper $escaper + * @var ChartsPeriod $viewModel + */ +$viewModel = $block->getViewModel(); +?> +<div class="dashboard-diagram-switcher"> + <label for="dashboard_chart_period" class="label"><?= $escaper->escapeHtml(__('Select Range:')) ?></label> + <select name="period" id="dashboard_chart_period" class="admin__control-select"> + <?php foreach ($viewModel->getDatePeriods() as $value => $label): ?> + <?php + if ($value === 'custom') { + continue; + } + ?> + <option value="<?= $escaper->escapeHtmlAttr($value) ?>" + <?php if ($block->getRequest()->getParam('period') === $value): ?> selected="selected"<?php endif; ?> + ><?= $escaper->escapeHtml($label) ?></option> + <?php endforeach; ?> + </select> +</div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph.phtml index 12b388c210774..21fd3f45a2965 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph.phtml @@ -3,6 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +/** + * @deprecated dashboard graphs were migrated to dynamic chart.js solution + * @see Magento_Backend::dashboard/chart.phtml + * @phpcs:ignoreFile + */ ?> <div class="dashboard-diagram"> <div class="dashboard-diagram-switcher"> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph/disabled.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph/disabled.phtml index f8e584ce5b9cd..513a76cde29c0 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph/disabled.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/graph/disabled.phtml @@ -3,6 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +/** + * @deprecated dashboard graphs were migrated to dynamic chart.js solution + * @see Magento_Backend::dashboard/chart/disabled.phtml + * @phpcs:ignoreFile + */ ?> <div class="dashboard-diagram-disabled"> <?= /* @noEscape */ __('Chart is disabled. To enable the chart, click <a href="%1">here</a>.', $block->escapeUrl($block->getConfigUrl())) ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/index.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/index.phtml index 6152c8fe1cff1..40df857968070 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/index.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/index.phtml @@ -5,78 +5,17 @@ */ ?> -<?php if (is_array($block->getChildBlock('diagrams')->getTabsIds())) : ?> -<script> -require([ - 'Magento_Ui/js/modal/alert', - 'prototype' -], function(alert){ - -window.changeDiagramsPeriod = function(periodObj) { - periodParam = periodObj.value ? 'period/' + periodObj.value + '/' : ''; - <?php foreach ($block->getChildBlock('diagrams')->getTabsIds() as $tabId) : ?> - ajaxBlockParam = 'block/tab_<?= $block->escapeJs($tabId) ?>/'; - ajaxBlockUrl = '<?= $block->escapeJs($block->getUrl('adminhtml/*/ajaxBlock', ['_current' => true, 'block' => '', 'period' => ''])) ?>' + ajaxBlockParam + periodParam; - new Ajax.Request(ajaxBlockUrl, { - parameters: {isAjax: 'true', form_key: FORM_KEY}, - onSuccess: function(transport) { - tabContentElementId = '<?= $block->escapeJs($block->getChildBlock('diagrams')->getId()) ?>_<?= $block->escapeJs($tabId) ?>_content'; - try { - if (transport.responseText.isJSON()) { - var response = transport.responseText.evalJSON() - if (response.error) { - alert({ - content: response.message - }); - } - if(response.ajaxExpired && response.ajaxRedirect) { - setLocation(response.ajaxRedirect); - } - } else { - $(tabContentElementId).update(transport.responseText); - } - } - catch (e) { - $(tabContentElementId).update(transport.responseText); - } - } - }); - <?php endforeach; ?> - ajaxBlockUrl = '<?= $block->escapeJs($block->getUrl('adminhtml/*/ajaxBlock', ['_current' => true, 'block' => 'totals', 'period' => ''])) ?>' + periodParam; - new Ajax.Request(ajaxBlockUrl, { - parameters: {isAjax: 'true', form_key: FORM_KEY}, - onSuccess: function(transport) { - tabContentElementId = 'dashboard_diagram_totals'; - try { - if (transport.responseText.isJSON()) { - var response = transport.responseText.evalJSON(); - if (response.error) { - alert({ - content: response.message - }); - } - if(response.ajaxExpired && response.ajaxRedirect) { - setLocation(response.ajaxRedirect); - } - } else { - $(tabContentElementId).replace(transport.responseText); - } - } - catch (e) { - $(tabContentElementId).replace(transport.responseText); - } - } - }); -} - -}); -</script> -<?php endif; ?> <div class="dashboard-container row"> <div class="dashboard-main col-m-8 col-m-push-4"> <div class="dashboard-diagram-container"> + <?= $block->getChildHtml('chartDisabled') ?> <?= $block->getChildHtml('diagrams') ?> - <?php if (is_array($block->getChildBlock('diagrams')->getTabsIds())) : ?> + <?php + $diagramsBlock = $block->getChildBlock('diagrams'); + $tabsIds = $diagramsBlock ? $diagramsBlock->getTabsIds() : []; + ?> + <?php if (is_array($tabsIds)): ?> + <?= $block->getChildHtml('diagramsPeriod') ?> <div id="diagram_tab_content" class="dashboard-diagram-tab-content"></div> <?php endif; ?> </div> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar.phtml index 918eea75fab99..44320e247f9d3 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar.phtml @@ -3,19 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +use Magento\Backend\Block\Dashboard\Totals; +use Magento\Framework\Escaper; + +/** + * @var Totals $block + * @var Escaper $escaper + */ ?> -<?php if (count($block->getTotals()) > 0) : ?> -<div class="dashboard-totals" id="dashboard_diagram_totals"> - <ul class="dashboard-totals-list"> - <?php foreach ($block->getTotals() as $_total) : ?> - <li class="dashboard-totals-item"> - <span class="dashboard-totals-label"><?= $block->escapeHtml($_total['label']) ?></span> - <strong class="dashboard-totals-value"> - <?= /* @noEscape */ $_total['value'] ?> - <span class="dashboard-totals-decimals"><?= /* @noEscape */ $_total['decimals'] ?></span> - </strong> - </li> - <?php endforeach; ?> - </ul> -</div> +<?php if (count($block->getTotals()) > 0): ?> + <div class="dashboard-totals" id="dashboard_diagram_totals"> + <ul class="dashboard-totals-list"> + <?php foreach ($block->getTotals() as $total): ?> + <li class="dashboard-totals-item"> + <span class="dashboard-totals-label"><?= $escaper->escapeHtml($total['label']) ?></span> + <strong class="dashboard-totals-value"> + <?= /* @noEscape */ $total['value'] ?> + <span class="dashboard-totals-decimals"><?= /* @noEscape */ $total['decimals'] ?></span> + </strong> + </li> + <?php endforeach; ?> + </ul> + </div> + <?= $block->getChildHtml() ?> <?php endif; ?> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar/script.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar/script.phtml new file mode 100644 index 0000000000000..7e8f12d19b7fd --- /dev/null +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/totalbar/script.phtml @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Framework\Escaper; +use Magento\Framework\View\Element\Template; + +/** + * @var Template $block + * @var Escaper $escaper + */ +?> +<script type="text/x-magento-init"> +{ + "#dashboard_diagram_totals": { + "Magento_Backend/js/dashboard/totals": { + "updateUrl": "<?= $escaper->escapeUrl($block->getUrl('adminhtml/*/ajaxBlock', [ + '_current' => true, + 'block' => 'totals', + 'period' => '' + ])) ?>", + "periodSelect": "#dashboard_chart_period" + } + } +} +</script> diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js b/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js new file mode 100644 index 0000000000000..c618e5db5c84a --- /dev/null +++ b/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js @@ -0,0 +1,157 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/*global define*/ +/*global FORM_KEY*/ +define([ + 'jquery', + 'chartJs', + 'jquery-ui-modules/widget', + 'moment' +], function ($, Chart) { + 'use strict'; + + $.widget('mage.dashboardChart', { + options: { + updateUrl: '', + periodSelect: null, + type: '' + }, + chart: null, + + /** + * @private + */ + _create: function () { + this.createChart(); + + if (this.options.periodSelect) { + $(document).on('change', this.options.periodSelect, this.refreshChartData.bind(this)); + + this.period = $(this.options.periodSelect).val(); + } + }, + + /** + * @public + */ + createChart: function () { + this.chart = new Chart(this.element, this.getChartSettings()); + this.refreshChartData(); + }, + + /** + * @public + */ + refreshChartData: function () { + var data = { + 'form_key': FORM_KEY + }; + + if (this.options.periodSelect) { + this.period = data.period = $(this.options.periodSelect).val(); + } + + $.ajax({ + url: this.options.updateUrl, + showLoader: true, + data: data, + dataType: 'json', + type: 'POST', + success: this.updateChart.bind(this) + }); + }, + + /** + * @public + * @param {Object} response + */ + updateChart: function (response) { + $(this.element).toggle(response.data.length > 0); + $(this.element).next('.dashboard-diagram-nodata').toggle(response.data.length === 0); + + this.chart.options.scales.xAxes[0].time.unit = this.getPeriodUnit(); + this.chart.data.datasets[0].data = response.data; + this.chart.data.datasets[0].label = response.label; + this.chart.update(); + }, + + /** + * @returns {String} time unit per currently set period + */ + getPeriodUnit: function () { + switch (this.period) { + case '7d': + case '1m': + return 'day'; + + case '1y': + case '2y': + return 'month'; + } + + return 'hour'; + }, + + /** + * @returns {Number} precision of numeric chart data + */ + getPrecision: function () { + if (this.options.type === 'amounts') { + return 2; + } + + return 0; + }, + + /** + * @returns {Object} chart object configuration + */ + getChartSettings: function () { + return { + type: 'bar', + data: { + datasets: [{ + data: [], + backgroundColor: '#f1d4b3', + borderColor: '#eb5202', + borderWidth: 1 + }] + }, + options: { + legend: { + onClick: this.handleChartLegendClick, + position: 'bottom' + }, + scales: { + xAxes: [{ + offset: true, + type: 'time', + ticks: { + autoSkip: true, + source: 'data' + } + }], + yAxes: [{ + ticks: { + beginAtZero: true, + precision: this.getPrecision() + } + }] + } + } + }; + }, + + /** + * @public + */ + handleChartLegendClick: function () { + // don't hide dataset on clicking into legend item + } + }); + + return $.mage.dashboardChart; +}); diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/totals.js b/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/totals.js new file mode 100644 index 0000000000000..18953140e5b62 --- /dev/null +++ b/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/totals.js @@ -0,0 +1,60 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/*global define*/ +/*global FORM_KEY*/ +define([ + 'jquery', + 'jquery-ui-modules/widget' +], function ($) { + 'use strict'; + + $.widget('mage.graph', { + options: { + updateUrl: '', + periodSelect: null + }, + elementId: null, + + /** + * @private + */ + _create: function () { + this.elementId = $(this.element).attr('id'); + + if (this.options.periodSelect) { + $(document).on('change', this.options.periodSelect, $.proxy(function () { + this.refreshTotals(); + }, this)); + } + }, + + /** + * @public + */ + refreshTotals: function () { + var periodParam = ''; + + if (this.options.periodSelect && $(this.options.periodSelect).val()) { + periodParam = 'period/' + $(this.options.periodSelect).val() + '/'; + } + + $.ajax({ + url: this.options.updateUrl + periodParam, + showLoader: true, + data: { + 'form_key': FORM_KEY + }, + dataType: 'html', + type: 'POST', + success: $.proxy(function (response) { + $('#' + this.elementId).replaceWith(response); + }, this) + }); + } + }); + + return $.mage.graph; +}); diff --git a/app/code/Magento/Ui/view/base/requirejs-config.js b/app/code/Magento/Ui/view/base/requirejs-config.js index cdbbd03c93ae1..6685a51d6561a 100644 --- a/app/code/Magento/Ui/view/base/requirejs-config.js +++ b/app/code/Magento/Ui/view/base/requirejs-config.js @@ -5,6 +5,7 @@ var config = { shim: { + 'chartjs/Chart': ['moment'], 'tiny_mce_4/tinymce.min': { exports: 'tinyMCE' } @@ -23,6 +24,7 @@ var config = { consoleLogger: 'Magento_Ui/js/lib/logger/console-logger', uiLayout: 'Magento_Ui/js/core/renderer/layout', buttonAdapter: 'Magento_Ui/js/form/button-adapter', + chartJs: 'chartjs/Chart', tinymce4: 'tiny_mce_4/tinymce.min', wysiwygAdapter: 'mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter' } diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_dashboard.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_dashboard.less index 7ef304932a649..3c50fc02a05c5 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_dashboard.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/pages/_dashboard.less @@ -126,16 +126,19 @@ // Range switcher .dashboard-diagram-switcher { - margin-bottom: 2rem; + border-top: 1px solid @color-gray68; + margin-top: -1px; + padding: 2rem 2rem 0; .label { &:extend(.abs-visually-hidden all); } -} -// Chart -.dashboard-diagram-image { - max-width: 100%; + + .dashboard-diagram-tab-content { + > .ui-tabs-panel { + border-top: 0 none; + } + } } // diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/GraphTest.php b/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/GraphTest.php deleted file mode 100644 index 497deb2c99110..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/GraphTest.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Backend\Block\Dashboard; - -/** - * @magentoAppArea adminhtml - */ -class GraphTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Backend\Block\Dashboard\Graph - */ - protected $_block; - - protected function setUp() - { - parent::setUp(); - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var \Magento\Framework\View\LayoutInterface $layout */ - $layout = $objectManager->get(\Magento\Framework\View\LayoutInterface::class); - $this->_block = $layout->createBlock(\Magento\Backend\Block\Dashboard\Graph::class); - $this->_block->setDataHelper($objectManager->get(\Magento\Backend\Helper\Dashboard\Order::class)); - } - - public function testGetChartUrl() - { - $this->assertStringStartsWith('https://image-charts.com/chart', $this->_block->getChartUrl()); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Dashboard/AjaxBlockTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Dashboard/AjaxBlockTest.php index 3deb225cead18..a21dff3dd854f 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Dashboard/AjaxBlockTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/Dashboard/AjaxBlockTest.php @@ -1,10 +1,8 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Backend\Controller\Adminhtml\Dashboard; @@ -20,6 +18,9 @@ class AjaxBlockTest extends AbstractBackendController /** * Test execute to check render block * + * @param string $block + * @param string $expectedResult + * * @dataProvider ajaxBlockDataProvider */ public function testExecute($block, $expectedResult) @@ -41,17 +42,9 @@ public function testExecute($block, $expectedResult) * * @return array */ - public function ajaxBlockDataProvider() + public function ajaxBlockDataProvider(): array { return [ - [ - 'tab_orders', - 'order_orders_period' - ], - [ - 'tab_amounts', - 'order_amounts_period' - ], [ 'totals', 'dashboard_diagram_totals' diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php deleted file mode 100644 index 0eb98379b4571..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Backend\Controller\Adminhtml; - -/** - * @magentoAppArea adminhtml - */ -class DashboardTest extends \Magento\TestFramework\TestCase\AbstractBackendController -{ - public function testAjaxBlockAction() - { - $this->getRequest()->setParam('block', 'tab_orders'); - $this->dispatch('backend/admin/dashboard/ajaxBlock'); - - $actual = $this->getResponse()->getBody(); - $this->assertContains('dashboard-diagram', $actual); - } - - /** - * Tests tunnelAction - * - * @throws \Exception - * @return void - */ - public function testTunnelAction() - { - // phpcs:disable Magento2.Functions.DiscouragedFunction - $testUrl = \Magento\Backend\Block\Dashboard\Graph::API_URL . '?cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World'; - $handle = curl_init(); - curl_setopt($handle, CURLOPT_URL, $testUrl); - curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); - try { - if (false === curl_exec($handle)) { - $this->markTestSkipped('Third-party service is unavailable: ' . $testUrl); - } - curl_close($handle); - } catch (\Exception $e) { - curl_close($handle); - throw $e; - } - // phpcs:enable - - $gaData = [ - 'cht' => 'lc', - 'chf' => 'bg,s,f4f4f4|c,lg,90,ffffff,0.1,ededed,0', - 'chm' => 'B,f4d4b2,0,0,0', - 'chco' => 'db4814', - 'chd' => 'e:AAAAAAAAf.AAAA', - 'chxt' => 'x,y', - 'chxl' => '0:|10/13/12|10/14/12|10/15/12|10/16/12|10/17/12|10/18/12|10/19/12|1:|0|1|2', - 'chs' => '587x300', - 'chg' => '16.666666666667,50,1,0', - ]; - $gaFixture = urlencode(base64_encode(json_encode($gaData))); - - /** @var $helper \Magento\Backend\Helper\Dashboard\Data */ - $helper = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Backend\Helper\Dashboard\Data::class - ); - $hash = $helper->getChartDataHash($gaFixture); - $this->getRequest()->setParam('ga', $gaFixture)->setParam('h', $hash); - $this->dispatch('backend/admin/dashboard/tunnel'); - $this->assertStringStartsWith("\x89\x50\x4E\x47", $this->getResponse()->getBody()); // PNG header - } -} diff --git a/lib/web/chartjs/Chart.bundle.js b/lib/web/chartjs/Chart.bundle.js new file mode 100644 index 0000000000000..b6f4f388c7459 --- /dev/null +++ b/lib/web/chartjs/Chart.bundle.js @@ -0,0 +1,20755 @@ +/*! + * Chart.js v2.9.3 + * https://www.chartjs.org + * (c) 2019 Chart.js Contributors + * Released under the MIT License + */ +(function (global, factory) { +typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : +typeof define === 'function' && define.amd ? define(factory) : +(global = global || self, global.Chart = factory()); +}(this, (function () { 'use strict'; + +var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +function commonjsRequire () { + throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs'); +} + +function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; +} + +function getCjsExportFromNamespace (n) { + return n && n['default'] || n; +} + +var colorName = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +var conversions = createCommonjsModule(function (module) { +/* MIT license */ + + +// NOTE: conversions should only return primitive values (i.e. arrays, or +// values that give correct `typeof` results). +// do not use box values types (i.e. Number(), String(), etc.) + +var reverseKeywords = {}; +for (var key in colorName) { + if (colorName.hasOwnProperty(key)) { + reverseKeywords[colorName[key]] = key; + } +} + +var convert = module.exports = { + rgb: {channels: 3, labels: 'rgb'}, + hsl: {channels: 3, labels: 'hsl'}, + hsv: {channels: 3, labels: 'hsv'}, + hwb: {channels: 3, labels: 'hwb'}, + cmyk: {channels: 4, labels: 'cmyk'}, + xyz: {channels: 3, labels: 'xyz'}, + lab: {channels: 3, labels: 'lab'}, + lch: {channels: 3, labels: 'lch'}, + hex: {channels: 1, labels: ['hex']}, + keyword: {channels: 1, labels: ['keyword']}, + ansi16: {channels: 1, labels: ['ansi16']}, + ansi256: {channels: 1, labels: ['ansi256']}, + hcg: {channels: 3, labels: ['h', 'c', 'g']}, + apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, + gray: {channels: 1, labels: ['gray']} +}; + +// hide .channels and .labels properties +for (var model in convert) { + if (convert.hasOwnProperty(model)) { + if (!('channels' in convert[model])) { + throw new Error('missing channels property: ' + model); + } + + if (!('labels' in convert[model])) { + throw new Error('missing channel labels property: ' + model); + } + + if (convert[model].labels.length !== convert[model].channels) { + throw new Error('channel and label counts mismatch: ' + model); + } + + var channels = convert[model].channels; + var labels = convert[model].labels; + delete convert[model].channels; + delete convert[model].labels; + Object.defineProperty(convert[model], 'channels', {value: channels}); + Object.defineProperty(convert[model], 'labels', {value: labels}); + } +} + +convert.rgb.hsl = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var min = Math.min(r, g, b); + var max = Math.max(r, g, b); + var delta = max - min; + var h; + var s; + var l; + + if (max === min) { + h = 0; + } else if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else if (b === max) { + h = 4 + (r - g) / delta; + } + + h = Math.min(h * 60, 360); + + if (h < 0) { + h += 360; + } + + l = (min + max) / 2; + + if (max === min) { + s = 0; + } else if (l <= 0.5) { + s = delta / (max + min); + } else { + s = delta / (2 - max - min); + } + + return [h, s * 100, l * 100]; +}; + +convert.rgb.hsv = function (rgb) { + var rdif; + var gdif; + var bdif; + var h; + var s; + + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var v = Math.max(r, g, b); + var diff = v - Math.min(r, g, b); + var diffc = function (c) { + return (v - c) / 6 / diff + 1 / 2; + }; + + if (diff === 0) { + h = s = 0; + } else { + s = diff / v; + rdif = diffc(r); + gdif = diffc(g); + bdif = diffc(b); + + if (r === v) { + h = bdif - gdif; + } else if (g === v) { + h = (1 / 3) + rdif - bdif; + } else if (b === v) { + h = (2 / 3) + gdif - rdif; + } + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + } + + return [ + h * 360, + s * 100, + v * 100 + ]; +}; + +convert.rgb.hwb = function (rgb) { + var r = rgb[0]; + var g = rgb[1]; + var b = rgb[2]; + var h = convert.rgb.hsl(rgb)[0]; + var w = 1 / 255 * Math.min(r, Math.min(g, b)); + + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; +}; + +convert.rgb.cmyk = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var c; + var m; + var y; + var k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + + return [c * 100, m * 100, y * 100, k * 100]; +}; + +/** + * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance + * */ +function comparativeDistance(x, y) { + return ( + Math.pow(x[0] - y[0], 2) + + Math.pow(x[1] - y[1], 2) + + Math.pow(x[2] - y[2], 2) + ); +} + +convert.rgb.keyword = function (rgb) { + var reversed = reverseKeywords[rgb]; + if (reversed) { + return reversed; + } + + var currentClosestDistance = Infinity; + var currentClosestKeyword; + + for (var keyword in colorName) { + if (colorName.hasOwnProperty(keyword)) { + var value = colorName[keyword]; + + // Compute comparative distance + var distance = comparativeDistance(rgb, value); + + // Check if its less, if so set as closest + if (distance < currentClosestDistance) { + currentClosestDistance = distance; + currentClosestKeyword = keyword; + } + } + } + + return currentClosestKeyword; +}; + +convert.keyword.rgb = function (keyword) { + return colorName[keyword]; +}; + +convert.rgb.xyz = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y * 100, z * 100]; +}; + +convert.rgb.lab = function (rgb) { + var xyz = convert.rgb.xyz(rgb); + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.hsl.rgb = function (hsl) { + var h = hsl[0] / 360; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var t1; + var t2; + var t3; + var rgb; + var val; + + if (s === 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * -(i - 1); + if (t3 < 0) { + t3++; + } + if (t3 > 1) { + t3--; + } + + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + + rgb[i] = val * 255; + } + + return rgb; +}; + +convert.hsl.hsv = function (hsl) { + var h = hsl[0]; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var smin = s; + var lmin = Math.max(l, 0.01); + var sv; + var v; + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + v = (l + s) / 2; + sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); + + return [h, sv * 100, v * 100]; +}; + +convert.hsv.rgb = function (hsv) { + var h = hsv[0] / 60; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var hi = Math.floor(h) % 6; + + var f = h - Math.floor(h); + var p = 255 * v * (1 - s); + var q = 255 * v * (1 - (s * f)); + var t = 255 * v * (1 - (s * (1 - f))); + v *= 255; + + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +}; + +convert.hsv.hsl = function (hsv) { + var h = hsv[0]; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var vmin = Math.max(v, 0.01); + var lmin; + var sl; + var l; + + l = (2 - s) * v; + lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= (lmin <= 1) ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; + + return [h, sl * 100, l * 100]; +}; + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +convert.hwb.rgb = function (hwb) { + var h = hwb[0] / 360; + var wh = hwb[1] / 100; + var bl = hwb[2] / 100; + var ratio = wh + bl; + var i; + var v; + var f; + var n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + + if ((i & 0x01) !== 0) { + f = 1 - f; + } + + n = wh + f * (v - wh); // linear interpolation + + var r; + var g; + var b; + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +}; + +convert.cmyk.rgb = function (cmyk) { + var c = cmyk[0] / 100; + var m = cmyk[1] / 100; + var y = cmyk[2] / 100; + var k = cmyk[3] / 100; + var r; + var g; + var b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.rgb = function (xyz) { + var x = xyz[0] / 100; + var y = xyz[1] / 100; + var z = xyz[2] / 100; + var r; + var g; + var b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 + ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r * 12.92; + + g = g > 0.0031308 + ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g * 12.92; + + b = b > 0.0031308 + ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b * 12.92; + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.lab = function (xyz) { + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.lab.xyz = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var x; + var y; + var z; + + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + + var y2 = Math.pow(y, 3); + var x2 = Math.pow(x, 3); + var z2 = Math.pow(z, 3); + y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; + x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; + z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; + + x *= 95.047; + y *= 100; + z *= 108.883; + + return [x, y, z]; +}; + +convert.lab.lch = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var hr; + var h; + var c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + + if (h < 0) { + h += 360; + } + + c = Math.sqrt(a * a + b * b); + + return [l, c, h]; +}; + +convert.lch.lab = function (lch) { + var l = lch[0]; + var c = lch[1]; + var h = lch[2]; + var a; + var b; + var hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + + return [l, a, b]; +}; + +convert.rgb.ansi16 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization + + value = Math.round(value / 50); + + if (value === 0) { + return 30; + } + + var ansi = 30 + + ((Math.round(b / 255) << 2) + | (Math.round(g / 255) << 1) + | Math.round(r / 255)); + + if (value === 2) { + ansi += 60; + } + + return ansi; +}; + +convert.hsv.ansi16 = function (args) { + // optimization here; we already know the value and don't need to get + // it converted for us. + return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); +}; + +convert.rgb.ansi256 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + + // we use the extended greyscale palette here, with the exception of + // black and white. normal palette only has 4 greyscale shades. + if (r === g && g === b) { + if (r < 8) { + return 16; + } + + if (r > 248) { + return 231; + } + + return Math.round(((r - 8) / 247) * 24) + 232; + } + + var ansi = 16 + + (36 * Math.round(r / 255 * 5)) + + (6 * Math.round(g / 255 * 5)) + + Math.round(b / 255 * 5); + + return ansi; +}; + +convert.ansi16.rgb = function (args) { + var color = args % 10; + + // handle greyscale + if (color === 0 || color === 7) { + if (args > 50) { + color += 3.5; + } + + color = color / 10.5 * 255; + + return [color, color, color]; + } + + var mult = (~~(args > 50) + 1) * 0.5; + var r = ((color & 1) * mult) * 255; + var g = (((color >> 1) & 1) * mult) * 255; + var b = (((color >> 2) & 1) * mult) * 255; + + return [r, g, b]; +}; + +convert.ansi256.rgb = function (args) { + // handle greyscale + if (args >= 232) { + var c = (args - 232) * 10 + 8; + return [c, c, c]; + } + + args -= 16; + + var rem; + var r = Math.floor(args / 36) / 5 * 255; + var g = Math.floor((rem = args % 36) / 6) / 5 * 255; + var b = (rem % 6) / 5 * 255; + + return [r, g, b]; +}; + +convert.rgb.hex = function (args) { + var integer = ((Math.round(args[0]) & 0xFF) << 16) + + ((Math.round(args[1]) & 0xFF) << 8) + + (Math.round(args[2]) & 0xFF); + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.hex.rgb = function (args) { + var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + if (!match) { + return [0, 0, 0]; + } + + var colorString = match[0]; + + if (match[0].length === 3) { + colorString = colorString.split('').map(function (char) { + return char + char; + }).join(''); + } + + var integer = parseInt(colorString, 16); + var r = (integer >> 16) & 0xFF; + var g = (integer >> 8) & 0xFF; + var b = integer & 0xFF; + + return [r, g, b]; +}; + +convert.rgb.hcg = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var max = Math.max(Math.max(r, g), b); + var min = Math.min(Math.min(r, g), b); + var chroma = (max - min); + var grayscale; + var hue; + + if (chroma < 1) { + grayscale = min / (1 - chroma); + } else { + grayscale = 0; + } + + if (chroma <= 0) { + hue = 0; + } else + if (max === r) { + hue = ((g - b) / chroma) % 6; + } else + if (max === g) { + hue = 2 + (b - r) / chroma; + } else { + hue = 4 + (r - g) / chroma + 4; + } + + hue /= 6; + hue %= 1; + + return [hue * 360, chroma * 100, grayscale * 100]; +}; + +convert.hsl.hcg = function (hsl) { + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var c = 1; + var f = 0; + + if (l < 0.5) { + c = 2.0 * s * l; + } else { + c = 2.0 * s * (1.0 - l); + } + + if (c < 1.0) { + f = (l - 0.5 * c) / (1.0 - c); + } + + return [hsl[0], c * 100, f * 100]; +}; + +convert.hsv.hcg = function (hsv) { + var s = hsv[1] / 100; + var v = hsv[2] / 100; + + var c = s * v; + var f = 0; + + if (c < 1.0) { + f = (v - c) / (1 - c); + } + + return [hsv[0], c * 100, f * 100]; +}; + +convert.hcg.rgb = function (hcg) { + var h = hcg[0] / 360; + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + if (c === 0.0) { + return [g * 255, g * 255, g * 255]; + } + + var pure = [0, 0, 0]; + var hi = (h % 1) * 6; + var v = hi % 1; + var w = 1 - v; + var mg = 0; + + switch (Math.floor(hi)) { + case 0: + pure[0] = 1; pure[1] = v; pure[2] = 0; break; + case 1: + pure[0] = w; pure[1] = 1; pure[2] = 0; break; + case 2: + pure[0] = 0; pure[1] = 1; pure[2] = v; break; + case 3: + pure[0] = 0; pure[1] = w; pure[2] = 1; break; + case 4: + pure[0] = v; pure[1] = 0; pure[2] = 1; break; + default: + pure[0] = 1; pure[1] = 0; pure[2] = w; + } + + mg = (1.0 - c) * g; + + return [ + (c * pure[0] + mg) * 255, + (c * pure[1] + mg) * 255, + (c * pure[2] + mg) * 255 + ]; +}; + +convert.hcg.hsv = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var v = c + g * (1.0 - c); + var f = 0; + + if (v > 0.0) { + f = c / v; + } + + return [hcg[0], f * 100, v * 100]; +}; + +convert.hcg.hsl = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var l = g * (1.0 - c) + 0.5 * c; + var s = 0; + + if (l > 0.0 && l < 0.5) { + s = c / (2 * l); + } else + if (l >= 0.5 && l < 1.0) { + s = c / (2 * (1 - l)); + } + + return [hcg[0], s * 100, l * 100]; +}; + +convert.hcg.hwb = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + var v = c + g * (1.0 - c); + return [hcg[0], (v - c) * 100, (1 - v) * 100]; +}; + +convert.hwb.hcg = function (hwb) { + var w = hwb[1] / 100; + var b = hwb[2] / 100; + var v = 1 - b; + var c = v - w; + var g = 0; + + if (c < 1) { + g = (v - c) / (1 - c); + } + + return [hwb[0], c * 100, g * 100]; +}; + +convert.apple.rgb = function (apple) { + return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; +}; + +convert.rgb.apple = function (rgb) { + return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; +}; + +convert.gray.rgb = function (args) { + return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; +}; + +convert.gray.hsl = convert.gray.hsv = function (args) { + return [0, 0, args[0]]; +}; + +convert.gray.hwb = function (gray) { + return [0, 100, gray[0]]; +}; + +convert.gray.cmyk = function (gray) { + return [0, 0, 0, gray[0]]; +}; + +convert.gray.lab = function (gray) { + return [gray[0], 0, 0]; +}; + +convert.gray.hex = function (gray) { + var val = Math.round(gray[0] / 100 * 255) & 0xFF; + var integer = (val << 16) + (val << 8) + val; + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.rgb.gray = function (rgb) { + var val = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [val / 255 * 100]; +}; +}); +var conversions_1 = conversions.rgb; +var conversions_2 = conversions.hsl; +var conversions_3 = conversions.hsv; +var conversions_4 = conversions.hwb; +var conversions_5 = conversions.cmyk; +var conversions_6 = conversions.xyz; +var conversions_7 = conversions.lab; +var conversions_8 = conversions.lch; +var conversions_9 = conversions.hex; +var conversions_10 = conversions.keyword; +var conversions_11 = conversions.ansi16; +var conversions_12 = conversions.ansi256; +var conversions_13 = conversions.hcg; +var conversions_14 = conversions.apple; +var conversions_15 = conversions.gray; + +/* + this function routes a model to all other models. + + all functions that are routed have a property `.conversion` attached + to the returned synthetic function. This property is an array + of strings, each with the steps in between the 'from' and 'to' + color models (inclusive). + + conversions that are not possible simply are not included. +*/ + +function buildGraph() { + var graph = {}; + // https://jsperf.com/object-keys-vs-for-in-with-closure/3 + var models = Object.keys(conversions); + + for (var len = models.length, i = 0; i < len; i++) { + graph[models[i]] = { + // http://jsperf.com/1-vs-infinity + // micro-opt, but this is simple. + distance: -1, + parent: null + }; + } + + return graph; +} + +// https://en.wikipedia.org/wiki/Breadth-first_search +function deriveBFS(fromModel) { + var graph = buildGraph(); + var queue = [fromModel]; // unshift -> queue -> pop + + graph[fromModel].distance = 0; + + while (queue.length) { + var current = queue.pop(); + var adjacents = Object.keys(conversions[current]); + + for (var len = adjacents.length, i = 0; i < len; i++) { + var adjacent = adjacents[i]; + var node = graph[adjacent]; + + if (node.distance === -1) { + node.distance = graph[current].distance + 1; + node.parent = current; + queue.unshift(adjacent); + } + } + } + + return graph; +} + +function link(from, to) { + return function (args) { + return to(from(args)); + }; +} + +function wrapConversion(toModel, graph) { + var path = [graph[toModel].parent, toModel]; + var fn = conversions[graph[toModel].parent][toModel]; + + var cur = graph[toModel].parent; + while (graph[cur].parent) { + path.unshift(graph[cur].parent); + fn = link(conversions[graph[cur].parent][cur], fn); + cur = graph[cur].parent; + } + + fn.conversion = path; + return fn; +} + +var route = function (fromModel) { + var graph = deriveBFS(fromModel); + var conversion = {}; + + var models = Object.keys(graph); + for (var len = models.length, i = 0; i < len; i++) { + var toModel = models[i]; + var node = graph[toModel]; + + if (node.parent === null) { + // no possible conversion, or this node is the source model. + continue; + } + + conversion[toModel] = wrapConversion(toModel, graph); + } + + return conversion; +}; + +var convert = {}; + +var models = Object.keys(conversions); + +function wrapRaw(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + return fn(args); + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +function wrapRounded(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + var result = fn(args); + + // we're assuming the result is an array here. + // see notice in conversions.js; don't use box types + // in conversion functions. + if (typeof result === 'object') { + for (var len = result.length, i = 0; i < len; i++) { + result[i] = Math.round(result[i]); + } + } + + return result; + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +models.forEach(function (fromModel) { + convert[fromModel] = {}; + + Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); + Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); + + var routes = route(fromModel); + var routeModels = Object.keys(routes); + + routeModels.forEach(function (toModel) { + var fn = routes[toModel]; + + convert[fromModel][toModel] = wrapRounded(fn); + convert[fromModel][toModel].raw = wrapRaw(fn); + }); +}); + +var colorConvert = convert; + +var colorName$1 = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +/* MIT license */ + + +var colorString = { + getRgba: getRgba, + getHsla: getHsla, + getRgb: getRgb, + getHsl: getHsl, + getHwb: getHwb, + getAlpha: getAlpha, + + hexString: hexString, + rgbString: rgbString, + rgbaString: rgbaString, + percentString: percentString, + percentaString: percentaString, + hslString: hslString, + hslaString: hslaString, + hwbString: hwbString, + keyword: keyword +}; + +function getRgba(string) { + if (!string) { + return; + } + var abbr = /^#([a-fA-F0-9]{3,4})$/i, + hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, + rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + keyword = /(\w+)/; + + var rgb = [0, 0, 0], + a = 1, + match = string.match(abbr), + hexAlpha = ""; + if (match) { + match = match[1]; + hexAlpha = match[3]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i] + match[i], 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(hex)) { + hexAlpha = match[2]; + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(rgba)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i + 1]); + } + a = parseFloat(match[4]); + } + else if (match = string.match(per)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); + } + a = parseFloat(match[4]); + } + else if (match = string.match(keyword)) { + if (match[1] == "transparent") { + return [0, 0, 0, 0]; + } + rgb = colorName$1[match[1]]; + if (!rgb) { + return; + } + } + + for (var i = 0; i < rgb.length; i++) { + rgb[i] = scale(rgb[i], 0, 255); + } + if (!a && a != 0) { + a = 1; + } + else { + a = scale(a, 0, 1); + } + rgb[3] = a; + return rgb; +} + +function getHsla(string) { + if (!string) { + return; + } + var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hsl); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + s = scale(parseFloat(match[2]), 0, 100), + l = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, s, l, a]; + } +} + +function getHwb(string) { + if (!string) { + return; + } + var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hwb); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + w = scale(parseFloat(match[2]), 0, 100), + b = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, w, b, a]; + } +} + +function getRgb(string) { + var rgba = getRgba(string); + return rgba && rgba.slice(0, 3); +} + +function getHsl(string) { + var hsla = getHsla(string); + return hsla && hsla.slice(0, 3); +} + +function getAlpha(string) { + var vals = getRgba(string); + if (vals) { + return vals[3]; + } + else if (vals = getHsla(string)) { + return vals[3]; + } + else if (vals = getHwb(string)) { + return vals[3]; + } +} + +// generators +function hexString(rgba, a) { + var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; + return "#" + hexDouble(rgba[0]) + + hexDouble(rgba[1]) + + hexDouble(rgba[2]) + + ( + (a >= 0 && a < 1) + ? hexDouble(Math.round(a * 255)) + : "" + ); +} + +function rgbString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return rgbaString(rgba, alpha); + } + return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; +} + +function rgbaString(rgba, alpha) { + if (alpha === undefined) { + alpha = (rgba[3] !== undefined ? rgba[3] : 1); + } + return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + + ", " + alpha + ")"; +} + +function percentString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return percentaString(rgba, alpha); + } + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + + return "rgb(" + r + "%, " + g + "%, " + b + "%)"; +} + +function percentaString(rgba, alpha) { + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; +} + +function hslString(hsla, alpha) { + if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { + return hslaString(hsla, alpha); + } + return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; +} + +function hslaString(hsla, alpha) { + if (alpha === undefined) { + alpha = (hsla[3] !== undefined ? hsla[3] : 1); + } + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + + alpha + ")"; +} + +// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax +// (hwb have alpha optional & 1 is default value) +function hwbString(hwb, alpha) { + if (alpha === undefined) { + alpha = (hwb[3] !== undefined ? hwb[3] : 1); + } + return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" + + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; +} + +function keyword(rgb) { + return reverseNames[rgb.slice(0, 3)]; +} + +// helpers +function scale(num, min, max) { + return Math.min(Math.max(min, num), max); +} + +function hexDouble(num) { + var str = num.toString(16).toUpperCase(); + return (str.length < 2) ? "0" + str : str; +} + + +//create a list of reverse color names +var reverseNames = {}; +for (var name in colorName$1) { + reverseNames[colorName$1[name]] = name; +} + +/* MIT license */ + + + +var Color = function (obj) { + if (obj instanceof Color) { + return obj; + } + if (!(this instanceof Color)) { + return new Color(obj); + } + + this.valid = false; + this.values = { + rgb: [0, 0, 0], + hsl: [0, 0, 0], + hsv: [0, 0, 0], + hwb: [0, 0, 0], + cmyk: [0, 0, 0, 0], + alpha: 1 + }; + + // parse Color() argument + var vals; + if (typeof obj === 'string') { + vals = colorString.getRgba(obj); + if (vals) { + this.setValues('rgb', vals); + } else if (vals = colorString.getHsla(obj)) { + this.setValues('hsl', vals); + } else if (vals = colorString.getHwb(obj)) { + this.setValues('hwb', vals); + } + } else if (typeof obj === 'object') { + vals = obj; + if (vals.r !== undefined || vals.red !== undefined) { + this.setValues('rgb', vals); + } else if (vals.l !== undefined || vals.lightness !== undefined) { + this.setValues('hsl', vals); + } else if (vals.v !== undefined || vals.value !== undefined) { + this.setValues('hsv', vals); + } else if (vals.w !== undefined || vals.whiteness !== undefined) { + this.setValues('hwb', vals); + } else if (vals.c !== undefined || vals.cyan !== undefined) { + this.setValues('cmyk', vals); + } + } +}; + +Color.prototype = { + isValid: function () { + return this.valid; + }, + rgb: function () { + return this.setSpace('rgb', arguments); + }, + hsl: function () { + return this.setSpace('hsl', arguments); + }, + hsv: function () { + return this.setSpace('hsv', arguments); + }, + hwb: function () { + return this.setSpace('hwb', arguments); + }, + cmyk: function () { + return this.setSpace('cmyk', arguments); + }, + + rgbArray: function () { + return this.values.rgb; + }, + hslArray: function () { + return this.values.hsl; + }, + hsvArray: function () { + return this.values.hsv; + }, + hwbArray: function () { + var values = this.values; + if (values.alpha !== 1) { + return values.hwb.concat([values.alpha]); + } + return values.hwb; + }, + cmykArray: function () { + return this.values.cmyk; + }, + rgbaArray: function () { + var values = this.values; + return values.rgb.concat([values.alpha]); + }, + hslaArray: function () { + var values = this.values; + return values.hsl.concat([values.alpha]); + }, + alpha: function (val) { + if (val === undefined) { + return this.values.alpha; + } + this.setValues('alpha', val); + return this; + }, + + red: function (val) { + return this.setChannel('rgb', 0, val); + }, + green: function (val) { + return this.setChannel('rgb', 1, val); + }, + blue: function (val) { + return this.setChannel('rgb', 2, val); + }, + hue: function (val) { + if (val) { + val %= 360; + val = val < 0 ? 360 + val : val; + } + return this.setChannel('hsl', 0, val); + }, + saturation: function (val) { + return this.setChannel('hsl', 1, val); + }, + lightness: function (val) { + return this.setChannel('hsl', 2, val); + }, + saturationv: function (val) { + return this.setChannel('hsv', 1, val); + }, + whiteness: function (val) { + return this.setChannel('hwb', 1, val); + }, + blackness: function (val) { + return this.setChannel('hwb', 2, val); + }, + value: function (val) { + return this.setChannel('hsv', 2, val); + }, + cyan: function (val) { + return this.setChannel('cmyk', 0, val); + }, + magenta: function (val) { + return this.setChannel('cmyk', 1, val); + }, + yellow: function (val) { + return this.setChannel('cmyk', 2, val); + }, + black: function (val) { + return this.setChannel('cmyk', 3, val); + }, + + hexString: function () { + return colorString.hexString(this.values.rgb); + }, + rgbString: function () { + return colorString.rgbString(this.values.rgb, this.values.alpha); + }, + rgbaString: function () { + return colorString.rgbaString(this.values.rgb, this.values.alpha); + }, + percentString: function () { + return colorString.percentString(this.values.rgb, this.values.alpha); + }, + hslString: function () { + return colorString.hslString(this.values.hsl, this.values.alpha); + }, + hslaString: function () { + return colorString.hslaString(this.values.hsl, this.values.alpha); + }, + hwbString: function () { + return colorString.hwbString(this.values.hwb, this.values.alpha); + }, + keyword: function () { + return colorString.keyword(this.values.rgb, this.values.alpha); + }, + + rgbNumber: function () { + var rgb = this.values.rgb; + return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + }, + + luminosity: function () { + // http://www.w3.org/TR/WCAG20/#relativeluminancedef + var rgb = this.values.rgb; + var lum = []; + for (var i = 0; i < rgb.length; i++) { + var chan = rgb[i] / 255; + lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); + } + return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; + }, + + contrast: function (color2) { + // http://www.w3.org/TR/WCAG20/#contrast-ratiodef + var lum1 = this.luminosity(); + var lum2 = color2.luminosity(); + if (lum1 > lum2) { + return (lum1 + 0.05) / (lum2 + 0.05); + } + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } + + return (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function () { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb; + var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function () { + return !this.dark(); + }, + + negate: function () { + var rgb = []; + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues('rgb', rgb); + return this; + }, + + lighten: function (ratio) { + var hsl = this.values.hsl; + hsl[2] += hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + darken: function (ratio) { + var hsl = this.values.hsl; + hsl[2] -= hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + saturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] += hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + desaturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] -= hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + whiten: function (ratio) { + var hwb = this.values.hwb; + hwb[1] += hwb[1] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + blacken: function (ratio) { + var hwb = this.values.hwb; + hwb[2] += hwb[2] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + greyscale: function () { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues('rgb', [val, val, val]); + return this; + }, + + clearer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha - (alpha * ratio)); + return this; + }, + + opaquer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha + (alpha * ratio)); + return this; + }, + + rotate: function (degrees) { + var hsl = this.values.hsl; + var hue = (hsl[0] + degrees) % 360; + hsl[0] = hue < 0 ? 360 + hue : hue; + this.setValues('hsl', hsl); + return this; + }, + + /** + * Ported from sass implementation in C + * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 + */ + mix: function (mixinColor, weight) { + var color1 = this; + var color2 = mixinColor; + var p = weight === undefined ? 0.5 : weight; + + var w = 2 * p - 1; + var a = color1.alpha() - color2.alpha(); + + var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + return this + .rgb( + w1 * color1.red() + w2 * color2.red(), + w1 * color1.green() + w2 * color2.green(), + w1 * color1.blue() + w2 * color2.blue() + ) + .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); + }, + + toJSON: function () { + return this.rgb(); + }, + + clone: function () { + // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, + // making the final build way to big to embed in Chart.js. So let's do it manually, + // assuming that values to clone are 1 dimension arrays containing only numbers, + // except 'alpha' which is a number. + var result = new Color(); + var source = this.values; + var target = result.values; + var value, type; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + value = source[prop]; + type = ({}).toString.call(value); + if (type === '[object Array]') { + target[prop] = value.slice(0); + } else if (type === '[object Number]') { + target[prop] = value; + } else { + console.error('unexpected color value:', value); + } + } + } + + return result; + } +}; + +Color.prototype.spaces = { + rgb: ['red', 'green', 'blue'], + hsl: ['hue', 'saturation', 'lightness'], + hsv: ['hue', 'saturation', 'value'], + hwb: ['hue', 'whiteness', 'blackness'], + cmyk: ['cyan', 'magenta', 'yellow', 'black'] +}; + +Color.prototype.maxes = { + rgb: [255, 255, 255], + hsl: [360, 100, 100], + hsv: [360, 100, 100], + hwb: [360, 100, 100], + cmyk: [100, 100, 100, 100] +}; + +Color.prototype.getValues = function (space) { + var values = this.values; + var vals = {}; + + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } + + if (values.alpha !== 1) { + vals.a = values.alpha; + } + + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; +}; + +Color.prototype.setValues = function (space, vals) { + var values = this.values; + var spaces = this.spaces; + var maxes = this.maxes; + var alpha = 1; + var i; + + this.valid = true; + + if (space === 'alpha') { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (i = 0; i < space.length; i++) { + values[space][i] = vals[space.charAt(i)]; + } + + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } + + alpha = vals.alpha; + } + + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + + if (space === 'alpha') { + return false; + } + + var capped; + + // cap values of the space prior converting all values + for (i = 0; i < space.length; i++) { + capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); + values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = colorConvert[space][sname](values[space]); + } + } + + return true; +}; + +Color.prototype.setSpace = function (space, args) { + var vals = args[0]; + + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } + + this.setValues(space, vals); + return this; +}; + +Color.prototype.setChannel = function (space, index, val) { + var svalues = this.values[space]; + if (val === undefined) { + // color.red() + return svalues[index]; + } else if (val === svalues[index]) { + // color.red(color.red()) + return this; + } + + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); + + return this; +}; + +if (typeof window !== 'undefined') { + window.Color = Color; +} + +var chartjsColor = Color; + +/** + * @namespace Chart.helpers + */ +var helpers = { + /** + * An empty function that can be used, for example, for optional callback. + */ + noop: function() {}, + + /** + * Returns a unique id, sequentially generated from a global variable. + * @returns {number} + * @function + */ + uid: (function() { + var id = 0; + return function() { + return id++; + }; + }()), + + /** + * Returns true if `value` is neither null nor undefined, else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isNullOrUndef: function(value) { + return value === null || typeof value === 'undefined'; + }, + + /** + * Returns true if `value` is an array (including typed arrays), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @function + */ + isArray: function(value) { + if (Array.isArray && Array.isArray(value)) { + return true; + } + var type = Object.prototype.toString.call(value); + if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { + return true; + } + return false; + }, + + /** + * Returns true if `value` is an object (excluding null), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isObject: function(value) { + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; + }, + + /** + * Returns true if `value` is a finite number, else returns false + * @param {*} value - The value to test. + * @returns {boolean} + */ + isFinite: function(value) { + return (typeof value === 'number' || value instanceof Number) && isFinite(value); + }, + + /** + * Returns `value` if defined, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is undefined. + * @returns {*} + */ + valueOrDefault: function(value, defaultValue) { + return typeof value === 'undefined' ? defaultValue : value; + }, + + /** + * Returns value at the given `index` in array if defined, else returns `defaultValue`. + * @param {Array} value - The array to lookup for value at `index`. + * @param {number} index - The index in `value` to lookup for value. + * @param {*} defaultValue - The value to return if `value[index]` is undefined. + * @returns {*} + */ + valueAtIndexOrDefault: function(value, index, defaultValue) { + return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); + }, + + /** + * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the + * value returned by `fn`. If `fn` is not a function, this method returns undefined. + * @param {function} fn - The function to call. + * @param {Array|undefined|null} args - The arguments with which `fn` should be called. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @returns {*} + */ + callback: function(fn, args, thisArg) { + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } + }, + + /** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {object|Array} loopable - The object or array to be iterated. + * @param {function} fn - The function to call for each item. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {boolean} [reverse] - If true, iterates backward on the loopable. + */ + each: function(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[i], i); + } + } + } else if (helpers.isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); + } + } + }, + + /** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see https://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {boolean} + */ + arrayEquals: function(a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k < klen; ++k) { + target[keys[k]] = helpers.clone(source[keys[k]]); + } + + return target; + } + + return source; + }, + + /** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. + * @private + */ + _merger: function(key, target, source, options) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.merge(tval, sval, options); + } else { + target[key] = helpers.clone(sval); + } + }, + + /** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ + _mergerIf: function(key, target, source) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.mergeIf(tval, sval); + } else if (!target.hasOwnProperty(key)) { + target[key] = helpers.clone(sval); + } + }, + + /** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @param {object} [options] - Merging options: + * @param {function} [options.merger] - The merge method (key, target, source, options) + * @returns {object} The `target` object. + */ + merge: function(target, source, options) { + var sources = helpers.isArray(source) ? source : [source]; + var ilen = sources.length; + var merge, i, keys, klen, k; + + if (!helpers.isObject(target)) { + return target; + } + + options = options || {}; + merge = options.merger || helpers._merger; + + for (i = 0; i < ilen; ++i) { + source = sources[i]; + if (!helpers.isObject(source)) { + continue; + } + + keys = Object.keys(source); + for (k = 0, klen = keys.length; k < klen; ++k) { + merge(keys[k], target, source, options); + } + } + + return target; + }, + + /** + * Recursively deep copies `source` properties into `target` *only* if not defined in target. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @returns {object} The `target` object. + */ + mergeIf: function(target, source) { + return helpers.merge(target, source, {merger: helpers._mergerIf}); + }, + + /** + * Applies the contents of two or more objects together into the first object. + * @param {object} target - The target object in which all objects are merged into. + * @param {object} arg1 - Object containing additional properties to merge in target. + * @param {object} argN - Additional objects containing properties to merge in target. + * @returns {object} The `target` object. + */ + extend: Object.assign || function(target) { + return helpers.merge(target, [].slice.call(arguments, 1), { + merger: function(key, dst, src) { + dst[key] = src[key]; + } + }); + }, + + /** + * Basic javascript inheritance based on the model created in Backbone.js + */ + inherits: function(extensions) { + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; + }, + + _deprecated: function(scope, value, previous, current) { + if (value !== undefined) { + console.warn(scope + ': "' + previous + + '" is deprecated. Please use "' + current + '" instead'); + } + } +}; + +var helpers_core = helpers; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.callback instead. + * @function Chart.helpers.callCallback + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +helpers.callCallback = helpers.callback; + +/** + * Provided for backward compatibility, use Array.prototype.indexOf instead. + * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ + * @function Chart.helpers.indexOf + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.indexOf = function(array, item, fromIndex) { + return Array.prototype.indexOf.call(array, item, fromIndex); +}; + +/** + * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. + * @function Chart.helpers.getValueOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueOrDefault = helpers.valueOrDefault; + +/** + * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. + * @function Chart.helpers.getValueAtIndexOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + +/** + * Easing functions adapted from Robert Penner's easing equations. + * @namespace Chart.helpers.easingEffects + * @see http://www.robertpenner.com/easing/ + */ +var effects = { + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return -t * (t - 2); + }, + + easeInOutQuad: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return (t = t - 1) * t * t + 1; + }, + + easeInOutCubic: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return -((t = t - 1) * t * t * t - 1); + }, + + easeInOutQuart: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return (t = t - 1) * t * t * t * t + 1; + }, + + easeInOutQuint: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine: function(t) { + return -Math.cos(t * (Math.PI / 2)) + 1; + }, + + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc: function(t) { + return Math.sqrt(1 - (t = t - 1) * t); + }, + + easeInOutCirc: function(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + }, + + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; + }, + + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.45; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack: function(t) { + var s = 1.70158; + return (t = t - 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce: function(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce: function(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce: function(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } +}; + +var helpers_easing = { + effects: effects +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.easing.effects instead. + * @function Chart.helpers.easingEffects + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.easingEffects = effects; + +var PI = Math.PI; +var RAD_PER_DEG = PI / 180; +var DOUBLE_PI = PI * 2; +var HALF_PI = PI / 2; +var QUARTER_PI = PI / 4; +var TWO_THIRDS_PI = PI * 2 / 3; + +/** + * @namespace Chart.helpers.canvas + */ +var exports$1 = { + /** + * Clears the entire canvas associated to the given `chart`. + * @param {Chart} chart - The chart for which to clear the canvas. + */ + clear: function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }, + + /** + * Creates a "path" for a rectangle with rounded corners at position (x, y) with a + * given size (width, height) and the same `radius` for all corners. + * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. + * @param {number} x - The x axis of the coordinate for the rectangle starting point. + * @param {number} y - The y axis of the coordinate for the rectangle starting point. + * @param {number} width - The rectangle's width. + * @param {number} height - The rectangle's height. + * @param {number} radius - The rounded amount (in pixels) for the four corners. + * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? + */ + roundedRect: function(ctx, x, y, width, height, radius) { + if (radius) { + var r = Math.min(radius, height / 2, width / 2); + var left = x + r; + var top = y + r; + var right = x + width - r; + var bottom = y + height - r; + + ctx.moveTo(x, top); + if (left < right && top < bottom) { + ctx.arc(left, top, r, -PI, -HALF_PI); + ctx.arc(right, top, r, -HALF_PI, 0); + ctx.arc(right, bottom, r, 0, HALF_PI); + ctx.arc(left, bottom, r, HALF_PI, PI); + } else if (left < right) { + ctx.moveTo(left, y); + ctx.arc(right, top, r, -HALF_PI, HALF_PI); + ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); + } else if (top < bottom) { + ctx.arc(left, top, r, -PI, 0); + ctx.arc(left, bottom, r, 0, PI); + } else { + ctx.arc(left, top, r, -PI, PI); + } + ctx.closePath(); + ctx.moveTo(x, y); + } else { + ctx.rect(x, y, width, height); + } + }, + + drawPoint: function(ctx, style, radius, x, y, rotation) { + var type, xOffset, yOffset, size, cornerRadius; + var rad = (rotation || 0) * RAD_PER_DEG; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rad); + ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); + ctx.restore(); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, DOUBLE_PI); + ctx.closePath(); + break; + case 'triangle': + ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + ctx.closePath(); + break; + case 'rectRounded': + // NOTE: the rounded rect implementation changed to use `arc` instead of + // `quadraticCurveTo` since it generates better results when rect is + // almost a circle. 0.516 (instead of 0.5) produces results with visually + // closer proportion to the previous impl and it is inscribed in the + // circle with `radius`. For more details, see the following PRs: + // https://github.com/chartjs/Chart.js/issues/5597 + // https://github.com/chartjs/Chart.js/issues/5858 + cornerRadius = radius * 0.516; + size = radius - cornerRadius; + xOffset = Math.cos(rad + QUARTER_PI) * size; + yOffset = Math.sin(rad + QUARTER_PI) * size; + ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); + ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); + ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); + ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); + ctx.closePath(); + break; + case 'rect': + if (!rotation) { + size = Math.SQRT1_2 * radius; + ctx.rect(x - size, y - size, 2 * size, 2 * size); + break; + } + rad += QUARTER_PI; + /* falls through */ + case 'rectRot': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + yOffset, y - xOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.lineTo(x - yOffset, y + xOffset); + ctx.closePath(); + break; + case 'crossRot': + rad += QUARTER_PI; + /* falls through */ + case 'cross': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'star': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + rad += QUARTER_PI; + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'line': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + break; + case 'dash': + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + break; + } + + ctx.fill(); + ctx.stroke(); + }, + + /** + * Returns true if the point is inside the rectangle + * @param {object} point - The point to test + * @param {object} area - The rectangle + * @returns {boolean} + * @private + */ + _isPointInArea: function(point, area) { + var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. + + return point.x > area.left - epsilon && point.x < area.right + epsilon && + point.y > area.top - epsilon && point.y < area.bottom + epsilon; + }, + + clipArea: function(ctx, area) { + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); + }, + + unclipArea: function(ctx) { + ctx.restore(); + }, + + lineTo: function(ctx, previous, target, flip) { + var stepped = target.steppedLine; + if (stepped) { + if (stepped === 'middle') { + var midpoint = (previous.x + target.x) / 2.0; + ctx.lineTo(midpoint, flip ? target.y : previous.y); + ctx.lineTo(midpoint, flip ? previous.y : target.y); + } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); + } + ctx.lineTo(target.x, target.y); + return; + } + + if (!target.tension) { + ctx.lineTo(target.x, target.y); + return; + } + + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); + } +}; + +var helpers_canvas = exports$1; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. + * @namespace Chart.helpers.clear + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.clear = exports$1.clear; + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. + * @namespace Chart.helpers.drawRoundedRectangle + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.drawRoundedRectangle = function(ctx) { + ctx.beginPath(); + exports$1.roundedRect.apply(exports$1, arguments); +}; + +var defaults = { + /** + * @private + */ + _set: function(scope, values) { + return helpers_core.merge(this[scope] || (this[scope] = {}), values); + } +}; + +// TODO(v3): remove 'global' from namespace. all default are global and +// there's inconsistency around which options are under 'global' +defaults._set('global', { + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + defaultLineHeight: 1.2, + showLines: true +}); + +var core_defaults = defaults; + +var valueOrDefault = helpers_core.valueOrDefault; + +/** + * Converts the given font object into a CSS font string. + * @param {object} font - A font object. + * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font + * @private + */ +function toFontString(font) { + if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { + return null; + } + + return (font.style ? font.style + ' ' : '') + + (font.weight ? font.weight + ' ' : '') + + font.size + 'px ' + + font.family; +} + +/** + * @alias Chart.helpers.options + * @namespace + */ +var helpers_options = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = +matches[2]; + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + } + + return size * value; + }, + + /** + * Converts the given value into a padding object with pre-computed width/height. + * @param {number|object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ + toPadding: function(value) { + var t, r, b, l; + + if (helpers_core.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; + }, + + /** + * Parses font options and returns the font object. + * @param {object} options - A object that contains font options to be parsed. + * @return {object} The font object. + * @todo Support font.* options and renamed to toFont(). + * @private + */ + _parseFont: function(options) { + var globalDefaults = core_defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var font = { + family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), + lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), + size: size, + style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), + weight: null, + string: '' + }; + + font.string = toFontString(font); + return font; + }, + + /** + * Evaluates the given `inputs` sequentially and returns the first defined value. + * @param {Array} inputs - An array of values, falling back to the last value. + * @param {object} [context] - If defined and the current value is a function, the value + * is called with `context` as first argument and the result becomes the new input. + * @param {number} [index] - If defined and the current value is an array, the value + * at `index` become the new input. + * @param {object} [info] - object to return information about resolution in + * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. + * @since 2.7.0 + */ + resolve: function(inputs, context, index, info) { + var cacheable = true; + var i, ilen, value; + + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + cacheable = false; + } + if (index !== undefined && helpers_core.isArray(value)) { + value = value[index]; + cacheable = false; + } + if (value !== undefined) { + if (info && !cacheable) { + info.cacheable = false; + } + return value; + } + } + } +}; + +/** + * @alias Chart.helpers.math + * @namespace + */ +var exports$2 = { + /** + * Returns an array of factors sorted from 1 to sqrt(value) + * @private + */ + _factorize: function(value) { + var result = []; + var sqrt = Math.sqrt(value); + var i; + + for (i = 1; i < sqrt; i++) { + if (value % i === 0) { + result.push(i); + result.push(value / i); + } + } + if (sqrt === (sqrt | 0)) { // if value is a square number + result.push(sqrt); + } + + result.sort(function(a, b) { + return a - b; + }).pop(); + return result; + }, + + log10: Math.log10 || function(x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); + + return isPowerOf10 ? powerOf10 : exponent; + } +}; + +var helpers_math = exports$2; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.math.log10 instead. + * @namespace Chart.helpers.log10 + * @deprecated since version 2.9.0 + * @todo remove at version 3 + * @private + */ +helpers_core.log10 = exports$2.log10; + +var getRtlAdapter = function(rectX, width) { + return { + x: function(x) { + return rectX + rectX + width - x; + }, + setWidth: function(w) { + width = w; + }, + textAlign: function(align) { + if (align === 'center') { + return align; + } + return align === 'right' ? 'left' : 'right'; + }, + xPlus: function(x, value) { + return x - value; + }, + leftForLtr: function(x, itemWidth) { + return x - itemWidth; + }, + }; +}; + +var getLtrAdapter = function() { + return { + x: function(x) { + return x; + }, + setWidth: function(w) { // eslint-disable-line no-unused-vars + }, + textAlign: function(align) { + return align; + }, + xPlus: function(x, value) { + return x + value; + }, + leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars + return x; + }, + }; +}; + +var getAdapter = function(rtl, rectX, width) { + return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); +}; + +var overrideTextDirection = function(ctx, direction) { + var style, original; + if (direction === 'ltr' || direction === 'rtl') { + style = ctx.canvas.style; + original = [ + style.getPropertyValue('direction'), + style.getPropertyPriority('direction'), + ]; + + style.setProperty('direction', direction, 'important'); + ctx.prevTextDirection = original; + } +}; + +var restoreTextDirection = function(ctx) { + var original = ctx.prevTextDirection; + if (original !== undefined) { + delete ctx.prevTextDirection; + ctx.canvas.style.setProperty('direction', original[0], original[1]); + } +}; + +var helpers_rtl = { + getRtlAdapter: getAdapter, + overrideTextDirection: overrideTextDirection, + restoreTextDirection: restoreTextDirection, +}; + +var helpers$1 = helpers_core; +var easing = helpers_easing; +var canvas = helpers_canvas; +var options = helpers_options; +var math = helpers_math; +var rtl = helpers_rtl; +helpers$1.easing = easing; +helpers$1.canvas = canvas; +helpers$1.options = options; +helpers$1.math = math; +helpers$1.rtl = rtl; + +function interpolate(start, view, model, ease) { + var keys = Object.keys(model); + var i, ilen, key, actual, origin, target, type, c0, c1; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + + target = model[key]; + + // if a value is added to the model after pivot() has been called, the view + // doesn't contain it, so let's initialize the view to the target value. + if (!view.hasOwnProperty(key)) { + view[key] = target; + } + + actual = view[key]; + + if (actual === target || key[0] === '_') { + continue; + } + + if (!start.hasOwnProperty(key)) { + start[key] = actual; + } + + origin = start[key]; + + type = typeof target; + + if (type === typeof origin) { + if (type === 'string') { + c0 = chartjsColor(origin); + if (c0.valid) { + c1 = chartjsColor(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } +} + +var Element = function(configuration) { + helpers$1.extend(this, configuration); + this.initialize.apply(this, arguments); +}; + +helpers$1.extend(Element.prototype, { + _type: undefined, + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers$1.extend({}, me._model); + } + me._start = {}; + return me; + }, + + transition: function(ease) { + var me = this; + var model = me._model; + var start = me._start; + var view = me._view; + + // No animation -> No Transition + if (!model || ease === 1) { + me._view = helpers$1.extend({}, model); + me._start = null; + return me; + } + + if (!view) { + view = me._view = {}; + } + + if (!start) { + start = me._start = {}; + } + + interpolate(start, view, model, ease); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); + } +}); + +Element.extend = helpers$1.inherits; + +var core_element = Element; + +var exports$3 = core_element.extend({ + chart: null, // the animation associated chart instance + currentStep: 0, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes +}); + +var core_animation = exports$3; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.Animation instead + * @prop Chart.Animation#animationObject + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'animationObject', { + get: function() { + return this; + } +}); + +/** + * Provided for backward compatibility, use Chart.Animation#chart instead + * @prop Chart.Animation#chartInstance + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'chartInstance', { + get: function() { + return this.chart; + }, + set: function(value) { + this.chart = value; + } +}); + +core_defaults._set('global', { + animation: { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers$1.noop, + onComplete: helpers$1.noop + } +}); + +var core_animations = { + animations: [], + request: null, + + /** + * @param {Chart} chart - The chart to animate. + * @param {Chart.Animation} animation - The animation that we will animate. + * @param {number} duration - The animation duration in ms. + * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chart, animation, duration, lazy) { + var animations = this.animations; + var i, ilen; + + animation.chart = chart; + animation.startTime = Date.now(); + animation.duration = duration; + + if (!lazy) { + chart.animating = true; + } + + for (i = 0, ilen = animations.length; i < ilen; ++i) { + if (animations[i].chart === chart) { + animations[i] = animation; + return; + } + } + + animations.push(animation); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (animations.length === 1) { + this.requestAnimationFrame(); + } + }, + + cancelAnimation: function(chart) { + var index = helpers$1.findIndex(this.animations, function(animation) { + return animation.chart === chart; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chart.animating = false; + } + }, + + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers$1.requestAnimFrame.call(window, function() { + me.request = null; + me.startDigest(); + }); + } + }, + + /** + * @private + */ + startDigest: function() { + var me = this; + + me.advance(); + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + }, + + /** + * @private + */ + advance: function() { + var animations = this.animations; + var animation, chart, numSteps, nextStep; + var i = 0; + + // 1 animation per chart, so we are looping charts here + while (i < animations.length) { + animation = animations[i]; + chart = animation.chart; + numSteps = animation.numSteps; + + // Make sure that currentStep starts at 1 + // https://github.com/chartjs/Chart.js/issues/6104 + nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; + animation.currentStep = Math.min(nextStep, numSteps); + + helpers$1.callback(animation.render, [chart, animation], chart); + helpers$1.callback(animation.onAnimationProgress, [animation], chart); + + if (animation.currentStep >= numSteps) { + helpers$1.callback(animation.onAnimationComplete, [animation], chart); + chart.animating = false; + animations.splice(i, 1); + } else { + ++i; + } + } + } +}; + +var resolve = helpers$1.options.resolve; + +var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + +/** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ +function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers$1.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; + } + }); + }); +} + +/** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ +function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } + + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; +} + +// Base class for all dataset controllers (line, bar, etc) +var DatasetController = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); +}; + +helpers$1.extend(DatasetController.prototype, { + + /** + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} + */ + datasetElementType: null, + + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, + + /** + * Dataset element option keys to be resolved in _resolveDatasetElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth' + ], + + /** + * Data element option keys to be resolved in _resolveDataElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'pointStyle' + ], + + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + me._type = me.getMeta().type; + }, + + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var chart = me.chart; + var scales = chart.scales; + var dataset = me.getDataset(); + var scalesOpts = chart.options.scales; + + if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) { + meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id; + } + if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) { + meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().yAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getValueScale: function() { + return this.getScaleForId(this._getValueScaleId()); + }, + + /** + * @private + */ + _getIndexScale: function() { + return this.getScaleForId(this._getIndexScaleId()); + }, + + reset: function() { + this._update(true); + }, + + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index + }); + }, + + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index, + _index: index + }); + }, + + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; + + for (i = 0, ilen = data.length; i < ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } + + meta.dataset = meta.dataset || me.createMetaDataset(); + }, + + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, + + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); + + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } + + if (data && Object.isExtensible(data)) { + listenArrayEvents(data, me); + } + me._data = data; + } + + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, + + /** + * Returns the merged user-supplied and default dataset-level options + * @private + */ + _configure: function() { + var me = this; + me._config = helpers$1.merge({}, [ + me.chart.options.datasets[me._type], + me.getDataset(), + ], { + merger: function(key, target, source) { + if (key !== '_meta' && key !== 'data') { + helpers$1._merger(key, target, source); + } + } + }); + }, + + _update: function(reset) { + var me = this; + me._configure(); + me._cachedDataOpts = null; + me.update(reset); + }, + + update: helpers$1.noop, + + transition: function(easingValue) { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + for (; i < ilen; ++i) { + elements[i].transition(easingValue); + } + + if (meta.dataset) { + meta.dataset.transition(easingValue); + } + }, + + draw: function() { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + if (meta.dataset) { + meta.dataset.draw(); + } + + for (; i < ilen; ++i) { + elements[i].draw(); + } + }, + + /** + * Returns a set of predefined style properties that should be used to represent the dataset + * or the data if the index is specified + * @param {number} index - data index + * @return {IStyleInterface} style object + */ + getStyle: function(index) { + var me = this; + var meta = me.getMeta(); + var dataset = meta.dataset; + var style; + + me._configure(); + if (dataset && index === undefined) { + style = me._resolveDatasetElementOptions(dataset || {}); + } else { + index = index || 0; + style = me._resolveDataElementOptions(meta.data[index] || {}, index); + } + + if (style.fill === false || style.fill === null) { + style.backgroundColor = style.borderColor; + } + + return style; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element, hover) { + var me = this; + var chart = me.chart; + var datasetOpts = me._config; + var custom = element.custom || {}; + var options = chart.options.elements[me.datasetElementType.prototype._type] || {}; + var elementOptions = me._datasetElementOptions; + var values = {}; + var i, ilen, key, readKey; + + // Scriptable options + var context = { + chart: chart, + dataset: me.getDataset(), + datasetIndex: me.index, + hover: hover + }; + + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key; + values[key] = resolve([ + custom[readKey], + datasetOpts[readKey], + options[readKey] + ], context); + } + + return values; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(element, index) { + var me = this; + var custom = element && element.custom; + var cached = me._cachedDataOpts; + if (cached && !custom) { + return cached; + } + var chart = me.chart; + var datasetOpts = me._config; + var options = chart.options.elements[me.dataElementType.prototype._type] || {}; + var elementOptions = me._dataElementOptions; + var values = {}; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: me.getDataset(), + datasetIndex: me.index + }; + + // `resolve` sets cacheable to `false` if any option is indexed or scripted + var info = {cacheable: !custom}; + + var keys, i, ilen, key; + + custom = custom || {}; + + if (helpers$1.isArray(elementOptions)) { + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + values[key] = resolve([ + custom[key], + datasetOpts[key], + options[key] + ], context, index, info); + } + } else { + keys = Object.keys(elementOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + datasetOpts[elementOptions[key]], + datasetOpts[key], + options[key] + ], context, index, info); + } + } + + if (info.cacheable) { + me._cachedDataOpts = Object.freeze(values); + } + + return values; + }, + + removeHoverStyle: function(element) { + helpers$1.merge(element._model, element.$previousStyle || {}); + delete element.$previousStyle; + }, + + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var model = element._model; + var getHoverColor = helpers$1.getHoverColor; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth + }; + + model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); + model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); + model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); + }, + + /** + * @private + */ + _removeDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + + if (element) { + this.removeHoverStyle(element); + } + }, + + /** + * @private + */ + _setDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + var prev = {}; + var i, ilen, key, keys, hoverOptions, model; + + if (!element) { + return; + } + + model = element._model; + hoverOptions = this._resolveDatasetElementOptions(element, true); + + keys = Object.keys(hoverOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + prev[key] = model[key]; + model[key] = hoverOptions[key]; + } + + element.$previousStyle = prev; + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i = 0; i < count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + var count = arguments.length; + this.insertElements(this.getDataset().data.length - count, count); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } +}); + +DatasetController.extend = helpers$1.inherits; + +var core_datasetController = DatasetController; + +var TAU = Math.PI * 2; + +core_defaults._set('global', { + elements: { + arc: { + backgroundColor: core_defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2, + borderAlign: 'center' + } + } +}); + +function clipArc(ctx, arc) { + var startAngle = arc.startAngle; + var endAngle = arc.endAngle; + var pixelMargin = arc.pixelMargin; + var angleMargin = pixelMargin / arc.outerRadius; + var x = arc.x; + var y = arc.y; + + // Draw an inner border by cliping the arc and drawing a double-width border + // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders + ctx.beginPath(); + ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin); + if (arc.innerRadius > pixelMargin) { + angleMargin = pixelMargin / arc.innerRadius; + ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true); + } else { + ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2); + } + ctx.closePath(); + ctx.clip(); +} + +function drawFullCircleBorders(ctx, vm, arc, inner) { + var endAngle = arc.endAngle; + var i; + + if (inner) { + arc.endAngle = arc.startAngle + TAU; + clipArc(ctx, arc); + arc.endAngle = endAngle; + if (arc.endAngle === arc.startAngle && arc.fullCircles) { + arc.endAngle += TAU; + arc.fullCircles--; + } + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } +} + +function drawBorder(ctx, vm, arc) { + var inner = vm.borderAlign === 'inner'; + + if (inner) { + ctx.lineWidth = vm.borderWidth * 2; + ctx.lineJoin = 'round'; + } else { + ctx.lineWidth = vm.borderWidth; + ctx.lineJoin = 'bevel'; + } + + if (arc.fullCircles) { + drawFullCircleBorders(ctx, vm, arc, inner); + } + + if (inner) { + clipArc(ctx, arc); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.stroke(); +} + +var element_arc = core_element.extend({ + _type: 'arc', + + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } + return false; + }, + + inRange: function(chartX, chartY) { + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); + var angle = pointRelativePosition.angle; + var distance = pointRelativePosition.distance; + + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += TAU; + } + while (angle > endAngle) { + angle -= TAU; + } + while (angle < startAngle) { + angle += TAU; + } + + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle); + var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + + tooltipPosition: function() { + var vm = this._view; + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); + var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; + var arc = { + x: vm.x, + y: vm.y, + innerRadius: vm.innerRadius, + outerRadius: Math.max(vm.outerRadius - pixelMargin, 0), + pixelMargin: pixelMargin, + startAngle: vm.startAngle, + endAngle: vm.endAngle, + fullCircles: Math.floor(vm.circumference / TAU) + }; + var i; + + ctx.save(); + + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + + if (arc.fullCircles) { + arc.endAngle = arc.startAngle + TAU; + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.fill(); + } + arc.endAngle = arc.startAngle + vm.circumference % TAU; + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.fill(); + + if (vm.borderWidth) { + drawBorder(ctx, vm, arc); + } + + ctx.restore(); + } +}); + +var valueOrDefault$1 = helpers$1.valueOrDefault; + +var defaultColor = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: defaultColor, + borderWidth: 3, + borderColor: defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + } + } +}); + +var element_line = core_element.extend({ + _type: 'line', + + draw: function() { + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; + var spanGaps = vm.spanGaps; + var points = me._children.slice(); // clone array + var globalDefaults = core_defaults.global; + var globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var closePath = me._loop; + var index, previous, currentVM; + + if (!points.length) { + return; + } + + if (me._loop) { + for (index = 0; index < points.length; ++index) { + previous = helpers$1.previousItem(points, index); + // If the line has an open path, shift the point array + if (!points[index]._view.skip && previous._view.skip) { + points = points.slice(index).concat(points.slice(0, index)); + closePath = spanGaps; + break; + } + } + // If the line has a close path, add the first point again + if (closePath) { + points.push(points[0]); + } + } + + ctx.save(); + + // Stroke Line Options + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } + + ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + + // Stroke Line + ctx.beginPath(); + + // First point moves to it's starting position no matter what + currentVM = points[0]._view; + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = 0; + } + + for (index = 1; index < points.length; ++index) { + currentVM = points[index]._view; + previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex]; + + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + helpers$1.canvas.lineTo(ctx, previous._view, currentVM); + } + lastDrawnIndex = index; + } + } + + if (closePath) { + ctx.closePath(); + } + + ctx.stroke(); + ctx.restore(); + } +}); + +var valueOrDefault$2 = helpers$1.valueOrDefault; + +var defaultColor$1 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor$1, + borderColor: defaultColor$1, + borderWidth: 1, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + } + } +}); + +function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; +} + +function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; +} + +var element_point = core_element.extend({ + _type: 'point', + + inRange: function(mouseX, mouseY) { + var vm = this._view; + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, + + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + }, + + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + + draw: function(chartArea) { + var vm = this._view; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var rotation = vm.rotation; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var globalDefaults = core_defaults.global; + var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow + + if (vm.skip) { + return; + } + + // Clipping for Points. + if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); + } + } +}); + +var defaultColor$2 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaultColor$2, + borderColor: defaultColor$2, + borderSkipped: 'bottom', + borderWidth: 0 + } + } +}); + +function isVertical(vm) { + return vm && vm.width !== undefined; +} + +/** + * Helper function to get the bounds of the bar regardless of the orientation + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + * @private + */ +function getBarBounds(vm) { + var x1, x2, y1, y2, half; + + if (isVertical(vm)) { + half = vm.width / 2; + x1 = vm.x - half; + x2 = vm.x + half; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + half = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - half; + y2 = vm.y + half; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; +} + +function swap(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} + +function parseBorderSkipped(vm) { + var edge = vm.borderSkipped; + var res = {}; + + if (!edge) { + return res; + } + + if (vm.horizontal) { + if (vm.base > vm.x) { + edge = swap(edge, 'left', 'right'); + } + } else if (vm.base < vm.y) { + edge = swap(edge, 'bottom', 'top'); + } + + res[edge] = true; + return res; +} + +function parseBorderWidth(vm, maxW, maxH) { + var value = vm.borderWidth; + var skip = parseBorderSkipped(vm); + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, + r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, + b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, + l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l + }; +} + +function boundingRects(vm) { + var bounds = getBarBounds(vm); + var width = bounds.right - bounds.left; + var height = bounds.bottom - bounds.top; + var border = parseBorderWidth(vm, width / 2, height / 2); + + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b + } + }; +} + +function inRange(vm, x, y) { + var skipX = x === null; + var skipY = y === null; + var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); + + return bounds + && (skipX || x >= bounds.left && x <= bounds.right) + && (skipY || y >= bounds.top && y <= bounds.bottom); +} + +var element_rectangle = core_element.extend({ + _type: 'rectangle', + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var rects = boundingRects(vm); + var outer = rects.outer; + var inner = rects.inner; + + ctx.fillStyle = vm.backgroundColor; + ctx.fillRect(outer.x, outer.y, outer.w, outer.h); + + if (outer.w === inner.w && outer.h === inner.h) { + return; + } + + ctx.save(); + ctx.beginPath(); + ctx.rect(outer.x, outer.y, outer.w, outer.h); + ctx.clip(); + ctx.fillStyle = vm.borderColor; + ctx.rect(inner.x, inner.y, inner.w, inner.h); + ctx.fill('evenodd'); + ctx.restore(); + }, + + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function(mouseX, mouseY) { + return inRange(this._view, mouseX, mouseY); + }, + + inLabelRange: function(mouseX, mouseY) { + var vm = this._view; + return isVertical(vm) + ? inRange(vm, mouseX, null) + : inRange(vm, null, mouseY); + }, + + inXRange: function(mouseX) { + return inRange(this._view, mouseX, null); + }, + + inYRange: function(mouseY) { + return inRange(this._view, null, mouseY); + }, + + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(vm)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + + getArea: function() { + var vm = this._view; + + return isVertical(vm) + ? vm.width * Math.abs(vm.y - vm.base) + : vm.height * Math.abs(vm.x - vm.base); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } +}); + +var elements = {}; +var Arc = element_arc; +var Line = element_line; +var Point = element_point; +var Rectangle = element_rectangle; +elements.Arc = Arc; +elements.Line = Line; +elements.Point = Point; +elements.Rectangle = Rectangle; + +var deprecated = helpers$1._deprecated; +var valueOrDefault$3 = helpers$1.valueOrDefault; + +core_defaults._set('bar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + offset: true, + gridLines: { + offsetGridLines: true + } + }], + + yAxes: [{ + type: 'linear' + }] + } +}); + +core_defaults._set('global', { + datasets: { + bar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +/** + * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. + * @private + */ +function computeMinSampleSize(scale, pixels) { + var min = scale._length; + var prev, curr, i, ilen; + + for (i = 1, ilen = pixels.length; i < ilen; ++i) { + min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); + } + + for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { + curr = scale.getPixelForTick(i); + min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; + prev = curr; + } + + return min; +} + +/** + * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, + * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This + * mode currently always generates bars equally sized (until we introduce scriptable options?). + * @private + */ +function computeFitCategoryTraits(index, ruler, options) { + var thickness = options.barThickness; + var count = ruler.stackCount; + var curr = ruler.pixels[index]; + var min = helpers$1.isNullOrUndef(thickness) + ? computeMinSampleSize(ruler.scale, ruler.pixels) + : -1; + var size, ratio; + + if (helpers$1.isNullOrUndef(thickness)) { + size = min * options.categoryPercentage; + ratio = options.barPercentage; + } else { + // When bar thickness is enforced, category and bar percentages are ignored. + // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') + // and deprecate barPercentage since this value is ignored when thickness is absolute. + size = thickness * count; + ratio = 1; + } + + return { + chunk: size / count, + ratio: ratio, + start: curr - (size / 2) + }; +} + +/** + * Computes an "optimal" category that globally arranges bars side by side (no gap when + * percentage options are 1), based on the previous and following categories. This mode + * generates bars with different widths when data are not evenly spaced. + * @private + */ +function computeFlexCategoryTraits(index, ruler, options) { + var pixels = ruler.pixels; + var curr = pixels[index]; + var prev = index > 0 ? pixels[index - 1] : null; + var next = index < pixels.length - 1 ? pixels[index + 1] : null; + var percent = options.categoryPercentage; + var start, size; + + if (prev === null) { + // first data: its size is double based on the next point or, + // if it's also the last data, we use the scale size. + prev = curr - (next === null ? ruler.end - ruler.start : next - curr); + } + + if (next === null) { + // last data: its size is also double based on the previous point. + next = curr + curr - prev; + } + + start = curr - (curr - Math.min(prev, next)) / 2 * percent; + size = Math.abs(next - prev) / 2 * percent; + + return { + chunk: size / ruler.stackCount, + ratio: options.barPercentage, + start: start + }; +} + +var controller_bar = core_datasetController.extend({ + + dataElementType: elements.Rectangle, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderSkipped', + 'borderWidth', + 'barPercentage', + 'barThickness', + 'categoryPercentage', + 'maxBarThickness', + 'minBarLength' + ], + + initialize: function() { + var me = this; + var meta, scaleOpts; + + core_datasetController.prototype.initialize.apply(me, arguments); + + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + + scaleOpts = me._getIndexScale().options; + deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage'); + deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness'); + deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage'); + deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength'); + deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness'); + }, + + update: function(reset) { + var me = this; + var rects = me.getMeta().data; + var i, ilen; + + me._ruler = me.getRuler(); + + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); + } + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var options = me._resolveDataElementOptions(rectangle, index); + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + rectangle._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderSkipped: options.borderSkipped, + borderWidth: options.borderWidth, + datasetLabel: dataset.label, + label: me.chart.data.labels[index] + }; + + if (helpers$1.isArray(dataset.data[index])) { + rectangle._model.borderSkipped = null; + } + + me._updateElementGeometry(rectangle, index, reset, options); + + rectangle.pivot(); + }, + + /** + * @private + */ + _updateElementGeometry: function(rectangle, index, reset, options) { + var me = this; + var model = rectangle._model; + var vscale = me._getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index, options); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options); + + model.horizontal = horizontal; + model.base = reset ? base : vpixels.base; + model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; + model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; + model.height = horizontal ? ipixels.size : undefined; + model.width = horizontal ? undefined : ipixels.size; + }, + + /** + * Returns the stacks based on groups and bar visibility. + * @param {number} [last] - The dataset index + * @returns {string[]} The list of stack IDs + * @private + */ + _getStacks: function(last) { + var me = this; + var scale = me._getIndexScale(); + var metasets = scale._getMatchingVisibleMetas(me._type); + var stacked = scale.options.stacked; + var ilen = metasets.length; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = metasets[i]; + // stacked | meta.stack + // | found | not found | undefined + // false | x | x | x + // true | | x | + // undefined | | x | x + if (stacked === false || stacks.indexOf(meta.stack) === -1 || + (stacked === undefined && meta.stack === undefined)) { + stacks.push(meta.stack); + } + if (meta.index === last) { + break; + } + } + + return stacks; + }, + + /** + * Returns the effective number of stacks based on groups and bar visibility. + * @private + */ + getStackCount: function() { + return this._getStacks().length; + }, + + /** + * Returns the stack index for the given dataset based on groups and bar visibility. + * @param {number} [datasetIndex] - The dataset index + * @param {string} [name] - The stack name to find + * @returns {number} The stack index + * @private + */ + getStackIndex: function(datasetIndex, name) { + var stacks = this._getStacks(datasetIndex); + var index = (name !== undefined) + ? stacks.indexOf(name) + : -1; // indexOf returns -1 if element is not present + + return (index === -1) + ? stacks.length - 1 + : index; + }, + + /** + * @private + */ + getRuler: function() { + var me = this; + var scale = me._getIndexScale(); + var pixels = []; + var i, ilen; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, me.index)); + } + + return { + pixels: pixels, + start: scale._startPixel, + end: scale._endPixel, + stackCount: me.getStackCount(), + scale: scale + }; + }, + + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function(datasetIndex, index, options) { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var isHorizontal = scale.isHorizontal(); + var datasets = chart.data.datasets; + var metasets = scale._getMatchingVisibleMetas(me._type); + var value = scale._parseValue(datasets[datasetIndex].data[index]); + var minBarLength = options.minBarLength; + var stacked = scale.options.stacked; + var stack = me.getMeta().stack; + var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; + var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; + var ilen = metasets.length; + var i, imeta, ivalue, base, head, size, stackLength; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < ilen; ++i) { + imeta = metasets[i]; + + if (imeta.index === datasetIndex) { + break; + } + + if (imeta.stack === stack) { + stackLength = scale._parseValue(datasets[imeta.index].data[index]); + ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; + + if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { + start += ivalue; + } + } + } + } + + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + length); + size = head - base; + + if (minBarLength !== undefined && Math.abs(size) < minBarLength) { + size = minBarLength; + if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) { + head = base - minBarLength; + } else { + head = base + minBarLength; + } + } + + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, + + /** + * @private + */ + calculateBarIndexPixels: function(datasetIndex, index, ruler, options) { + var me = this; + var range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options) + : computeFitCategoryTraits(index, ruler, options); + + var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); + var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + var size = Math.min( + valueOrDefault$3(options.maxBarThickness, Infinity), + range.chunk * range.ratio); + + return { + base: center - size / 2, + head: center + size / 2, + center: center, + size: size + }; + }, + + draw: function() { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var rects = me.getMeta().data; + var dataset = me.getDataset(); + var ilen = rects.length; + var i = 0; + + helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + var val = scale._parseValue(dataset.data[i]); + if (!isNaN(val.min) && !isNaN(val.max)) { + rects[i].draw(); + } + } + + helpers$1.canvas.unclipArea(chart.ctx); + }, + + /** + * @private + */ + _resolveDataElementOptions: function() { + var me = this; + var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments)); + var indexOpts = me._getIndexScale().options; + var valueOpts = me._getValueScale().options; + + values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage); + values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness); + values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage); + values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness); + values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength); + + return values; + } + +}); + +var valueOrDefault$4 = helpers$1.valueOrDefault; +var resolve$1 = helpers$1.options.resolve; + +core_defaults._set('bubble', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, + + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + var dataPoint = data.datasets[item.datasetIndex].data[item.index]; + return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; + } + } + } +}); + +var controller_bubble = core_datasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle', + 'rotation' + ], + + /** + * @protected + */ + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers$1.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, + + /** + * @protected + */ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var options = me._resolveDataElementOptions(point, index); + var data = me.getDataset().data[index]; + var dsIndex = me.index; + + var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); + var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); + + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = dsIndex; + point._index = index; + point._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + hitRadius: options.hitRadius, + pointStyle: options.pointStyle, + rotation: options.rotation, + radius: reset ? 0 : options.radius, + skip: custom.skip || isNaN(x) || isNaN(y), + x: x, + y: y, + }; + + point.pivot(); + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(point, index) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var custom = point.custom || {}; + var data = dataset.data[index] || {}; + var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + // In case values were cached (and thus frozen), we need to clone the values + if (me._cachedDataOpts === values) { + values = helpers$1.extend({}, values); + } + + // Custom radius resolution + values.radius = resolve$1([ + custom.radius, + data.r, + me._config.radius, + chart.options.elements.point.radius + ], context, index); + + return values; + } +}); + +var valueOrDefault$5 = helpers$1.valueOrDefault; + +var PI$1 = Math.PI; +var DOUBLE_PI$1 = PI$1 * 2; +var HALF_PI$1 = PI$1 / 2; + +core_defaults._set('doughnut', { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: -HALF_PI$1, + + // The total circumference of the chart. + circumference: DOUBLE_PI$1, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers$1.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } +}); + +var controller_doughnut = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var ratioX = 1; + var ratioY = 1; + var offsetX = 0; + var offsetY = 0; + var meta = me.getMeta(); + var arcs = meta.data; + var cutout = opts.cutoutPercentage / 100 || 0; + var circumference = opts.circumference; + var chartWeight = me._getRingWeight(me.index); + var maxWidth, maxHeight, i, ilen; + + // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc + if (circumference < DOUBLE_PI$1) { + var startAngle = opts.rotation % DOUBLE_PI$1; + startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0; + var endAngle = startAngle + circumference; + var startX = Math.cos(startAngle); + var startY = Math.sin(startAngle); + var endX = Math.cos(endAngle); + var endY = Math.sin(endAngle); + var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1; + var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1; + var contains180 = startAngle === -PI$1 || endAngle >= PI$1; + var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1; + var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); + var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); + var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); + var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); + ratioX = (maxX - minX) / 2; + ratioY = (maxY - minY) / 2; + offsetX = -(maxX + minX) / 2; + offsetY = -(maxY + minY) / 2; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + } + + chart.borderWidth = me.getMaxBorderWidth(); + maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; + maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; + chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); + chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); + chart.offsetX = offsetX * chart.outerRadius; + chart.offsetY = offsetY * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + me.updateElement(arcs[i], i, reset); + } + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var animationOpts = opts.animation; + var centerX = (chartArea.left + chartArea.right) / 2; + var centerY = (chartArea.top + chartArea.bottom) / 2; + var startAngle = opts.rotation; // non reset case handled later + var endAngle = opts.rotation; // non reset case handled later + var dataset = me.getDataset(); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; + } + + model.endAngle = model.startAngle + model.circumference; + } + + arc.pivot(); + }, + + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers$1.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); + + /* if (total === 0) { + total = NaN; + }*/ + + return total; + }, + + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return DOUBLE_PI$1 * (Math.abs(value) / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(arcs) { + var me = this; + var max = 0; + var chart = me.chart; + var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; + + if (!arcs) { + // Find the outmost visible dataset + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + arcs = meta.data; + if (i !== me.index) { + controller = meta.controller; + } + break; + } + } + } + + if (!arcs) { + return 0; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arc = arcs[i]; + if (controller) { + controller._configure(); + options = controller._resolveDataElementOptions(arc, i); + } else { + options = arc._options; + } + if (options.borderAlign !== 'inner') { + borderWidth = options.borderWidth; + hoverWidth = options.hoverBorderWidth; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + } + return max; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly + * @private + */ + _getRingWeightOffset: function(datasetIndex) { + var ringWeightOffset = 0; + + for (var i = 0; i < datasetIndex; ++i) { + if (this.chart.isDatasetVisible(i)) { + ringWeightOffset += this._getRingWeight(i); + } + } + + return ringWeightOffset; + }, + + /** + * @private + */ + _getRingWeight: function(dataSetIndex) { + return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0); + }, + + /** + * Returns the sum of all visibile data set weights. This value can be 0. + * @private + */ + _getVisibleDatasetWeightTotal: function() { + return this._getRingWeightOffset(this.chart.data.datasets.length); + } +}); + +core_defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + type: 'category', + position: 'left', + offset: true, + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + + tooltips: { + mode: 'index', + axis: 'y' + } +}); + +core_defaults._set('global', { + datasets: { + horizontalBar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +var controller_horizontalBar = controller_bar.extend({ + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().yAxisID; + } +}); + +var valueOrDefault$6 = helpers$1.valueOrDefault; +var resolve$2 = helpers$1.options.resolve; +var isPointInArea = helpers$1.canvas._isPointInArea; + +core_defaults._set('line', { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } +}); + +function scaleClip(scale, halfBorderWidth) { + var tickOpts = scale && scale.options.ticks || {}; + var reverse = tickOpts.reverse; + var min = tickOpts.min === undefined ? halfBorderWidth : 0; + var max = tickOpts.max === undefined ? halfBorderWidth : 0; + return { + start: reverse ? max : min, + end: reverse ? min : max + }; +} + +function defaultClip(xScale, yScale, borderWidth) { + var halfBorderWidth = borderWidth / 2; + var x = scaleClip(xScale, halfBorderWidth); + var y = scaleClip(yScale, halfBorderWidth); + + return { + top: y.end, + right: x.end, + bottom: y.start, + left: x.start + }; +} + +function toClip(value) { + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = value.top; + r = value.right; + b = value.bottom; + l = value.left; + } else { + t = r = b = l = value; + } + + return { + top: t, + right: r, + bottom: b, + left: l + }; +} + + +var controller_line = core_datasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth', + 'cubicInterpolationMode', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var config = me._config; + var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines); + var i, ilen; + + me._xScale = me.getScaleForId(meta.xAxisID); + me._yScale = me.getScaleForId(meta.yAxisID); + + // Update Line + if (showLine) { + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = me._yScale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + } + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var xScale = me._xScale; + var yScale = me._yScale; + var lineModel = meta.dataset._model; + var x, y; + + var options = me._resolveDataElementOptions(point, index); + + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), + steppedLine: lineModel ? lineModel.steppedLine : false, + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element) { + var me = this; + var config = me._config; + var custom = element.custom || {}; + var options = me.chart.options; + var lineOptions = options.elements.line; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension); + values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]); + values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth))); + + return values; + }, + + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var yScale = me._yScale; + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen; + + if (yScale.options.stacked) { + rightValue = +yScale.getRightValue(value); + metasets = chart._getSortedVisibleDatasetMetas(); + ilen = metasets.length; + + for (i = 0; i < ilen; ++i) { + dsMeta = metasets[i]; + if (dsMeta.index === datasetIndex) { + break; + } + + ds = chart.data.datasets[dsMeta.index]; + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { + stackedRightValue = +yScale.getRightValue(ds.data[index]); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } + + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var lineModel = meta.dataset._model; + var area = chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (lineModel.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + if (lineModel.cubicInterpolationMode === 'monotone') { + helpers$1.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i)._model, + model, + helpers$1.nextItem(points, i)._model, + lineModel.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } + + if (chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + if (isPointInArea(model, area)) { + if (i > 0 && isPointInArea(points[i - 1]._model, area)) { + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + } + if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); + } + } + } + } + }, + + draw: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var points = meta.data || []; + var area = chart.chartArea; + var canvas = chart.canvas; + var i = 0; + var ilen = points.length; + var clip; + + if (me._showLine) { + clip = meta.dataset._model.clip; + + helpers$1.canvas.clipArea(chart.ctx, { + left: clip.left === false ? 0 : area.left - clip.left, + right: clip.right === false ? canvas.width : area.right + clip.right, + top: clip.top === false ? 0 : area.top - clip.top, + bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom + }); + + meta.dataset.draw(); + + helpers$1.canvas.unclipArea(chart.ctx); + } + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); + model.radius = valueOrDefault$6(options.hoverRadius, options.radius); + }, +}); + +var resolve$3 = helpers$1.options.resolve; + +core_defaults._set('polarArea', { + scale: { + type: 'radialLinear', + angleLines: { + display: false + }, + gridLines: { + circular: true + }, + pointLabels: { + display: false + }, + ticks: { + beginAtZero: true + } + }, + + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, + + startAngle: -0.5 * Math.PI, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(item, data) { + return data.labels[item.index] + ': ' + item.yLabel; + } + } + } +}); + +var controller_polarArea = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var dataset = me.getDataset(); + var meta = me.getMeta(); + var start = me.chart.options.startAngle || 0; + var starts = me._starts = []; + var angles = me._angles = []; + var arcs = meta.data; + var i, ilen, angle; + + me._updateRadius(); + + meta.count = me.countVisibleElements(); + + for (i = 0, ilen = dataset.data.length; i < ilen; i++) { + starts[i] = start; + angle = me._computeAngle(i); + angles[i] = angle; + start += angle; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + me.updateElement(arcs[i], i, reset); + } + }, + + /** + * @private + */ + _updateRadius: function() { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + + chart.outerRadius = Math.max(minSize / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var labels = chart.data.labels; + + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = me._starts[index]; + var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + arc.pivot(); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers$1.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + var valueOrDefault = helpers$1.valueOrDefault; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * @private + */ + _computeAngle: function(index) { + var me = this; + var count = this.getMeta().count; + var dataset = me.getDataset(); + var meta = me.getMeta(); + + if (isNaN(dataset.data[index]) || meta.data[index].hidden) { + return 0; + } + + // Scriptable options + var context = { + chart: me.chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + return resolve$3([ + me.chart.options.elements.arc.angle, + (2 * Math.PI) / count + ], context, index); + } +}); + +core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); +core_defaults._set('pie', { + cutoutPercentage: 0 +}); + +// Pie charts are Doughnut chart with different defaults +var controller_pie = controller_doughnut; + +var valueOrDefault$7 = helpers$1.valueOrDefault; + +core_defaults._set('radar', { + spanGaps: false, + scale: { + type: 'radialLinear' + }, + elements: { + line: { + fill: 'start', + tension: 0 // no bezier in radar + } + } +}); + +var controller_radar = core_datasetController.extend({ + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderWidth', + 'borderColor', + 'borderCapStyle', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var scale = me.chart.scale; + var config = me._config; + var i, ilen; + + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + line._loop = true; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + // Update bezier control points + me.updateBezierControlPoints(); + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + var options = me._resolveDataElementOptions(point, index); + var lineModel = me.getMeta().dataset._model; + var x = reset ? scale.xCenter : pointPosition.x; + var y = reset ? scale.yCenter : pointPosition.y; + + // Utility + point._scale = scale; + point._options = options; + point._datasetIndex = me.index; + point._index = index; + + // Desired view properties + point._model = { + x: x, // value not used in dataset scale, but we want a consistent API between scales + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0), + + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function() { + var me = this; + var config = me._config; + var options = me.chart.options; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension); + + return values; + }, + + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i, true)._model, + model, + helpers$1.nextItem(points, i, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); + model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); + model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); + model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + } + }, + + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth); + model.radius = valueOrDefault$7(options.hoverRadius, options.radius); + } +}); + +core_defaults._set('scatter', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + id: 'x-axis-1', // need an ID so datasets can reference the scale + type: 'linear', // scatter should not use a category axis + position: 'bottom' + }], + yAxes: [{ + id: 'y-axis-1', + type: 'linear', + position: 'left' + }] + }, + + tooltips: { + callbacks: { + title: function() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label: function(item) { + return '(' + item.xLabel + ', ' + item.yLabel + ')'; + } + } + } +}); + +core_defaults._set('global', { + datasets: { + scatter: { + showLine: false + } + } +}); + +// Scatter charts use line controllers +var controller_scatter = controller_line; + +// NOTE export a map in which the key represents the controller type, not +// the class, and so must be CamelCase in order to be correctly retrieved +// by the controller in core.controller.js (`controllers[meta.type]`). + +var controllers = { + bar: controller_bar, + bubble: controller_bubble, + doughnut: controller_doughnut, + horizontalBar: controller_horizontalBar, + line: controller_line, + polarArea: controller_polarArea, + pie: controller_pie, + radar: controller_radar, + scatter: controller_scatter +}; + +/** + * Helper function to get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {object} the event position + */ +function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers$1.getRelativePosition(e, chart); +} + +/** + * Helper function to traverse all of the visible elements in the chart + * @param {Chart} chart - the chart + * @param {function} handler - the callback to execute for each visible item + */ +function parseVisibleItems(chart, handler) { + var metasets = chart._getSortedVisibleDatasetMetas(); + var metadata, i, j, ilen, jlen, element; + + for (i = 0, ilen = metasets.length; i < ilen; ++i) { + metadata = metasets[i].data; + for (j = 0, jlen = metadata.length; j < jlen; ++j) { + element = metadata[j]; + if (!element._view.skip) { + handler(element); + } + } + } +} + +/** + * Helper function to get the items that intersect the event position + * @param {ChartElement[]} items - elements to filter + * @param {object} position - the point to be nearest to + * @return {ChartElement[]} the nearest items + */ +function getIntersectItems(chart, position) { + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; +} + +/** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param {Chart} chart - the chart to look at elements from + * @param {object} position - the point to be nearest to + * @param {boolean} intersect - if true, only consider items that intersect the position + * @param {function} distanceMetric - function to provide the distance between points + * @return {ChartElement[]} the nearest items + */ +function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; + + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } + + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + }); + + return nearestItems; +} + +/** + * Get a distance metric function for two points based on the + * axis mode setting + * @param {string} axis - the axis mode. x|y|xy + */ +function getDistanceMetricForAxis(axis) { + var useX = axis.indexOf('x') !== -1; + var useY = axis.indexOf('y') !== -1; + + return function(pt1, pt2) { + var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; +} + +function indexMode(chart, e, options) { + var position = getRelativePosition(e, chart); + // Default axis for index mode is 'x' to match old behaviour + options.axis = options.axis || 'x'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; + + if (!items.length) { + return []; + } + + chart._getSortedVisibleDatasetMetas().forEach(function(meta) { + var element = meta.data[items[0]._index]; + + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } + }); + + return elements; +} + +/** + * @interface IInteractionOptions + */ +/** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ + +/** + * Contains interaction related functions + * @namespace Chart.Interaction + */ +var core_interaction = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart); + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + * @todo remove at version 3 + * @private + */ + label: indexMode, + + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, + + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } + + return items; + }, + + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + * @todo remove at version 3 + * @private + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, {intersect: false}); + }, + + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = getRelativePosition(e, chart); + return getIntersectItems(chart, position); + }, + + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + return getNearestItems(chart, position, options.intersect, distanceMetric); + }, + + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + } + } +}; + +var extend = helpers$1.extend; + +function filterByPosition(array, position) { + return helpers$1.where(array, function(v) { + return v.pos === position; + }); +} + +function sortByWeight(array, reverse) { + return array.sort(function(a, b) { + var v0 = reverse ? b : a; + var v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0.index - v1.index : + v0.weight - v1.weight; + }); +} + +function wrapBoxes(boxes) { + var layoutBoxes = []; + var i, ilen, box; + + for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { + box = boxes[i]; + layoutBoxes.push({ + index: i, + box: box, + pos: box.position, + horizontal: box.isHorizontal(), + weight: box.weight + }); + } + return layoutBoxes; +} + +function setLayoutDims(layouts, params) { + var i, ilen, layout; + for (i = 0, ilen = layouts.length; i < ilen; ++i) { + layout = layouts[i]; + // store width used instead of chartArea.w in fitBoxes + layout.width = layout.horizontal + ? layout.box.fullWidth && params.availableWidth + : params.vBoxMaxWidth; + // store height used instead of chartArea.h in fitBoxes + layout.height = layout.horizontal && params.hBoxMaxHeight; + } +} + +function buildLayoutBoxes(boxes) { + var layoutBoxes = wrapBoxes(boxes); + var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); + var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); + var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); + var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + + return { + leftAndTop: left.concat(top), + rightAndBottom: right.concat(bottom), + chartArea: filterByPosition(layoutBoxes, 'chartArea'), + vertical: left.concat(right), + horizontal: top.concat(bottom) + }; +} + +function getCombinedMax(maxPadding, chartArea, a, b) { + return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); +} + +function updateDims(chartArea, params, layout) { + var box = layout.box; + var maxPadding = chartArea.maxPadding; + var newWidth, newHeight; + + if (layout.size) { + // this layout was already counted for, lets first reduce old size + chartArea[layout.pos] -= layout.size; + } + layout.size = layout.horizontal ? box.height : box.width; + chartArea[layout.pos] += layout.size; + + if (box.getPadding) { + var boxPadding = box.getPadding(); + maxPadding.top = Math.max(maxPadding.top, boxPadding.top); + maxPadding.left = Math.max(maxPadding.left, boxPadding.left); + maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); + maxPadding.right = Math.max(maxPadding.right, boxPadding.right); + } + + newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); + newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); + + if (newWidth !== chartArea.w || newHeight !== chartArea.h) { + chartArea.w = newWidth; + chartArea.h = newHeight; + + // return true if chart area changed in layout's direction + return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; + } +} + +function handleMaxPadding(chartArea) { + var maxPadding = chartArea.maxPadding; + + function updatePos(pos) { + var change = Math.max(maxPadding[pos] - chartArea[pos], 0); + chartArea[pos] += change; + return change; + } + chartArea.y += updatePos('top'); + chartArea.x += updatePos('left'); + updatePos('right'); + updatePos('bottom'); +} + +function getMargins(horizontal, chartArea) { + var maxPadding = chartArea.maxPadding; + + function marginForPositions(positions) { + var margin = {left: 0, top: 0, right: 0, bottom: 0}; + positions.forEach(function(pos) { + margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); + }); + return margin; + } + + return horizontal + ? marginForPositions(['left', 'right']) + : marginForPositions(['top', 'bottom']); +} + +function fitBoxes(boxes, chartArea, params) { + var refitBoxes = []; + var i, ilen, layout, box, refit, changed; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + + box.update( + layout.width || chartArea.w, + layout.height || chartArea.h, + getMargins(layout.horizontal, chartArea) + ); + if (updateDims(chartArea, params, layout)) { + changed = true; + if (refitBoxes.length) { + // Dimensions changed and there were non full width boxes before this + // -> we have to refit those + refit = true; + } + } + if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case + refitBoxes.push(layout); + } + } + + return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; +} + +function placeBoxes(boxes, chartArea, params) { + var userPadding = params.padding; + var x = chartArea.x; + var y = chartArea.y; + var i, ilen, layout, box; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + if (layout.horizontal) { + box.left = box.fullWidth ? userPadding.left : chartArea.left; + box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; + box.top = y; + box.bottom = y + box.height; + box.width = box.right - box.left; + y = box.bottom; + } else { + box.left = x; + box.right = x + box.width; + box.top = chartArea.top; + box.bottom = chartArea.top + chartArea.h; + box.height = box.bottom - box.top; + x = box.right; + } + } + + chartArea.x = x; + chartArea.y = y; +} + +core_defaults._set('global', { + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } +}); + +/** + * @interface ILayoutItem + * @prop {string} position - The position of the item in the chart layout. Possible values are + * 'left', 'top', 'right', 'bottom', and 'chartArea' + * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area + * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down + * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) + * @prop {function} update - Takes two parameters: width and height. Returns size of item + * @prop {function} getPadding - Returns an object with padding on the edges + * @prop {number} width - Width of item. Must be valid after update() + * @prop {number} height - Height of item. Must be valid after update() + * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update + * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update + * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update + * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update + */ + +// The layout service is very self explanatory. It's responsible for the layout within a chart. +// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need +// It is this service's responsibility of carrying out that layout. +var core_layouts = { + defaults: {}, + + /** + * Register a box to a chart. + * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. + * @param {Chart} chart - the chart to use + * @param {ILayoutItem} item - the item to add to be layed out + */ + addBox: function(chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + + // initialize item with default values + item.fullWidth = item.fullWidth || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + item._layers = item._layers || function() { + return [{ + z: 0, + draw: function() { + item.draw.apply(item, arguments); + } + }]; + }; + + chart.boxes.push(item); + }, + + /** + * Remove a layoutItem from a chart + * @param {Chart} chart - the chart to remove the box from + * @param {ILayoutItem} layoutItem - the item to remove from the layout + */ + removeBox: function(chart, layoutItem) { + var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + + /** + * Sets (or updates) options on the given `item`. + * @param {Chart} chart - the chart in which the item lives (or will be added to) + * @param {ILayoutItem} item - the item to configure with the given options + * @param {object} options - the new item options. + */ + configure: function(chart, item, options) { + var props = ['fullWidth', 'position', 'weight']; + var ilen = props.length; + var i = 0; + var prop; + + for (; i < ilen; ++i) { + prop = props[i]; + if (options.hasOwnProperty(prop)) { + item[prop] = options[prop]; + } + } + }, + + /** + * Fits boxes of the given chart into the given size by having each box measure itself + * then running a fitting algorithm + * @param {Chart} chart - the chart + * @param {number} width - the width to fit into + * @param {number} height - the height to fit into + */ + update: function(chart, width, height) { + if (!chart) { + return; + } + + var layoutOptions = chart.options.layout || {}; + var padding = helpers$1.options.toPadding(layoutOptions.padding); + + var availableWidth = width - padding.width; + var availableHeight = height - padding.height; + var boxes = buildLayoutBoxes(chart.boxes); + var verticalBoxes = boxes.vertical; + var horizontalBoxes = boxes.horizontal; + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + + var params = Object.freeze({ + outerWidth: width, + outerHeight: height, + padding: padding, + availableWidth: availableWidth, + vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, + hBoxMaxHeight: availableHeight / 2 + }); + var chartArea = extend({ + maxPadding: extend({}, padding), + w: availableWidth, + h: availableHeight, + x: padding.left, + y: padding.top + }, padding); + + setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + + // First fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + + // Then fit horizontal boxes + if (fitBoxes(horizontalBoxes, chartArea, params)) { + // if the area changed, re-fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + } + + handleMaxPadding(chartArea); + + // Finally place the boxes to correct coordinates + placeBoxes(boxes.leftAndTop, chartArea, params); + + // Move to opposite side of chart + chartArea.x += chartArea.w; + chartArea.y += chartArea.h; + + placeBoxes(boxes.rightAndBottom, chartArea, params); + + chart.chartArea = { + left: chartArea.left, + top: chartArea.top, + right: chartArea.left + chartArea.w, + bottom: chartArea.top + chartArea.h + }; + + // Finally update boxes in chartArea (radial scale for example) + helpers$1.each(boxes.chartArea, function(layout) { + var box = layout.box; + extend(box, chart.chartArea); + box.update(chartArea.w, chartArea.h); + }); + } +}; + +/** + * Platform fallback implementation (minimal). + * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 + */ + +var platform_basic = { + acquireContext: function(item) { + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + return item && item.getContext('2d') || null; + } +}; + +var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n"; + +var platform_dom$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +'default': platform_dom +}); + +var stylesheet = getCjsExportFromNamespace(platform_dom$1); + +var EXPANDO_KEY = '$chartjs'; +var CSS_PREFIX = 'chartjs-'; +var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; +var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; +var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; +var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; + +/** + * DOM event types -> Chart.js event types. + * Note: only events with different types are mapped. + * @see https://developer.mozilla.org/en-US/docs/Web/Events + */ +var EVENT_TYPES = { + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {number} Size in pixels or undefined if unknown. + */ +function readUsedSize(element, property) { + var value = helpers$1.getStyle(element, property); + var matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? Number(matches[1]) : undefined; +} + +/** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + */ +function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; +} + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +var supportsEventListenerOptions = (function() { + var supports = false; + try { + var options = Object.defineProperty({}, 'passive', { + // eslint-disable-next-line getter-return + get: function() { + supports = true; + } + }); + window.addEventListener('e', null, options); + } catch (e) { + // continue regardless of error + } + return supports; +}()); + +// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. +// https://github.com/chartjs/Chart.js/issues/4287 +var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; + +function addListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} + +function removeListener(node, type, listener) { + node.removeEventListener(type, listener, eventListenerOptions); +} + +function createEvent(type, chart, x, y, nativeEvent) { + return { + type: type, + chart: chart, + native: nativeEvent || null, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null, + }; +} + +function fromNativeEvent(event, chart) { + var type = EVENT_TYPES[event.type] || event.type; + var pos = helpers$1.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); +} + +function throttled(fn, thisArg) { + var ticking = false; + var args = []; + + return function() { + args = Array.prototype.slice.call(arguments); + thisArg = thisArg || this; + + if (!ticking) { + ticking = true; + helpers$1.requestAnimFrame.call(window, function() { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; +} + +function createDiv(cls) { + var el = document.createElement('div'); + el.className = cls || ''; + return el; +} + +// Implementation based on https://github.com/marcj/css-element-queries +function createResizer(handler) { + var maxSize = 1000000; + + // NOTE(SB) Don't use innerHTML because it could be considered unsafe. + // https://github.com/chartjs/Chart.js/issues/5902 + var resizer = createDiv(CSS_SIZE_MONITOR); + var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); + var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); + + expand.appendChild(createDiv()); + shrink.appendChild(createDiv()); + + resizer.appendChild(expand); + resizer.appendChild(shrink); + resizer._reset = function() { + expand.scrollLeft = maxSize; + expand.scrollTop = maxSize; + shrink.scrollLeft = maxSize; + shrink.scrollTop = maxSize; + }; + + var onScroll = function() { + resizer._reset(); + handler(); + }; + + addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); + + return resizer; +} + +// https://davidwalsh.name/detect-node-insertion +function watchForRender(node, handler) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + var proxy = expando.renderProxy = function(e) { + if (e.animationName === CSS_RENDER_ANIMATION) { + handler(); + } + }; + + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + addListener(node, type, proxy); + }); + + // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class + // is removed then added back immediately (same animation frame?). Accessing the + // `offsetParent` property will force a reflow and re-evaluate the CSS animation. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics + // https://github.com/chartjs/Chart.js/issues/4737 + expando.reflow = !!node.offsetParent; + + node.classList.add(CSS_RENDER_MONITOR); +} + +function unwatchForRender(node) { + var expando = node[EXPANDO_KEY] || {}; + var proxy = expando.renderProxy; + + if (proxy) { + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + removeListener(node, type, proxy); + }); + + delete expando.renderProxy; + } + + node.classList.remove(CSS_RENDER_MONITOR); +} + +function addResizeListener(node, listener, chart) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + + // Let's keep track of this added resizer and thus avoid DOM query when removing it. + var resizer = expando.resizer = createResizer(throttled(function() { + if (expando.resizer) { + var container = chart.options.maintainAspectRatio && node.parentNode; + var w = container ? container.clientWidth : 0; + listener(createEvent('resize', chart)); + if (container && container.clientWidth < w && chart.canvas) { + // If the container size shrank during chart resize, let's assume + // scrollbar appeared. So we resize again with the scrollbar visible - + // effectively making chart smaller and the scrollbar hidden again. + // Because we are inside `throttled`, and currently `ticking`, scroll + // events are ignored during this whole 2 resize process. + // If we assumed wrong and something else happened, we are resizing + // twice in a frame (potential performance issue) + listener(createEvent('resize', chart)); + } + } + })); + + // The resizer needs to be attached to the node parent, so we first need to be + // sure that `node` is attached to the DOM before injecting the resizer element. + watchForRender(node, function() { + if (expando.resizer) { + var container = node.parentNode; + if (container && container !== resizer.parentNode) { + container.insertBefore(resizer, container.firstChild); + } + + // The container size might have changed, let's reset the resizer state. + resizer._reset(); + } + }); +} + +function removeResizeListener(node) { + var expando = node[EXPANDO_KEY] || {}; + var resizer = expando.resizer; + + delete expando.resizer; + unwatchForRender(node); + + if (resizer && resizer.parentNode) { + resizer.parentNode.removeChild(resizer); + } +} + +/** + * Injects CSS styles inline if the styles are not already present. + * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>. + * @param {string} css - the CSS to be injected. + */ +function injectCSS(rootNode, css) { + // https://stackoverflow.com/q/3922139 + var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {}); + if (!expando.containsStyles) { + expando.containsStyles = true; + css = '/* Chart.js */\n' + css; + var style = document.createElement('style'); + style.setAttribute('type', 'text/css'); + style.appendChild(document.createTextNode(css)); + rootNode.appendChild(style); + } +} + +var platform_dom$2 = { + /** + * When `true`, prevents the automatic injection of the stylesheet required to + * correctly detect when the chart is added to the DOM and then resized. This + * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`) + * to be manually imported to make this library compatible with any CSP. + * See https://github.com/chartjs/Chart.js/issues/5208 + */ + disableCSSInjection: false, + + /** + * This property holds whether this platform is enabled for the current environment. + * Currently used by platform.js to select the proper implementation. + * @private + */ + _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', + + /** + * Initializes resources that depend on platform options. + * @param {HTMLCanvasElement} canvas - The Canvas element. + * @private + */ + _ensureLoaded: function(canvas) { + if (!this.disableCSSInjection) { + // If the canvas is in a shadow DOM, then the styles must also be inserted + // into the same shadow DOM. + // https://github.com/chartjs/Chart.js/issues/5763 + var root = canvas.getRootNode ? canvas.getRootNode() : document; + var targetNode = root.host ? root : document.head; + injectCSS(targetNode, stylesheet); + } + }, + + acquireContext: function(item, config) { + if (typeof item === 'string') { + item = document.getElementById(item); + } else if (item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } + + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + var context = item && item.getContext && item.getContext('2d'); + + // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is + // inside an iframe or when running in a protected environment. We could guess the + // types from their toString() value but let's keep things flexible and assume it's + // a sufficient condition if the item has a context2D which has item as `canvas`. + // https://github.com/chartjs/Chart.js/issues/3887 + // https://github.com/chartjs/Chart.js/issues/4102 + // https://github.com/chartjs/Chart.js/issues/4152 + if (context && context.canvas === item) { + // Load platform resources on first chart creation, to make it possible to + // import the library before setting platform options. + this._ensureLoaded(item); + initCanvas(item, config); + return context; + } + + return null; + }, + + releaseContext: function(context) { + var canvas = context.canvas; + if (!canvas[EXPANDO_KEY]) { + return; + } + + var initial = canvas[EXPANDO_KEY].initial; + ['height', 'width'].forEach(function(prop) { + var value = initial[prop]; + if (helpers$1.isNullOrUndef(value)) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + + helpers$1.each(initial.style || {}, function(value, key) { + canvas.style[key] = value; + }); + + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + // eslint-disable-next-line no-self-assign + canvas.width = canvas.width; + + delete canvas[EXPANDO_KEY]; + }, + + addEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + addResizeListener(canvas, listener, chart); + return; + } + + var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); + var proxies = expando.proxies || (expando.proxies = {}); + var proxy = proxies[chart.id + '_' + type] = function(event) { + listener(fromNativeEvent(event, chart)); + }; + + addListener(canvas, type, proxy); + }, + + removeEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + removeResizeListener(canvas); + return; + } + + var expando = listener[EXPANDO_KEY] || {}; + var proxies = expando.proxies || {}; + var proxy = proxies[chart.id + '_' + type]; + if (!proxy) { + return; + } + + removeListener(canvas, type, proxy); + } +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use EventTarget.addEventListener instead. + * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + * @function Chart.helpers.addEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers$1.addEvent = addListener; + +/** + * Provided for backward compatibility, use EventTarget.removeEventListener instead. + * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener + * @function Chart.helpers.removeEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers$1.removeEvent = removeListener; + +// @TODO Make possible to select another platform at build time. +var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic; + +/** + * @namespace Chart.platform + * @see https://chartjs.gitbooks.io/proposals/content/Platform.html + * @since 2.4.0 + */ +var platform = helpers$1.extend({ + /** + * @since 2.7.0 + */ + initialize: function() {}, + + /** + * Called at chart construction time, returns a context2d instance implementing + * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. + * @param {*} item - The native item from which to acquire context (platform specific) + * @param {object} options - The chart options + * @returns {CanvasRenderingContext2D} context2d instance + */ + acquireContext: function() {}, + + /** + * Called at chart destruction time, releases any resources associated to the context + * previously returned by the acquireContext() method. + * @param {CanvasRenderingContext2D} context - The context2d instance + * @returns {boolean} true if the method succeeded, else false + */ + releaseContext: function() {}, + + /** + * Registers the specified listener on the given chart. + * @param {Chart} chart - Chart from which to listen for event + * @param {string} type - The ({@link IEvent}) type to listen for + * @param {function} listener - Receives a notification (an object that implements + * the {@link IEvent} interface) when an event of the specified type occurs. + */ + addEventListener: function() {}, + + /** + * Removes the specified listener previously registered with addEventListener. + * @param {Chart} chart - Chart from which to remove the listener + * @param {string} type - The ({@link IEvent}) type to remove + * @param {function} listener - The listener function to remove from the event target. + */ + removeEventListener: function() {} + +}, implementation); + +core_defaults._set('global', { + plugins: {} +}); + +/** + * The plugin service singleton + * @namespace Chart.plugins + * @since 2.1.0 + */ +var core_plugins = { + /** + * Globally registered plugins. + * @private + */ + _plugins: [], + + /** + * This identifier is used to invalidate the descriptors cache attached to each chart + * when a global plugin is registered or unregistered. In this case, the cache ID is + * incremented and descriptors are regenerated during following API calls. + * @private + */ + _cacheId: 0, + + /** + * Registers the given plugin(s) if not already registered. + * @param {IPlugin[]|IPlugin} plugins plugin instance(s). + */ + register: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + if (p.indexOf(plugin) === -1) { + p.push(plugin); + } + }); + + this._cacheId++; + }, + + /** + * Unregisters the given plugin(s) only if registered. + * @param {IPlugin[]|IPlugin} plugins plugin instance(s). + */ + unregister: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + var idx = p.indexOf(plugin); + if (idx !== -1) { + p.splice(idx, 1); + } + }); + + this._cacheId++; + }, + + /** + * Remove all registered plugins. + * @since 2.1.5 + */ + clear: function() { + this._plugins = []; + this._cacheId++; + }, + + /** + * Returns the number of registered plugins? + * @returns {number} + * @since 2.1.5 + */ + count: function() { + return this._plugins.length; + }, + + /** + * Returns all registered plugin instances. + * @returns {IPlugin[]} array of plugin objects. + * @since 2.1.5 + */ + getAll: function() { + return this._plugins; + }, + + /** + * Calls enabled plugins for `chart` on the specified hook and with the given args. + * This method immediately returns as soon as a plugin explicitly returns false. The + * returned value can be used, for instance, to interrupt the current action. + * @param {Chart} chart - The chart instance for which plugins should be called. + * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). + * @param {Array} [args] - Extra arguments to apply to the hook call. + * @returns {boolean} false if any of the plugins return false, else returns true. + */ + notify: function(chart, hook, args) { + var descriptors = this.descriptors(chart); + var ilen = descriptors.length; + var i, descriptor, plugin, params, method; + + for (i = 0; i < ilen; ++i) { + descriptor = descriptors[i]; + plugin = descriptor.plugin; + method = plugin[hook]; + if (typeof method === 'function') { + params = [chart].concat(args || []); + params.push(descriptor.options); + if (method.apply(plugin, params) === false) { + return false; + } + } + } + + return true; + }, + + /** + * Returns descriptors of enabled plugins for the given chart. + * @returns {object[]} [{ plugin, options }] + * @private + */ + descriptors: function(chart) { + var cache = chart.$plugins || (chart.$plugins = {}); + if (cache.id === this._cacheId) { + return cache.descriptors; + } + + var plugins = []; + var descriptors = []; + var config = (chart && chart.config) || {}; + var options = (config.options && config.options.plugins) || {}; + + this._plugins.concat(config.plugins || []).forEach(function(plugin) { + var idx = plugins.indexOf(plugin); + if (idx !== -1) { + return; + } + + var id = plugin.id; + var opts = options[id]; + if (opts === false) { + return; + } + + if (opts === true) { + opts = helpers$1.clone(core_defaults.global.plugins[id]); + } + + plugins.push(plugin); + descriptors.push({ + plugin: plugin, + options: opts || {} + }); + }); + + cache.descriptors = descriptors; + cache.id = this._cacheId; + return descriptors; + }, + + /** + * Invalidates cache for the given chart: descriptors hold a reference on plugin option, + * but in some cases, this reference can be changed by the user when updating options. + * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + * @private + */ + _invalidate: function(chart) { + delete chart.$plugins; + } +}; + +var core_scaleService = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers + + // Scale config defaults + defaults: {}, + registerScaleType: function(type, scaleConstructor, scaleDefaults) { + this.constructors[type] = scaleConstructor; + this.defaults[type] = helpers$1.clone(scaleDefaults); + }, + getScaleConstructor: function(type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, + getScaleDefaults: function(type) { + // Return the scale defaults merged with the global settings so that we always use the latest ones + return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {}; + }, + updateScaleDefaults: function(type, additions) { + var me = this; + if (me.defaults.hasOwnProperty(type)) { + me.defaults[type] = helpers$1.extend(me.defaults[type], additions); + } + }, + addScalesToLayout: function(chart) { + // Adds each scale to the chart.boxes array to be sized accordingly + helpers$1.each(chart.scales, function(scale) { + // Set ILayoutItem parameters for backwards compatibility + scale.fullWidth = scale.options.fullWidth; + scale.position = scale.options.position; + scale.weight = scale.options.weight; + core_layouts.addBox(chart, scale); + }); + } +}; + +var valueOrDefault$8 = helpers$1.valueOrDefault; +var getRtlHelper = helpers$1.rtl.getRtlAdapter; + +core_defaults._set('global', { + tooltips: { + enabled: true, + custom: null, + mode: 'nearest', + position: 'average', + intersect: true, + backgroundColor: 'rgba(0,0,0,0.8)', + titleFontStyle: 'bold', + titleSpacing: 2, + titleMarginBottom: 6, + titleFontColor: '#fff', + titleAlign: 'left', + bodySpacing: 2, + bodyFontColor: '#fff', + bodyAlign: 'left', + footerFontStyle: 'bold', + footerSpacing: 2, + footerMarginTop: 6, + footerFontColor: '#fff', + footerAlign: 'left', + yPadding: 6, + xPadding: 6, + caretPadding: 2, + caretSize: 5, + cornerRadius: 6, + multiKeyBackground: '#fff', + displayColors: true, + borderColor: 'rgba(0,0,0,0)', + borderWidth: 0, + callbacks: { + // Args are: (tooltipItems, data) + beforeTitle: helpers$1.noop, + title: function(tooltipItems, data) { + var title = ''; + var labels = data.labels; + var labelCount = labels ? labels.length : 0; + + if (tooltipItems.length > 0) { + var item = tooltipItems[0]; + if (item.label) { + title = item.label; + } else if (item.xLabel) { + title = item.xLabel; + } else if (labelCount > 0 && item.index < labelCount) { + title = labels[item.index]; + } + } + + return title; + }, + afterTitle: helpers$1.noop, + + // Args are: (tooltipItems, data) + beforeBody: helpers$1.noop, + + // Args are: (tooltipItem, data) + beforeLabel: helpers$1.noop, + label: function(tooltipItem, data) { + var label = data.datasets[tooltipItem.datasetIndex].label || ''; + + if (label) { + label += ': '; + } + if (!helpers$1.isNullOrUndef(tooltipItem.value)) { + label += tooltipItem.value; + } else { + label += tooltipItem.yLabel; + } + return label; + }, + labelColor: function(tooltipItem, chart) { + var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); + var activeElement = meta.data[tooltipItem.index]; + var view = activeElement._view; + return { + borderColor: view.borderColor, + backgroundColor: view.backgroundColor + }; + }, + labelTextColor: function() { + return this._options.bodyFontColor; + }, + afterLabel: helpers$1.noop, + + // Args are: (tooltipItems, data) + afterBody: helpers$1.noop, + + // Args are: (tooltipItems, data) + beforeFooter: helpers$1.noop, + footer: helpers$1.noop, + afterFooter: helpers$1.noop + } + } +}); + +var positioners = { + /** + * Average mode places the tooltip at the average position of the elements shown + * @function Chart.Tooltip.positioners.average + * @param elements {ChartElement[]} the elements being displayed in the tooltip + * @returns {object} tooltip position + */ + average: function(elements) { + if (!elements.length) { + return false; + } + + var i, len; + var x = 0; + var y = 0; + var count = 0; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var pos = el.tooltipPosition(); + x += pos.x; + y += pos.y; + ++count; + } + } + + return { + x: x / count, + y: y / count + }; + }, + + /** + * Gets the tooltip position nearest of the item nearest to the event position + * @function Chart.Tooltip.positioners.nearest + * @param elements {Chart.Element[]} the tooltip elements + * @param eventPosition {object} the position of the event in canvas coordinates + * @returns {object} the tooltip position + */ + nearest: function(elements, eventPosition) { + var x = eventPosition.x; + var y = eventPosition.y; + var minDistance = Number.POSITIVE_INFINITY; + var i, len, nearestElement; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var center = el.getCenterPoint(); + var d = helpers$1.distanceBetweenPoints(eventPosition, center); + + if (d < minDistance) { + minDistance = d; + nearestElement = el; + } + } + } + + if (nearestElement) { + var tp = nearestElement.tooltipPosition(); + x = tp.x; + y = tp.y; + } + + return { + x: x, + y: y + }; + } +}; + +// Helper to push or concat based on if the 2nd parameter is an array or not +function pushOrConcat(base, toPush) { + if (toPush) { + if (helpers$1.isArray(toPush)) { + // base = base.concat(toPush); + Array.prototype.push.apply(base, toPush); + } else { + base.push(toPush); + } + } + + return base; +} + +/** + * Returns array of strings split by newline + * @param {string} value - The value to split by newline. + * @returns {string[]} value if newline present - Returned from String split() method + * @function + */ +function splitNewlines(str) { + if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { + return str.split('\n'); + } + return str; +} + + +/** + * Private helper to create a tooltip item model + * @param element - the chart element (point, arc, bar) to create the tooltip item for + * @return new tooltip item + */ +function createTooltipItem(element) { + var xScale = element._xScale; + var yScale = element._yScale || element._scale; // handle radar || polarArea charts + var index = element._index; + var datasetIndex = element._datasetIndex; + var controller = element._chart.getDatasetMeta(datasetIndex).controller; + var indexScale = controller._getIndexScale(); + var valueScale = controller._getValueScale(); + + return { + xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', + label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '', + value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '', + index: index, + datasetIndex: datasetIndex, + x: element._model.x, + y: element._model.y + }; +} + +/** + * Helper to get the reset model for the tooltip + * @param tooltipOpts {object} the tooltip options + */ +function getBaseModel(tooltipOpts) { + var globalDefaults = core_defaults.global; + + return { + // Positioning + xPadding: tooltipOpts.xPadding, + yPadding: tooltipOpts.yPadding, + xAlign: tooltipOpts.xAlign, + yAlign: tooltipOpts.yAlign, + + // Drawing direction and text direction + rtl: tooltipOpts.rtl, + textDirection: tooltipOpts.textDirection, + + // Body + bodyFontColor: tooltipOpts.bodyFontColor, + _bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), + _bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), + _bodyAlign: tooltipOpts.bodyAlign, + bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), + bodySpacing: tooltipOpts.bodySpacing, + + // Title + titleFontColor: tooltipOpts.titleFontColor, + _titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), + _titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), + titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), + _titleAlign: tooltipOpts.titleAlign, + titleSpacing: tooltipOpts.titleSpacing, + titleMarginBottom: tooltipOpts.titleMarginBottom, + + // Footer + footerFontColor: tooltipOpts.footerFontColor, + _footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), + _footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), + footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), + _footerAlign: tooltipOpts.footerAlign, + footerSpacing: tooltipOpts.footerSpacing, + footerMarginTop: tooltipOpts.footerMarginTop, + + // Appearance + caretSize: tooltipOpts.caretSize, + cornerRadius: tooltipOpts.cornerRadius, + backgroundColor: tooltipOpts.backgroundColor, + opacity: 0, + legendColorBackground: tooltipOpts.multiKeyBackground, + displayColors: tooltipOpts.displayColors, + borderColor: tooltipOpts.borderColor, + borderWidth: tooltipOpts.borderWidth + }; +} + +/** + * Get the size of the tooltip + */ +function getTooltipSize(tooltip, model) { + var ctx = tooltip._chart.ctx; + + var height = model.yPadding * 2; // Tooltip Padding + var width = 0; + + // Count of all lines in the body + var body = model.body; + var combinedBodyLength = body.reduce(function(count, bodyItem) { + return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; + }, 0); + combinedBodyLength += model.beforeBody.length + model.afterBody.length; + + var titleLineCount = model.title.length; + var footerLineCount = model.footer.length; + var titleFontSize = model.titleFontSize; + var bodyFontSize = model.bodyFontSize; + var footerFontSize = model.footerFontSize; + + height += titleLineCount * titleFontSize; // Title Lines + height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing + height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin + height += combinedBodyLength * bodyFontSize; // Body Lines + height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing + height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin + height += footerLineCount * (footerFontSize); // Footer Lines + height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing + + // Title width + var widthPadding = 0; + var maxLineWidth = function(line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; + + ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); + helpers$1.each(model.title, maxLineWidth); + + // Body width + ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); + helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth); + + // Body lines may include some extra width due to the color box + widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; + helpers$1.each(body, function(bodyItem) { + helpers$1.each(bodyItem.before, maxLineWidth); + helpers$1.each(bodyItem.lines, maxLineWidth); + helpers$1.each(bodyItem.after, maxLineWidth); + }); + + // Reset back to 0 + widthPadding = 0; + + // Footer width + ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); + helpers$1.each(model.footer, maxLineWidth); + + // Add padding + width += 2 * model.xPadding; + + return { + width: width, + height: height + }; +} + +/** + * Helper to get the alignment of a tooltip given the size + */ +function determineAlignment(tooltip, size) { + var model = tooltip._model; + var chart = tooltip._chart; + var chartArea = tooltip._chart.chartArea; + var xAlign = 'center'; + var yAlign = 'center'; + + if (model.y < size.height) { + yAlign = 'top'; + } else if (model.y > (chart.height - size.height)) { + yAlign = 'bottom'; + } + + var lf, rf; // functions to determine left, right alignment + var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart + var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges + var midX = (chartArea.left + chartArea.right) / 2; + var midY = (chartArea.top + chartArea.bottom) / 2; + + if (yAlign === 'center') { + lf = function(x) { + return x <= midX; + }; + rf = function(x) { + return x > midX; + }; + } else { + lf = function(x) { + return x <= (size.width / 2); + }; + rf = function(x) { + return x >= (chart.width - (size.width / 2)); + }; + } + + olf = function(x) { + return x + size.width + model.caretSize + model.caretPadding > chart.width; + }; + orf = function(x) { + return x - size.width - model.caretSize - model.caretPadding < 0; + }; + yf = function(y) { + return y <= midY ? 'top' : 'bottom'; + }; + + if (lf(model.x)) { + xAlign = 'left'; + + // Is tooltip too wide and goes over the right side of the chart.? + if (olf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } else if (rf(model.x)) { + xAlign = 'right'; + + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } + + var opts = tooltip._options; + return { + xAlign: opts.xAlign ? opts.xAlign : xAlign, + yAlign: opts.yAlign ? opts.yAlign : yAlign + }; +} + +/** + * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment + */ +function getBackgroundPoint(vm, size, alignment, chart) { + // Background Position + var x = vm.x; + var y = vm.y; + + var caretSize = vm.caretSize; + var caretPadding = vm.caretPadding; + var cornerRadius = vm.cornerRadius; + var xAlign = alignment.xAlign; + var yAlign = alignment.yAlign; + var paddingAndSize = caretSize + caretPadding; + var radiusAndPadding = cornerRadius + caretPadding; + + if (xAlign === 'right') { + x -= size.width; + } else if (xAlign === 'center') { + x -= (size.width / 2); + if (x + size.width > chart.width) { + x = chart.width - size.width; + } + if (x < 0) { + x = 0; + } + } + + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= size.height + paddingAndSize; + } else { + y -= (size.height / 2); + } + + if (yAlign === 'center') { + if (xAlign === 'left') { + x += paddingAndSize; + } else if (xAlign === 'right') { + x -= paddingAndSize; + } + } else if (xAlign === 'left') { + x -= radiusAndPadding; + } else if (xAlign === 'right') { + x += radiusAndPadding; + } + + return { + x: x, + y: y + }; +} + +function getAlignedX(vm, align) { + return align === 'center' + ? vm.x + vm.width / 2 + : align === 'right' + ? vm.x + vm.width - vm.xPadding + : vm.x + vm.xPadding; +} + +/** + * Helper to build before and after body lines + */ +function getBeforeAfterBodyLines(callback) { + return pushOrConcat([], splitNewlines(callback)); +} + +var exports$4 = core_element.extend({ + initialize: function() { + this._model = getBaseModel(this._options); + this._lastActive = []; + }, + + // Get the title + // Args are: (tooltipItem, data) + getTitle: function() { + var me = this; + var opts = me._options; + var callbacks = opts.callbacks; + + var beforeTitle = callbacks.beforeTitle.apply(me, arguments); + var title = callbacks.title.apply(me, arguments); + var afterTitle = callbacks.afterTitle.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeTitle)); + lines = pushOrConcat(lines, splitNewlines(title)); + lines = pushOrConcat(lines, splitNewlines(afterTitle)); + + return lines; + }, + + // Args are: (tooltipItem, data) + getBeforeBody: function() { + return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); + }, + + // Args are: (tooltipItem, data) + getBody: function(tooltipItems, data) { + var me = this; + var callbacks = me._options.callbacks; + var bodyItems = []; + + helpers$1.each(tooltipItems, function(tooltipItem) { + var bodyItem = { + before: [], + lines: [], + after: [] + }; + pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); + pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); + + bodyItems.push(bodyItem); + }); + + return bodyItems; + }, + + // Args are: (tooltipItem, data) + getAfterBody: function() { + return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); + }, + + // Get the footer and beforeFooter and afterFooter lines + // Args are: (tooltipItem, data) + getFooter: function() { + var me = this; + var callbacks = me._options.callbacks; + + var beforeFooter = callbacks.beforeFooter.apply(me, arguments); + var footer = callbacks.footer.apply(me, arguments); + var afterFooter = callbacks.afterFooter.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeFooter)); + lines = pushOrConcat(lines, splitNewlines(footer)); + lines = pushOrConcat(lines, splitNewlines(afterFooter)); + + return lines; + }, + + update: function(changed) { + var me = this; + var opts = me._options; + + // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition + // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time + // which breaks any animations. + var existingModel = me._model; + var model = me._model = getBaseModel(opts); + var active = me._active; + + var data = me._data; + + // In the case where active.length === 0 we need to keep these at existing values for good animations + var alignment = { + xAlign: existingModel.xAlign, + yAlign: existingModel.yAlign + }; + var backgroundPoint = { + x: existingModel.x, + y: existingModel.y + }; + var tooltipSize = { + width: existingModel.width, + height: existingModel.height + }; + var tooltipPosition = { + x: existingModel.caretX, + y: existingModel.caretY + }; + + var i, len; + + if (active.length) { + model.opacity = 1; + + var labelColors = []; + var labelTextColors = []; + tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); + + var tooltipItems = []; + for (i = 0, len = active.length; i < len; ++i) { + tooltipItems.push(createTooltipItem(active[i])); + } + + // If the user provided a filter function, use it to modify the tooltip items + if (opts.filter) { + tooltipItems = tooltipItems.filter(function(a) { + return opts.filter(a, data); + }); + } + + // If the user provided a sorting function, use it to modify the tooltip items + if (opts.itemSort) { + tooltipItems = tooltipItems.sort(function(a, b) { + return opts.itemSort(a, b, data); + }); + } + + // Determine colors for boxes + helpers$1.each(tooltipItems, function(tooltipItem) { + labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); + labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); + }); + + + // Build the Text Lines + model.title = me.getTitle(tooltipItems, data); + model.beforeBody = me.getBeforeBody(tooltipItems, data); + model.body = me.getBody(tooltipItems, data); + model.afterBody = me.getAfterBody(tooltipItems, data); + model.footer = me.getFooter(tooltipItems, data); + + // Initial positioning and colors + model.x = tooltipPosition.x; + model.y = tooltipPosition.y; + model.caretPadding = opts.caretPadding; + model.labelColors = labelColors; + model.labelTextColors = labelTextColors; + + // data points + model.dataPoints = tooltipItems; + + // We need to determine alignment of the tooltip + tooltipSize = getTooltipSize(this, model); + alignment = determineAlignment(this, tooltipSize); + // Final Size and Position + backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); + } else { + model.opacity = 0; + } + + model.xAlign = alignment.xAlign; + model.yAlign = alignment.yAlign; + model.x = backgroundPoint.x; + model.y = backgroundPoint.y; + model.width = tooltipSize.width; + model.height = tooltipSize.height; + + // Point where the caret on the tooltip points to + model.caretX = tooltipPosition.x; + model.caretY = tooltipPosition.y; + + me._model = model; + + if (changed && opts.custom) { + opts.custom.call(me, model); + } + + return me; + }, + + drawCaret: function(tooltipPoint, size) { + var ctx = this._chart.ctx; + var vm = this._view; + var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); + + ctx.lineTo(caretPosition.x1, caretPosition.y1); + ctx.lineTo(caretPosition.x2, caretPosition.y2); + ctx.lineTo(caretPosition.x3, caretPosition.y3); + }, + getCaretPosition: function(tooltipPoint, size, vm) { + var x1, x2, x3, y1, y2, y3; + var caretSize = vm.caretSize; + var cornerRadius = vm.cornerRadius; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var ptX = tooltipPoint.x; + var ptY = tooltipPoint.y; + var width = size.width; + var height = size.height; + + if (yAlign === 'center') { + y2 = ptY + (height / 2); + + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; + x3 = x1; + + y1 = y2 + caretSize; + y3 = y2 - caretSize; + } else { + x1 = ptX + width; + x2 = x1 + caretSize; + x3 = x1; + + y1 = y2 - caretSize; + y3 = y2 + caretSize; + } + } else { + if (xAlign === 'left') { + x2 = ptX + cornerRadius + (caretSize); + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else if (xAlign === 'right') { + x2 = ptX + width - cornerRadius - caretSize; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else { + x2 = vm.caretX; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; + y3 = y1; + } else { + y1 = ptY + height; + y2 = y1 + caretSize; + y3 = y1; + // invert drawing order + var tmp = x3; + x3 = x1; + x1 = tmp; + } + } + return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; + }, + + drawTitle: function(pt, vm, ctx) { + var title = vm.title; + var length = title.length; + var titleFontSize, titleSpacing, i; + + if (length) { + var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); + + pt.x = getAlignedX(vm, vm._titleAlign); + + ctx.textAlign = rtlHelper.textAlign(vm._titleAlign); + ctx.textBaseline = 'middle'; + + titleFontSize = vm.titleFontSize; + titleSpacing = vm.titleSpacing; + + ctx.fillStyle = vm.titleFontColor; + ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + + for (i = 0; i < length; ++i) { + ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2); + pt.y += titleFontSize + titleSpacing; // Line Height and spacing + + if (i + 1 === length) { + pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing + } + } + } + }, + + drawBody: function(pt, vm, ctx) { + var bodyFontSize = vm.bodyFontSize; + var bodySpacing = vm.bodySpacing; + var bodyAlign = vm._bodyAlign; + var body = vm.body; + var drawColorBoxes = vm.displayColors; + var xLinePadding = 0; + var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0; + + var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); + + var fillLineOfText = function(line) { + ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2); + pt.y += bodyFontSize + bodySpacing; + }; + + var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen; + var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); + + ctx.textAlign = bodyAlign; + ctx.textBaseline = 'middle'; + ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + + pt.x = getAlignedX(vm, bodyAlignForCalculation); + + // Before body lines + ctx.fillStyle = vm.bodyFontColor; + helpers$1.each(vm.beforeBody, fillLineOfText); + + xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right' + ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2) + : 0; + + // Draw body lines now + for (i = 0, ilen = body.length; i < ilen; ++i) { + bodyItem = body[i]; + textColor = vm.labelTextColors[i]; + labelColors = vm.labelColors[i]; + + ctx.fillStyle = textColor; + helpers$1.each(bodyItem.before, fillLineOfText); + + lines = bodyItem.lines; + for (j = 0, jlen = lines.length; j < jlen; ++j) { + // Draw Legend-like boxes if needed + if (drawColorBoxes) { + var rtlColorX = rtlHelper.x(colorX); + + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = vm.legendColorBackground; + ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); + + // Border + ctx.lineWidth = 1; + ctx.strokeStyle = labelColors.borderColor; + ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); + + // Inner square + ctx.fillStyle = labelColors.backgroundColor; + ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); + ctx.fillStyle = textColor; + } + + fillLineOfText(lines[j]); + } + + helpers$1.each(bodyItem.after, fillLineOfText); + } + + // Reset back to 0 for after body + xLinePadding = 0; + + // After body lines + helpers$1.each(vm.afterBody, fillLineOfText); + pt.y -= bodySpacing; // Remove last body spacing + }, + + drawFooter: function(pt, vm, ctx) { + var footer = vm.footer; + var length = footer.length; + var footerFontSize, i; + + if (length) { + var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); + + pt.x = getAlignedX(vm, vm._footerAlign); + pt.y += vm.footerMarginTop; + + ctx.textAlign = rtlHelper.textAlign(vm._footerAlign); + ctx.textBaseline = 'middle'; + + footerFontSize = vm.footerFontSize; + + ctx.fillStyle = vm.footerFontColor; + ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + + for (i = 0; i < length; ++i) { + ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2); + pt.y += footerFontSize + vm.footerSpacing; + } + } + }, + + drawBackground: function(pt, vm, ctx, tooltipSize) { + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var x = pt.x; + var y = pt.y; + var width = tooltipSize.width; + var height = tooltipSize.height; + var radius = vm.cornerRadius; + + ctx.beginPath(); + ctx.moveTo(x + radius, y); + if (yAlign === 'top') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + if (yAlign === 'center' && xAlign === 'right') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + if (yAlign === 'bottom') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + if (yAlign === 'center' && xAlign === 'left') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + + ctx.fill(); + + if (vm.borderWidth > 0) { + ctx.stroke(); + } + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + + if (vm.opacity === 0) { + return; + } + + var tooltipSize = { + width: vm.width, + height: vm.height + }; + var pt = { + x: vm.x, + y: vm.y + }; + + // IE11/Edge does not like very small opacities, so snap to 0 + var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; + + // Truthy/falsey value for empty tooltip + var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; + + if (this._options.enabled && hasTooltipContent) { + ctx.save(); + ctx.globalAlpha = opacity; + + // Draw Background + this.drawBackground(pt, vm, ctx, tooltipSize); + + // Draw Title, Body, and Footer + pt.y += vm.yPadding; + + helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection); + + // Titles + this.drawTitle(pt, vm, ctx); + + // Body + this.drawBody(pt, vm, ctx); + + // Footer + this.drawFooter(pt, vm, ctx); + + helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection); + + ctx.restore(); + } + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @returns {boolean} true if the tooltip changed + */ + handleEvent: function(e) { + var me = this; + var options = me._options; + var changed = false; + + me._lastActive = me._lastActive || []; + + // Find Active Elements for tooltips + if (e.type === 'mouseout') { + me._active = []; + } else { + me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); + if (options.reverse) { + me._active.reverse(); + } + } + + // Remember Last Actives + changed = !helpers$1.arrayEquals(me._active, me._lastActive); + + // Only handle target event on tooltip change + if (changed) { + me._lastActive = me._active; + + if (options.enabled || options.custom) { + me._eventPosition = { + x: e.x, + y: e.y + }; + + me.update(true); + me.pivot(); + } + } + + return changed; + } +}); + +/** + * @namespace Chart.Tooltip.positioners + */ +var positioners_1 = positioners; + +var core_tooltip = exports$4; +core_tooltip.positioners = positioners_1; + +var valueOrDefault$9 = helpers$1.valueOrDefault; + +core_defaults._set('global', { + elements: {}, + events: [ + 'mousemove', + 'mouseout', + 'click', + 'touchstart', + 'touchmove' + ], + hover: { + onHover: null, + mode: 'nearest', + intersect: true, + animationDuration: 400 + }, + onClick: null, + maintainAspectRatio: true, + responsive: true, + responsiveAnimationDuration: 0 +}); + +/** + * Recursively merge the given config objects representing the `scales` option + * by incorporating scale defaults in `xAxes` and `yAxes` array items, then + * returns a deep copy of the result, thus doesn't alter inputs. + */ +function mergeScaleConfig(/* config objects ... */) { + return helpers$1.merge({}, [].slice.call(arguments), { + merger: function(key, target, source, options) { + if (key === 'xAxes' || key === 'yAxes') { + var slen = source[key].length; + var i, type, scale; + + if (!target[key]) { + target[key] = []; + } + + for (i = 0; i < slen; ++i) { + scale = source[key][i]; + type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear'); + + if (i >= target[key].length) { + target[key].push({}); + } + + if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { + // new/untyped scale or type changed: let's apply the new defaults + // then merge source scale to correctly overwrite the defaults. + helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]); + } else { + // scales type are the same + helpers$1.merge(target[key][i], scale); + } + } + } else { + helpers$1._merger(key, target, source, options); + } + } + }); +} + +/** + * Recursively merge the given config objects as the root options by handling + * default scale options for the `scales` and `scale` properties, then returns + * a deep copy of the result, thus doesn't alter inputs. + */ +function mergeConfig(/* config objects ... */) { + return helpers$1.merge({}, [].slice.call(arguments), { + merger: function(key, target, source, options) { + var tval = target[key] || {}; + var sval = source[key]; + + if (key === 'scales') { + // scale config merging is complex. Add our own function here for that + target[key] = mergeScaleConfig(tval, sval); + } else if (key === 'scale') { + // used in polar area & radar charts since there is only one scale + target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]); + } else { + helpers$1._merger(key, target, source, options); + } + } + }); +} + +function initConfig(config) { + config = config || {}; + + // Do NOT use mergeConfig for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + var data = config.data = config.data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + + config.options = mergeConfig( + core_defaults.global, + core_defaults[config.type], + config.options || {}); + + return config; +} + +function updateConfig(chart) { + var newOptions = chart.options; + + helpers$1.each(chart.scales, function(scale) { + core_layouts.removeBox(chart, scale); + }); + + newOptions = mergeConfig( + core_defaults.global, + core_defaults[chart.config.type], + newOptions); + + chart.options = chart.config.options = newOptions; + chart.ensureScalesHaveIDs(); + chart.buildOrUpdateScales(); + + // Tooltip + chart.tooltip._options = newOptions.tooltips; + chart.tooltip.initialize(); +} + +function nextAvailableScaleId(axesOpts, prefix, index) { + var id; + var hasId = function(obj) { + return obj.id === id; + }; + + do { + id = prefix + index++; + } while (helpers$1.findIndex(axesOpts, hasId) >= 0); + + return id; +} + +function positionIsHorizontal(position) { + return position === 'top' || position === 'bottom'; +} + +function compare2Level(l1, l2) { + return function(a, b) { + return a[l1] === b[l1] + ? a[l2] - b[l2] + : a[l1] - b[l1]; + }; +} + +var Chart = function(item, config) { + this.construct(item, config); + return this; +}; + +helpers$1.extend(Chart.prototype, /** @lends Chart */ { + /** + * @private + */ + construct: function(item, config) { + var me = this; + + config = initConfig(config); + + var context = platform.acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; + + me.id = helpers$1.uid(); + me.ctx = context; + me.canvas = canvas; + me.config = config; + me.width = width; + me.height = height; + me.aspectRatio = height ? width / height : null; + me.options = config.options; + me._bufferedRender = false; + me._layers = []; + + /** + * Provided for backward compatibility, Chart and Chart.Controller have been merged, + * the "instance" still need to be defined since it might be called from plugins. + * @prop Chart#chart + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + me.chart = me; + me.controller = me; // chart.chart.controller #inception + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + // Define alias to the config data: `chart.data === chart.config.data` + Object.defineProperty(me, 'data', { + get: function() { + return me.config.data; + }, + set: function(value) { + me.config.data = value; + } + }); + + if (!context || !canvas) { + // The given item is not a compatible context2d element, let's return before finalizing + // the chart initialization but after setting basic chart / controller properties that + // can help to figure out that the chart is not valid (e.g chart.canvas !== null); + // https://github.com/chartjs/Chart.js/issues/2807 + console.error("Failed to create chart: can't acquire context from the given item"); + return; + } + + me.initialize(); + me.update(); + }, + + /** + * @private + */ + initialize: function() { + var me = this; + + // Before init plugin notification + core_plugins.notify(me, 'beforeInit'); + + helpers$1.retinaScale(me, me.options.devicePixelRatio); + + me.bindEvents(); + + if (me.options.responsive) { + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } + + me.initToolTip(); + + // After init plugin notification + core_plugins.notify(me, 'afterInit'); + + return me; + }, + + clear: function() { + helpers$1.canvas.clear(this); + return this; + }, + + stop: function() { + // Stops any current animation loop occurring + core_animations.cancelAnimation(this); + return this; + }, + + resize: function(silent) { + var me = this; + var options = me.options; + var canvas = me.canvas; + var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; + + // the canvas render width and height will be casted to integers so make sure that + // the canvas display style uses the same integer values to avoid blurring effect. + + // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed + var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas))); + var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas))); + + if (me.width === newWidth && me.height === newHeight) { + return; + } + + canvas.width = me.width = newWidth; + canvas.height = me.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; + + helpers$1.retinaScale(me, options.devicePixelRatio); + + if (!silent) { + // Notify any plugins about the resize + var newSize = {width: newWidth, height: newHeight}; + core_plugins.notify(me, 'resize', [newSize]); + + // Notify of resize + if (options.onResize) { + options.onResize(me, newSize); + } + + me.stop(); + me.update({ + duration: options.responsiveAnimationDuration + }); + } + }, + + ensureScalesHaveIDs: function() { + var options = this.options; + var scalesOptions = options.scales || {}; + var scaleOptions = options.scale; + + helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) { + if (!xAxisOptions.id) { + xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index); + } + }); + + helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) { + if (!yAxisOptions.id) { + yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index); + } + }); + + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + }, + + /** + * Builds a map of scale ID to scale object for future lookup. + */ + buildOrUpdateScales: function() { + var me = this; + var options = me.options; + var scales = me.scales || {}; + var items = []; + var updated = Object.keys(scales).reduce(function(obj, id) { + obj[id] = false; + return obj; + }, {}); + + if (options.scales) { + items = items.concat( + (options.scales.xAxes || []).map(function(xAxisOptions) { + return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; + }), + (options.scales.yAxes || []).map(function(yAxisOptions) { + return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; + }) + ); + } + + if (options.scale) { + items.push({ + options: options.scale, + dtype: 'radialLinear', + isDefault: true, + dposition: 'chartArea' + }); + } + + helpers$1.each(items, function(item) { + var scaleOptions = item.options; + var id = scaleOptions.id; + var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype); + + if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { + scaleOptions.position = item.dposition; + } + + updated[id] = true; + var scale = null; + if (id in scales && scales[id].type === scaleType) { + scale = scales[id]; + scale.options = scaleOptions; + scale.ctx = me.ctx; + scale.chart = me; + } else { + var scaleClass = core_scaleService.getScaleConstructor(scaleType); + if (!scaleClass) { + return; + } + scale = new scaleClass({ + id: id, + type: scaleType, + options: scaleOptions, + ctx: me.ctx, + chart: me + }); + scales[scale.id] = scale; + } + + scale.mergeTicksOptions(); + + // TODO(SB): I think we should be able to remove this custom case (options.scale) + // and consider it as a regular scale part of the "scales"" map only! This would + // make the logic easier and remove some useless? custom code. + if (item.isDefault) { + me.scale = scale; + } + }); + // clear up discarded scales + helpers$1.each(updated, function(hasUpdated, id) { + if (!hasUpdated) { + delete scales[id]; + } + }); + + me.scales = scales; + + core_scaleService.addScalesToLayout(this); + }, + + buildOrUpdateControllers: function() { + var me = this; + var newControllers = []; + var datasets = me.data.datasets; + var i, ilen; + + for (i = 0, ilen = datasets.length; i < ilen; i++) { + var dataset = datasets[i]; + var meta = me.getDatasetMeta(i); + var type = dataset.type || me.config.type; + + if (meta.type && meta.type !== type) { + me.destroyDatasetMeta(i); + meta = me.getDatasetMeta(i); + } + meta.type = type; + meta.order = dataset.order || 0; + meta.index = i; + + if (meta.controller) { + meta.controller.updateIndex(i); + meta.controller.linkScales(); + } else { + var ControllerClass = controllers[meta.type]; + if (ControllerClass === undefined) { + throw new Error('"' + meta.type + '" is not a chart type.'); + } + + meta.controller = new ControllerClass(me, i); + newControllers.push(meta.controller); + } + } + + return newControllers; + }, + + /** + * Reset the elements of all datasets + * @private + */ + resetElements: function() { + var me = this; + helpers$1.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + }, + + /** + * Resets the chart back to it's state before the initial animation + */ + reset: function() { + this.resetElements(); + this.tooltip.initialize(); + }, + + update: function(config) { + var me = this; + var i, ilen; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + updateConfig(me); + + // plugins options references might have change, let's invalidate the cache + // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + core_plugins._invalidate(me); + + if (core_plugins.notify(me, 'beforeUpdate') === false) { + return; + } + + // In case the entire data object changed + me.tooltip._data = me.data; + + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = me.buildOrUpdateControllers(); + + // Make sure all dataset controllers have correct meta data counts + for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) { + me.getDatasetMeta(i).controller.buildOrUpdateElements(); + } + + me.updateLayout(); + + // Can only reset the new controllers after the scales have been updated + if (me.options.animation && me.options.animation.duration) { + helpers$1.each(newControllers, function(controller) { + controller.reset(); + }); + } + + me.updateDatasets(); + + // Need to reset tooltip in case it is displayed with elements that are removed + // after update. + me.tooltip.initialize(); + + // Last active contains items that were previously in the tooltip. + // When we reset the tooltip, we need to clear it + me.lastActive = []; + + // Do this before render so that any plugins that need final scale updates can use it + core_plugins.notify(me, 'afterUpdate'); + + me._layers.sort(compare2Level('z', '_idx')); + + if (me._bufferedRender) { + me._bufferedRequest = { + duration: config.duration, + easing: config.easing, + lazy: config.lazy + }; + } else { + me.render(config); + } + }, + + /** + * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` + * hook, in which case, plugins will not be called on `afterLayout`. + * @private + */ + updateLayout: function() { + var me = this; + + if (core_plugins.notify(me, 'beforeLayout') === false) { + return; + } + + core_layouts.update(this, this.width, this.height); + + me._layers = []; + helpers$1.each(me.boxes, function(box) { + // _configure is called twice, once in core.scale.update and once here. + // Here the boxes are fully updated and at their final positions. + if (box._configure) { + box._configure(); + } + me._layers.push.apply(me._layers, box._layers()); + }, me); + + me._layers.forEach(function(item, index) { + item._idx = index; + }); + + /** + * Provided for backward compatibility, use `afterLayout` instead. + * @method IPlugin#afterScaleUpdate + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ + core_plugins.notify(me, 'afterScaleUpdate'); + core_plugins.notify(me, 'afterLayout'); + }, + + /** + * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` + * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. + * @private + */ + updateDatasets: function() { + var me = this; + + if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) { + return; + } + + for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.updateDataset(i); + } + + core_plugins.notify(me, 'afterDatasetsUpdate'); + }, + + /** + * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` + * hook, in which case, plugins will not be called on `afterDatasetUpdate`. + * @private + */ + updateDataset: function(index) { + var me = this; + var meta = me.getDatasetMeta(index); + var args = { + meta: meta, + index: index + }; + + if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { + return; + } + + meta.controller._update(); + + core_plugins.notify(me, 'afterDatasetUpdate', [args]); + }, + + render: function(config) { + var me = this; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + var animationOptions = me.options.animation; + var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration); + var lazy = config.lazy; + + if (core_plugins.notify(me, 'beforeRender') === false) { + return; + } + + var onComplete = function(animation) { + core_plugins.notify(me, 'afterRender'); + helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me); + }; + + if (animationOptions && duration) { + var animation = new core_animation({ + numSteps: duration / 16.66, // 60 fps + easing: config.easing || animationOptions.easing, + + render: function(chart, animationObject) { + var easingFunction = helpers$1.easing.effects[animationObject.easing]; + var currentStep = animationObject.currentStep; + var stepDecimal = currentStep / animationObject.numSteps; + + chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); + }, + + onAnimationProgress: animationOptions.onProgress, + onAnimationComplete: onComplete + }); + + core_animations.addAnimation(me, animation, duration, lazy); + } else { + me.draw(); + + // See https://github.com/chartjs/Chart.js/issues/3781 + onComplete(new core_animation({numSteps: 0, chart: me})); + } + + return me; + }, + + draw: function(easingValue) { + var me = this; + var i, layers; + + me.clear(); + + if (helpers$1.isNullOrUndef(easingValue)) { + easingValue = 1; + } + + me.transition(easingValue); + + if (me.width <= 0 || me.height <= 0) { + return; + } + + if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) { + return; + } + + // Because of plugin hooks (before/afterDatasetsDraw), datasets can't + // currently be part of layers. Instead, we draw + // layers <= 0 before(default, backward compat), and the rest after + layers = me._layers; + for (i = 0; i < layers.length && layers[i].z <= 0; ++i) { + layers[i].draw(me.chartArea); + } + + me.drawDatasets(easingValue); + + // Rest of layers + for (; i < layers.length; ++i) { + layers[i].draw(me.chartArea); + } + + me._drawTooltip(easingValue); + + core_plugins.notify(me, 'afterDraw', [easingValue]); + }, + + /** + * @private + */ + transition: function(easingValue) { + var me = this; + + for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { + if (me.isDatasetVisible(i)) { + me.getDatasetMeta(i).controller.transition(easingValue); + } + } + + me.tooltip.transition(easingValue); + }, + + /** + * @private + */ + _getSortedDatasetMetas: function(filterVisible) { + var me = this; + var datasets = me.data.datasets || []; + var result = []; + var i, ilen; + + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!filterVisible || me.isDatasetVisible(i)) { + result.push(me.getDatasetMeta(i)); + } + } + + result.sort(compare2Level('order', 'index')); + + return result; + }, + + /** + * @private + */ + _getSortedVisibleDatasetMetas: function() { + return this._getSortedDatasetMetas(true); + }, + + /** + * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` + * hook, in which case, plugins will not be called on `afterDatasetsDraw`. + * @private + */ + drawDatasets: function(easingValue) { + var me = this; + var metasets, i; + + if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { + return; + } + + metasets = me._getSortedVisibleDatasetMetas(); + for (i = metasets.length - 1; i >= 0; --i) { + me.drawDataset(metasets[i], easingValue); + } + + core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]); + }, + + /** + * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` + * hook, in which case, plugins will not be called on `afterDatasetDraw`. + * @private + */ + drawDataset: function(meta, easingValue) { + var me = this; + var args = { + meta: meta, + index: meta.index, + easingValue: easingValue + }; + + if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { + return; + } + + meta.controller.draw(easingValue); + + core_plugins.notify(me, 'afterDatasetDraw', [args]); + }, + + /** + * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` + * hook, in which case, plugins will not be called on `afterTooltipDraw`. + * @private + */ + _drawTooltip: function(easingValue) { + var me = this; + var tooltip = me.tooltip; + var args = { + tooltip: tooltip, + easingValue: easingValue + }; + + if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { + return; + } + + tooltip.draw(); + + core_plugins.notify(me, 'afterTooltipDraw', [args]); + }, + + /** + * Get the single element that was clicked on + * @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw + */ + getElementAtEvent: function(e) { + return core_interaction.modes.single(this, e); + }, + + getElementsAtEvent: function(e) { + return core_interaction.modes.label(this, e, {intersect: true}); + }, + + getElementsAtXAxis: function(e) { + return core_interaction.modes['x-axis'](this, e, {intersect: true}); + }, + + getElementsAtEventForMode: function(e, mode, options) { + var method = core_interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); + } + + return []; + }, + + getDatasetAtEvent: function(e) { + return core_interaction.modes.dataset(this, e, {intersect: true}); + }, + + getDatasetMeta: function(datasetIndex) { + var me = this; + var dataset = me.data.datasets[datasetIndex]; + if (!dataset._meta) { + dataset._meta = {}; + } + + var meta = dataset._meta[me.id]; + if (!meta) { + meta = dataset._meta[me.id] = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null, + order: dataset.order || 0, + index: datasetIndex + }; + } + + return meta; + }, + + getVisibleDatasetCount: function() { + var count = 0; + for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { + if (this.isDatasetVisible(i)) { + count++; + } + } + return count; + }, + + isDatasetVisible: function(datasetIndex) { + var meta = this.getDatasetMeta(datasetIndex); + + // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, + // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. + return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; + }, + + generateLegend: function() { + return this.options.legendCallback(this); + }, + + /** + * @private + */ + destroyDatasetMeta: function(datasetIndex) { + var id = this.id; + var dataset = this.data.datasets[datasetIndex]; + var meta = dataset._meta && dataset._meta[id]; + + if (meta) { + meta.controller.destroy(); + delete dataset._meta[id]; + } + }, + + destroy: function() { + var me = this; + var canvas = me.canvas; + var i, ilen; + + me.stop(); + + // dataset controllers need to cleanup associated data + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.destroyDatasetMeta(i); + } + + if (canvas) { + me.unbindEvents(); + helpers$1.canvas.clear(me); + platform.releaseContext(me.ctx); + me.canvas = null; + me.ctx = null; + } + + core_plugins.notify(me, 'destroy'); + + delete Chart.instances[me.id]; + }, + + toBase64Image: function() { + return this.canvas.toDataURL.apply(this.canvas, arguments); + }, + + initToolTip: function() { + var me = this; + me.tooltip = new core_tooltip({ + _chart: me, + _chartInstance: me, // deprecated, backward compatibility + _data: me.data, + _options: me.options.tooltips + }, me); + }, + + /** + * @private + */ + bindEvents: function() { + var me = this; + var listeners = me._listeners = {}; + var listener = function() { + me.eventHandler.apply(me, arguments); + }; + + helpers$1.each(me.options.events, function(type) { + platform.addEventListener(me, type, listener); + listeners[type] = listener; + }); + + // Elements used to detect size change should not be injected for non responsive charts. + // See https://github.com/chartjs/Chart.js/issues/2210 + if (me.options.responsive) { + listener = function() { + me.resize(); + }; + + platform.addEventListener(me, 'resize', listener); + listeners.resize = listener; + } + }, + + /** + * @private + */ + unbindEvents: function() { + var me = this; + var listeners = me._listeners; + if (!listeners) { + return; + } + + delete me._listeners; + helpers$1.each(listeners, function(listener, type) { + platform.removeEventListener(me, type, listener); + }); + }, + + updateHoverStyle: function(elements, mode, enabled) { + var prefix = enabled ? 'set' : 'remove'; + var element, i, ilen; + + for (i = 0, ilen = elements.length; i < ilen; ++i) { + element = elements[i]; + if (element) { + this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element); + } + } + + if (mode === 'dataset') { + this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle'](); + } + }, + + /** + * @private + */ + eventHandler: function(e) { + var me = this; + var tooltip = me.tooltip; + + if (core_plugins.notify(me, 'beforeEvent', [e]) === false) { + return; + } + + // Buffer any update calls so that renders do not occur + me._bufferedRender = true; + me._bufferedRequest = null; + + var changed = me.handleEvent(e); + // for smooth tooltip animations issue #4989 + // the tooltip should be the source of change + // Animation check workaround: + // tooltip._start will be null when tooltip isn't animating + if (tooltip) { + changed = tooltip._start + ? tooltip.handleEvent(e) + : changed | tooltip.handleEvent(e); + } + + core_plugins.notify(me, 'afterEvent', [e]); + + var bufferedRequest = me._bufferedRequest; + if (bufferedRequest) { + // If we have an update that was triggered, we need to do a normal render + me.render(bufferedRequest); + } else if (changed && !me.animating) { + // If entering, leaving, or changing elements, animate the change via pivot + me.stop(); + + // We only need to render at this point. Updating will cause scales to be + // recomputed generating flicker & using more memory than necessary. + me.render({ + duration: me.options.hover.animationDuration, + lazy: true + }); + } + + me._bufferedRender = false; + me._bufferedRequest = null; + + return me; + }, + + /** + * Handle an event + * @private + * @param {IEvent} event the event to handle + * @return {boolean} true if the chart needs to re-render + */ + handleEvent: function(e) { + var me = this; + var options = me.options || {}; + var hoverOptions = options.hover; + var changed = false; + + me.lastActive = me.lastActive || []; + + // Find Active Elements for hover and tooltips + if (e.type === 'mouseout') { + me.active = []; + } else { + me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); + } + + // Invoke onHover hook + // Need to call with native event here to not break backwards compatibility + helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); + + if (e.type === 'mouseup' || e.type === 'click') { + if (options.onClick) { + // Use e.native here for backwards compatibility + options.onClick.call(me, e.native, me.active); + } + } + + // Remove styling for last active (even if it may still be active) + if (me.lastActive.length) { + me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); + } + + // Built in hover styling + if (me.active.length && hoverOptions.mode) { + me.updateHoverStyle(me.active, hoverOptions.mode, true); + } + + changed = !helpers$1.arrayEquals(me.active, me.lastActive); + + // Remember Last Actives + me.lastActive = me.active; + + return changed; + } +}); + +/** + * NOTE(SB) We actually don't use this container anymore but we need to keep it + * for backward compatibility. Though, it can still be useful for plugins that + * would need to work on multiple charts?! + */ +Chart.instances = {}; + +var core_controller = Chart; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart instead. + * @class Chart.Controller + * @deprecated since version 2.6 + * @todo remove at version 3 + * @private + */ +Chart.Controller = Chart; + +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart + * @deprecated since version 2.8 + * @todo remove at version 3 + * @private + */ +Chart.types = {}; + +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart.helpers.configMerge + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ +helpers$1.configMerge = mergeConfig; + +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart.helpers.scaleMerge + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ +helpers$1.scaleMerge = mergeScaleConfig; + +var core_helpers = function() { + + // -- Basic js utility methods + + helpers$1.where = function(collection, filterCallback) { + if (helpers$1.isArray(collection) && Array.prototype.filter) { + return collection.filter(filterCallback); + } + var filtered = []; + + helpers$1.each(collection, function(item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); + + return filtered; + }; + helpers$1.findIndex = Array.prototype.findIndex ? + function(array, callback, scope) { + return array.findIndex(callback, scope); + } : + function(array, callback, scope) { + scope = scope === undefined ? array : scope; + for (var i = 0, ilen = array.length; i < ilen; ++i) { + if (callback.call(scope, array[i], i, array)) { + return i; + } + } + return -1; + }; + helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to start of the array + if (helpers$1.isNullOrUndef(startIndex)) { + startIndex = -1; + } + for (var i = startIndex + 1; i < arrayToSearch.length; i++) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to end of the array + if (helpers$1.isNullOrUndef(startIndex)) { + startIndex = arrayToSearch.length; + } + for (var i = startIndex - 1; i >= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + + // -- Math methods + helpers$1.isNumber = function(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + helpers$1.almostEquals = function(x, y, epsilon) { + return Math.abs(x - y) < epsilon; + }; + helpers$1.almostWhole = function(x, epsilon) { + var rounded = Math.round(x); + return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x); + }; + helpers$1.max = function(array) { + return array.reduce(function(max, value) { + if (!isNaN(value)) { + return Math.max(max, value); + } + return max; + }, Number.NEGATIVE_INFINITY); + }; + helpers$1.min = function(array) { + return array.reduce(function(min, value) { + if (!isNaN(value)) { + return Math.min(min, value); + } + return min; + }, Number.POSITIVE_INFINITY); + }; + helpers$1.sign = Math.sign ? + function(x) { + return Math.sign(x); + } : + function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; + helpers$1.toRadians = function(degrees) { + return degrees * (Math.PI / 180); + }; + helpers$1.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }; + + /** + * Returns the number of decimal places + * i.e. the number of digits after the decimal point, of the value of this Number. + * @param {number} x - A number. + * @returns {number} The number of decimal places. + * @private + */ + helpers$1._decimalPlaces = function(x) { + if (!helpers$1.isFinite(x)) { + return; + } + var e = 1; + var p = 0; + while (Math.round(x * e) / e !== x) { + e *= 10; + p++; + } + return p; + }; + + // Gets the angle from vertical upright to the point about a centre. + helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) { + var distanceFromXCenter = anglePoint.x - centrePoint.x; + var distanceFromYCenter = anglePoint.y - centrePoint.y; + var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + + var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } + + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }; + helpers$1.distanceBetweenPoints = function(pt1, pt2) { + return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); + }; + + /** + * Provided for backward compatibility, not available anymore + * @function Chart.helpers.aliasPixel + * @deprecated since version 2.8.0 + * @todo remove at version 3 + */ + helpers$1.aliasPixel = function(pixelWidth) { + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }; + + /** + * Returns the aligned pixel value to avoid anti-aliasing blur + * @param {Chart} chart - The chart instance. + * @param {number} pixel - A pixel value. + * @param {number} width - The width of the element. + * @returns {number} The aligned pixel value. + * @private + */ + helpers$1._alignPixel = function(chart, pixel, width) { + var devicePixelRatio = chart.currentDevicePixelRatio; + var halfWidth = width / 2; + return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; + }; + + helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { + // Props to Rob Spencer at scaled innovation for his post on splining between points + // http://scaledinnovation.com/analytics/splines/aboutSplines.html + + // This function must also respect "skipped" points + + var previous = firstPoint.skip ? middlePoint : firstPoint; + var current = middlePoint; + var next = afterPoint.skip ? middlePoint : afterPoint; + + var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + + var s01 = d01 / (d01 + d12); + var s12 = d12 / (d01 + d12); + + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; + + var fa = t * s01; // scaling factor for triangle Ta + var fb = t * s12; + + return { + previous: { + x: current.x - fa * (next.x - previous.x), + y: current.y - fa * (next.y - previous.y) + }, + next: { + x: current.x + fb * (next.x - previous.x), + y: current.y + fb * (next.y - previous.y) + } + }; + }; + helpers$1.EPSILON = Number.EPSILON || 1e-14; + helpers$1.splineCurveMonotone = function(points) { + // This function calculates Bézier control points in a similar way than |splineCurve|, + // but preserves monotonicity of the provided data and ensures no local extremums are added + // between the dataset discrete points due to the interpolation. + // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation + + var pointsWithTangents = (points || []).map(function(point) { + return { + model: point._model, + deltaK: 0, + mK: 0 + }; + }); + + // Calculate slopes (deltaK) and initialize tangents (mK) + var pointsLen = pointsWithTangents.length; + var i, pointBefore, pointCurrent, pointAfter; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointAfter && !pointAfter.model.skip) { + var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); + + // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 + pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; + } + + if (!pointBefore || pointBefore.model.skip) { + pointCurrent.mK = pointCurrent.deltaK; + } else if (!pointAfter || pointAfter.model.skip) { + pointCurrent.mK = pointBefore.deltaK; + } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { + pointCurrent.mK = 0; + } else { + pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; + } + } + + // Adjust tangents to ensure monotonic properties + var alphaK, betaK, tauK, squaredMagnitude; + for (i = 0; i < pointsLen - 1; ++i) { + pointCurrent = pointsWithTangents[i]; + pointAfter = pointsWithTangents[i + 1]; + if (pointCurrent.model.skip || pointAfter.model.skip) { + continue; + } + + if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { + pointCurrent.mK = pointAfter.mK = 0; + continue; + } + + alphaK = pointCurrent.mK / pointCurrent.deltaK; + betaK = pointAfter.mK / pointCurrent.deltaK; + squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); + if (squaredMagnitude <= 9) { + continue; + } + + tauK = 3 / Math.sqrt(squaredMagnitude); + pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; + pointAfter.mK = betaK * tauK * pointCurrent.deltaK; + } + + // Compute control points + var deltaX; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointBefore && !pointBefore.model.skip) { + deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; + pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; + pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; + } + if (pointAfter && !pointAfter.model.skip) { + deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; + pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; + pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; + } + } + }; + helpers$1.nextItem = function(collection, index, loop) { + if (loop) { + return index >= collection.length - 1 ? collection[0] : collection[index + 1]; + } + return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; + }; + helpers$1.previousItem = function(collection, index, loop) { + if (loop) { + return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; + } + return index <= 0 ? collection[0] : collection[index - 1]; + }; + // Implementation of the nice number algorithm used in determining where axis labels will go + helpers$1.niceNum = function(range, round) { + var exponent = Math.floor(helpers$1.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + + return niceFraction * Math.pow(10, exponent); + }; + // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + helpers$1.requestAnimFrame = (function() { + if (typeof window === 'undefined') { + return function(callback) { + callback(); + }; + } + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + }()); + // -- DOM methods + helpers$1.getRelativePosition = function(evt, chart) { + var mouseX, mouseY; + var e = evt.originalEvent || evt; + var canvas = evt.target || evt.srcElement; + var boundingRect = canvas.getBoundingClientRect(); + + var touches = e.touches; + if (touches && touches.length > 0) { + mouseX = touches[0].clientX; + mouseY = touches[0].clientY; + + } else { + mouseX = e.clientX; + mouseY = e.clientY; + } + + // Scale mouse coordinates into canvas coordinates + // by following the pattern laid out by 'jerryj' in the comments of + // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ + var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left')); + var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top')); + var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right')); + var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom')); + var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; + var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; + + // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However + // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here + mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); + mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); + + return { + x: mouseX, + y: mouseY + }; + + }; + + // Private helper function to convert max-width/max-height values that may be percentages into a number + function parseMaxStyle(styleValue, node, parentProperty) { + var valueInPixels; + if (typeof styleValue === 'string') { + valueInPixels = parseInt(styleValue, 10); + + if (styleValue.indexOf('%') !== -1) { + // percentage * size in dimension + valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + } + } else { + valueInPixels = styleValue; + } + + return valueInPixels; + } + + /** + * Returns if the given value contains an effective constraint. + * @private + */ + function isConstrainedValue(value) { + return value !== undefined && value !== null && value !== 'none'; + } + + /** + * Returns the max width or height of the given DOM node in a cross-browser compatible fashion + * @param {HTMLElement} domNode - the node to check the constraint on + * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height') + * @param {string} percentageProperty - property of parent to use when calculating width as a percentage + * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser} + */ + function getConstraintDimension(domNode, maxStyle, percentageProperty) { + var view = document.defaultView; + var parentNode = helpers$1._getParentNode(domNode); + var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; + var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; + var hasCNode = isConstrainedValue(constrainedNode); + var hasCContainer = isConstrainedValue(constrainedContainer); + var infinity = Number.POSITIVE_INFINITY; + + if (hasCNode || hasCContainer) { + return Math.min( + hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, + hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); + } + + return 'none'; + } + // returns Number or undefined if no constraint + helpers$1.getConstraintWidth = function(domNode) { + return getConstraintDimension(domNode, 'max-width', 'clientWidth'); + }; + // returns Number or undefined if no constraint + helpers$1.getConstraintHeight = function(domNode) { + return getConstraintDimension(domNode, 'max-height', 'clientHeight'); + }; + /** + * @private + */ + helpers$1._calculatePadding = function(container, padding, parentDimension) { + padding = helpers$1.getStyle(container, padding); + + return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10); + }; + /** + * @private + */ + helpers$1._getParentNode = function(domNode) { + var parent = domNode.parentNode; + if (parent && parent.toString() === '[object ShadowRoot]') { + parent = parent.host; + } + return parent; + }; + helpers$1.getMaximumWidth = function(domNode) { + var container = helpers$1._getParentNode(domNode); + if (!container) { + return domNode.clientWidth; + } + + var clientWidth = container.clientWidth; + var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth); + var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth); + + var w = clientWidth - paddingLeft - paddingRight; + var cw = helpers$1.getConstraintWidth(domNode); + return isNaN(cw) ? w : Math.min(w, cw); + }; + helpers$1.getMaximumHeight = function(domNode) { + var container = helpers$1._getParentNode(domNode); + if (!container) { + return domNode.clientHeight; + } + + var clientHeight = container.clientHeight; + var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight); + var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight); + + var h = clientHeight - paddingTop - paddingBottom; + var ch = helpers$1.getConstraintHeight(domNode); + return isNaN(ch) ? h : Math.min(h, ch); + }; + helpers$1.getStyle = function(el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }; + helpers$1.retinaScale = function(chart, forceRatio) { + var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; + if (pixelRatio === 1) { + return; + } + + var canvas = chart.canvas; + var height = chart.height; + var width = chart.width; + + canvas.height = height * pixelRatio; + canvas.width = width * pixelRatio; + chart.ctx.scale(pixelRatio, pixelRatio); + + // If no style has been set on the canvas, the render size is used as display size, + // making the chart visually bigger, so let's enforce it to the "correct" values. + // See https://github.com/chartjs/Chart.js/issues/3575 + if (!canvas.style.height && !canvas.style.width) { + canvas.style.height = height + 'px'; + canvas.style.width = width + 'px'; + } + }; + // -- Canvas methods + helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) { + return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; + }; + helpers$1.longestText = function(ctx, font, arrayOfThings, cache) { + cache = cache || {}; + var data = cache.data = cache.data || {}; + var gc = cache.garbageCollect = cache.garbageCollect || []; + + if (cache.font !== font) { + data = cache.data = {}; + gc = cache.garbageCollect = []; + cache.font = font; + } + + ctx.font = font; + var longest = 0; + var ilen = arrayOfThings.length; + var i, j, jlen, thing, nestedThing; + for (i = 0; i < ilen; i++) { + thing = arrayOfThings[i]; + + // Undefined strings and arrays should not be measured + if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) { + longest = helpers$1.measureText(ctx, data, gc, longest, thing); + } else if (helpers$1.isArray(thing)) { + // if it is an array lets measure each element + // to do maybe simplify this function a bit so we can do this more recursively? + for (j = 0, jlen = thing.length; j < jlen; j++) { + nestedThing = thing[j]; + // Undefined strings and arrays should not be measured + if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) { + longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing); + } + } + } + } + + var gcLen = gc.length / 2; + if (gcLen > arrayOfThings.length) { + for (i = 0; i < gcLen; i++) { + delete data[gc[i]]; + } + gc.splice(0, gcLen); + } + return longest; + }; + helpers$1.measureText = function(ctx, data, gc, longest, string) { + var textWidth = data[string]; + if (!textWidth) { + textWidth = data[string] = ctx.measureText(string).width; + gc.push(string); + } + if (textWidth > longest) { + longest = textWidth; + } + return longest; + }; + + /** + * @deprecated + */ + helpers$1.numberOfLabelLines = function(arrayOfThings) { + var numberOfLines = 1; + helpers$1.each(arrayOfThings, function(thing) { + if (helpers$1.isArray(thing)) { + if (thing.length > numberOfLines) { + numberOfLines = thing.length; + } + } + }); + return numberOfLines; + }; + + helpers$1.color = !chartjsColor ? + function(value) { + console.error('Color.js not found!'); + return value; + } : + function(value) { + /* global CanvasGradient */ + if (value instanceof CanvasGradient) { + value = core_defaults.global.defaultColor; + } + + return chartjsColor(value); + }; + + helpers$1.getHoverColor = function(colorValue) { + /* global CanvasPattern */ + return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ? + colorValue : + helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString(); + }; +}; + +function abstract() { + throw new Error( + 'This method is not implemented: either no adapter can ' + + 'be found or an incomplete integration was provided.' + ); +} + +/** + * Date adapter (current used by the time scale) + * @namespace Chart._adapters._date + * @memberof Chart._adapters + * @private + */ + +/** + * Currently supported unit string values. + * @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')} + * @memberof Chart._adapters._date + * @name Unit + */ + +/** + * @class + */ +function DateAdapter(options) { + this.options = options || {}; +} + +helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ { + /** + * Returns a map of time formats for the supported formatting units defined + * in Unit as well as 'datetime' representing a detailed date/time string. + * @returns {{string: string}} + */ + formats: abstract, + + /** + * Parses the given `value` and return the associated timestamp. + * @param {any} value - the value to parse (usually comes from the data) + * @param {string} [format] - the expected data format + * @returns {(number|null)} + * @function + */ + parse: abstract, + + /** + * Returns the formatted date in the specified `format` for a given `timestamp`. + * @param {number} timestamp - the timestamp to format + * @param {string} format - the date/time token + * @return {string} + * @function + */ + format: abstract, + + /** + * Adds the specified `amount` of `unit` to the given `timestamp`. + * @param {number} timestamp - the input timestamp + * @param {number} amount - the amount to add + * @param {Unit} unit - the unit as string + * @return {number} + * @function + */ + add: abstract, + + /** + * Returns the number of `unit` between the given timestamps. + * @param {number} max - the input timestamp (reference) + * @param {number} min - the timestamp to substract + * @param {Unit} unit - the unit as string + * @return {number} + * @function + */ + diff: abstract, + + /** + * Returns start of `unit` for the given `timestamp`. + * @param {number} timestamp - the input timestamp + * @param {Unit} unit - the unit as string + * @param {number} [weekday] - the ISO day of the week with 1 being Monday + * and 7 being Sunday (only needed if param *unit* is `isoWeek`). + * @function + */ + startOf: abstract, + + /** + * Returns end of `unit` for the given `timestamp`. + * @param {number} timestamp - the input timestamp + * @param {Unit} unit - the unit as string + * @function + */ + endOf: abstract, + + // DEPRECATIONS + + /** + * Provided for backward compatibility for scale.getValueForPixel(), + * this method should be overridden only by the moment adapter. + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ + _create: function(value) { + return value; + } +}); + +DateAdapter.override = function(members) { + helpers$1.extend(DateAdapter.prototype, members); +}; + +var _date = DateAdapter; + +var core_adapters = { + _date: _date +}; + +/** + * Namespace to hold static tick generation functions + * @namespace Chart.Ticks + */ +var core_ticks = { + /** + * Namespace to hold formatters for different types of ticks + * @namespace Chart.Ticks.formatters + */ + formatters: { + /** + * Formatter for value labels + * @method Chart.Ticks.formatters.values + * @param value the value to display + * @return {string|string[]} the label to display + */ + values: function(value) { + return helpers$1.isArray(value) ? value : '' + value; + }, + + /** + * Formatter for linear numeric ticks + * @method Chart.Ticks.formatters.linear + * @param tickValue {number} the value to be formatted + * @param index {number} the position of the tickValue parameter in the ticks array + * @param ticks {number[]} the list of ticks being converted + * @return {string} string representation of the tickValue parameter + */ + linear: function(tickValue, index, ticks) { + // If we have lots of ticks, don't use the ones + var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; + + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1) { + if (tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } + } + + var logDelta = helpers$1.log10(Math.abs(delta)); + var tickString = ''; + + if (tickValue !== 0) { + var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); + if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation + var logTick = helpers$1.log10(Math.abs(tickValue)); + var numExponential = Math.floor(logTick) - Math.floor(logDelta); + numExponential = Math.max(Math.min(numExponential, 20), 0); + tickString = tickValue.toExponential(numExponential); + } else { + var numDecimal = -1 * Math.floor(logDelta); + numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places + tickString = tickValue.toFixed(numDecimal); + } + } else { + tickString = '0'; // never show decimal places for 0 + } + + return tickString; + }, + + logarithmic: function(tickValue, index, ticks) { + var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue)))); + + if (tickValue === 0) { + return '0'; + } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { + return tickValue.toExponential(); + } + return ''; + } + } +}; + +var isArray = helpers$1.isArray; +var isNullOrUndef = helpers$1.isNullOrUndef; +var valueOrDefault$a = helpers$1.valueOrDefault; +var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault; + +core_defaults._set('scale', { + display: true, + position: 'left', + offset: false, + + // grid line settings + gridLines: { + display: true, + color: 'rgba(0,0,0,0.1)', + lineWidth: 1, + drawBorder: true, + drawOnChartArea: true, + drawTicks: true, + tickMarkLength: 10, + zeroLineWidth: 1, + zeroLineColor: 'rgba(0,0,0,0.25)', + zeroLineBorderDash: [], + zeroLineBorderDashOffset: 0.0, + offsetGridLines: false, + borderDash: [], + borderDashOffset: 0.0 + }, + + // scale label + scaleLabel: { + // display property + display: false, + + // actual label + labelString: '', + + // top/bottom padding + padding: { + top: 4, + bottom: 4 + } + }, + + // label settings + ticks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 0, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. + callback: core_ticks.formatters.values, + minor: {}, + major: {} + } +}); + +/** Returns a new array containing numItems from arr */ +function sample(arr, numItems) { + var result = []; + var increment = arr.length / numItems; + var i = 0; + var len = arr.length; + + for (; i < len; i += increment) { + result.push(arr[Math.floor(i)]); + } + return result; +} + +function getPixelForGridLine(scale, index, offsetGridLines) { + var length = scale.getTicks().length; + var validIndex = Math.min(index, length - 1); + var lineValue = scale.getPixelForTick(validIndex); + var start = scale._startPixel; + var end = scale._endPixel; + var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. + var offset; + + if (offsetGridLines) { + if (length === 1) { + offset = Math.max(lineValue - start, end - lineValue); + } else if (index === 0) { + offset = (scale.getPixelForTick(1) - lineValue) / 2; + } else { + offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; + } + lineValue += validIndex < index ? offset : -offset; + + // Return undefined if the pixel is out of the range + if (lineValue < start - epsilon || lineValue > end + epsilon) { + return; + } + } + return lineValue; +} + +function garbageCollect(caches, length) { + helpers$1.each(caches, function(cache) { + var gc = cache.gc; + var gcLen = gc.length / 2; + var i; + if (gcLen > length) { + for (i = 0; i < gcLen; ++i) { + delete cache.data[gc[i]]; + } + gc.splice(0, gcLen); + } + }); +} + +/** + * Returns {width, height, offset} objects for the first, last, widest, highest tick + * labels where offset indicates the anchor point offset from the top in pixels. + */ +function computeLabelSizes(ctx, tickFonts, ticks, caches) { + var length = ticks.length; + var widths = []; + var heights = []; + var offsets = []; + var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest; + + for (i = 0; i < length; ++i) { + label = ticks[i].label; + tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor; + ctx.font = fontString = tickFont.string; + cache = caches[fontString] = caches[fontString] || {data: {}, gc: []}; + lineHeight = tickFont.lineHeight; + width = height = 0; + // Undefined labels and arrays should not be measured + if (!isNullOrUndef(label) && !isArray(label)) { + width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label); + height = lineHeight; + } else if (isArray(label)) { + // if it is an array let's measure each element + for (j = 0, jlen = label.length; j < jlen; ++j) { + nestedLabel = label[j]; + // Undefined labels and arrays should not be measured + if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { + width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel); + height += lineHeight; + } + } + } + widths.push(width); + heights.push(height); + offsets.push(lineHeight / 2); + } + garbageCollect(caches, length); + + widest = widths.indexOf(Math.max.apply(null, widths)); + highest = heights.indexOf(Math.max.apply(null, heights)); + + function valueAt(idx) { + return { + width: widths[idx] || 0, + height: heights[idx] || 0, + offset: offsets[idx] || 0 + }; + } + + return { + first: valueAt(0), + last: valueAt(length - 1), + widest: valueAt(widest), + highest: valueAt(highest) + }; +} + +function getTickMarkLength(options) { + return options.drawTicks ? options.tickMarkLength : 0; +} + +function getScaleLabelHeight(options) { + var font, padding; + + if (!options.display) { + return 0; + } + + font = helpers$1.options._parseFont(options); + padding = helpers$1.options.toPadding(options.padding); + + return font.lineHeight + padding.height; +} + +function parseFontOptions(options, nestedOpts) { + return helpers$1.extend(helpers$1.options._parseFont({ + fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily), + fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize), + fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle), + lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight) + }), { + color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor]) + }); +} + +function parseTickFontOptions(options) { + var minor = parseFontOptions(options, options.minor); + var major = options.major.enabled ? parseFontOptions(options, options.major) : minor; + + return {minor: minor, major: major}; +} + +function nonSkipped(ticksToFilter) { + var filtered = []; + var item, index, len; + for (index = 0, len = ticksToFilter.length; index < len; ++index) { + item = ticksToFilter[index]; + if (typeof item._index !== 'undefined') { + filtered.push(item); + } + } + return filtered; +} + +function getEvenSpacing(arr) { + var len = arr.length; + var i, diff; + + if (len < 2) { + return false; + } + + for (diff = arr[0], i = 1; i < len; ++i) { + if (arr[i] - arr[i - 1] !== diff) { + return false; + } + } + return diff; +} + +function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) { + var evenMajorSpacing = getEvenSpacing(majorIndices); + var spacing = (ticks.length - 1) / ticksLimit; + var factors, factor, i, ilen; + + // If the major ticks are evenly spaced apart, place the minor ticks + // so that they divide the major ticks into even chunks + if (!evenMajorSpacing) { + return Math.max(spacing, 1); + } + + factors = helpers$1.math._factorize(evenMajorSpacing); + for (i = 0, ilen = factors.length - 1; i < ilen; i++) { + factor = factors[i]; + if (factor > spacing) { + return factor; + } + } + return Math.max(spacing, 1); +} + +function getMajorIndices(ticks) { + var result = []; + var i, ilen; + for (i = 0, ilen = ticks.length; i < ilen; i++) { + if (ticks[i].major) { + result.push(i); + } + } + return result; +} + +function skipMajors(ticks, majorIndices, spacing) { + var count = 0; + var next = majorIndices[0]; + var i, tick; + + spacing = Math.ceil(spacing); + for (i = 0; i < ticks.length; i++) { + tick = ticks[i]; + if (i === next) { + tick._index = i; + count++; + next = majorIndices[count * spacing]; + } else { + delete tick.label; + } + } +} + +function skip(ticks, spacing, majorStart, majorEnd) { + var start = valueOrDefault$a(majorStart, 0); + var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length); + var count = 0; + var length, i, tick, next; + + spacing = Math.ceil(spacing); + if (majorEnd) { + length = majorEnd - majorStart; + spacing = length / Math.floor(length / spacing); + } + + next = start; + + while (next < 0) { + count++; + next = Math.round(start + count * spacing); + } + + for (i = Math.max(start, 0); i < end; i++) { + tick = ticks[i]; + if (i === next) { + tick._index = i; + count++; + next = Math.round(start + count * spacing); + } else { + delete tick.label; + } + } +} + +var Scale = core_element.extend({ + + zeroLineIndex: 0, + + /** + * Get the padding needed for the scale + * @method getPadding + * @private + * @returns {Padding} the necessary padding + */ + getPadding: function() { + var me = this; + return { + left: me.paddingLeft || 0, + top: me.paddingTop || 0, + right: me.paddingRight || 0, + bottom: me.paddingBottom || 0 + }; + }, + + /** + * Returns the scale tick objects ({label, major}) + * @since 2.7 + */ + getTicks: function() { + return this._ticks; + }, + + /** + * @private + */ + _getLabels: function() { + var data = this.chart.data; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; + }, + + // These methods are ordered by lifecyle. Utilities then follow. + // Any function defined here is inherited by all scale types. + // Any function can be extended by the scale type + + /** + * Provided for backward compatibility, not available anymore + * @function Chart.Scale.mergeTicksOptions + * @deprecated since version 2.8.0 + * @todo remove at version 3 + */ + mergeTicksOptions: function() { + // noop + }, + + beforeUpdate: function() { + helpers$1.callback(this.options.beforeUpdate, [this]); + }, + + /** + * @param {number} maxWidth - the max width in pixels + * @param {number} maxHeight - the max height in pixels + * @param {object} margins - the space between the edge of the other scales and edge of the chart + * This space comes from two sources: + * - padding - space that's required to show the labels at the edges of the scale + * - thickness of scales or legends in another orientation + */ + update: function(maxWidth, maxHeight, margins) { + var me = this; + var tickOpts = me.options.ticks; + var sampleSize = tickOpts.sampleSize; + var i, ilen, labels, ticks, samplingEnabled; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = helpers$1.extend({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); + + me._ticks = null; + me.ticks = null; + me._labelSizes = null; + me._maxLabelLines = 0; + me.longestLabelWidth = 0; + me.longestTextCache = me.longestTextCache || {}; + me._gridLineItems = null; + me._labelItems = null; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + + // Data min/max + me.beforeDataLimits(); + me.determineDataLimits(); + me.afterDataLimits(); + + // Ticks - `this.ticks` is now DEPRECATED! + // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member + // and must not be accessed directly from outside this class. `this.ticks` being + // around for long time and not marked as private, we can't change its structure + // without unexpected breaking changes. If you need to access the scale ticks, + // use scale.getTicks() instead. + + me.beforeBuildTicks(); + + // New implementations should return an array of objects but for BACKWARD COMPAT, + // we still support no return (`this.ticks` internally set by calling this method). + ticks = me.buildTicks() || []; + + // Allow modification of ticks in callback. + ticks = me.afterBuildTicks(ticks) || ticks; + + // Ensure ticks contains ticks in new tick format + if ((!ticks || !ticks.length) && me.ticks) { + ticks = []; + for (i = 0, ilen = me.ticks.length; i < ilen; ++i) { + ticks.push({ + value: me.ticks[i], + major: false + }); + } + } + + me._ticks = ticks; + + // Compute tick rotation and fit using a sampled subset of labels + // We generally don't need to compute the size of every single label for determining scale size + samplingEnabled = sampleSize < ticks.length; + labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks); + + // _configure is called twice, once here, once from core.controller.updateLayout. + // Here we haven't been positioned yet, but dimensions are correct. + // Variables set in _configure are needed for calculateTickRotation, and + // it's ok that coordinates are not correct there, only dimensions matter. + me._configure(); + + // Tick Rotation + me.beforeCalculateTickRotation(); + me.calculateTickRotation(); + me.afterCalculateTickRotation(); + + me.beforeFit(); + me.fit(); + me.afterFit(); + + // Auto-skip + me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks; + + if (samplingEnabled) { + // Generate labels using all non-skipped ticks + labels = me._convertTicksToLabels(me._ticksToDraw); + } + + me.ticks = labels; // BACKWARD COMPATIBILITY + + // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change! + + me.afterUpdate(); + + // TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused + // make maxWidth and maxHeight private + return me.minSize; + }, + + /** + * @private + */ + _configure: function() { + var me = this; + var reversePixels = me.options.ticks.reverse; + var startPixel, endPixel; + + if (me.isHorizontal()) { + startPixel = me.left; + endPixel = me.right; + } else { + startPixel = me.top; + endPixel = me.bottom; + // by default vertical scales are from bottom to top, so pixels are reversed + reversePixels = !reversePixels; + } + me._startPixel = startPixel; + me._endPixel = endPixel; + me._reversePixels = reversePixels; + me._length = endPixel - startPixel; + }, + + afterUpdate: function() { + helpers$1.callback(this.options.afterUpdate, [this]); + }, + + // + + beforeSetDimensions: function() { + helpers$1.callback(this.options.beforeSetDimensions, [this]); + }, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + }, + afterSetDimensions: function() { + helpers$1.callback(this.options.afterSetDimensions, [this]); + }, + + // Data limits + beforeDataLimits: function() { + helpers$1.callback(this.options.beforeDataLimits, [this]); + }, + determineDataLimits: helpers$1.noop, + afterDataLimits: function() { + helpers$1.callback(this.options.afterDataLimits, [this]); + }, + + // + beforeBuildTicks: function() { + helpers$1.callback(this.options.beforeBuildTicks, [this]); + }, + buildTicks: helpers$1.noop, + afterBuildTicks: function(ticks) { + var me = this; + // ticks is empty for old axis implementations here + if (isArray(ticks) && ticks.length) { + return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]); + } + // Support old implementations (that modified `this.ticks` directly in buildTicks) + me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks; + return ticks; + }, + + beforeTickToLabelConversion: function() { + helpers$1.callback(this.options.beforeTickToLabelConversion, [this]); + }, + convertTicksToLabels: function() { + var me = this; + // Convert ticks to strings + var tickOpts = me.options.ticks; + me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); + }, + afterTickToLabelConversion: function() { + helpers$1.callback(this.options.afterTickToLabelConversion, [this]); + }, + + // + + beforeCalculateTickRotation: function() { + helpers$1.callback(this.options.beforeCalculateTickRotation, [this]); + }, + calculateTickRotation: function() { + var me = this; + var options = me.options; + var tickOpts = options.ticks; + var numTicks = me.getTicks().length; + var minRotation = tickOpts.minRotation || 0; + var maxRotation = tickOpts.maxRotation; + var labelRotation = minRotation; + var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal; + + if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) { + me.labelRotation = minRotation; + return; + } + + labelSizes = me._getLabelSizes(); + maxLabelWidth = labelSizes.widest.width; + maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset; + + // Estimate the width of each grid based on the canvas width, the maximum + // label width and the number of tick intervals + maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth); + tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1); + + // Allow 3 pixels x2 padding either side for label readability + if (maxLabelWidth + 6 > tickWidth) { + tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); + maxHeight = me.maxHeight - getTickMarkLength(options.gridLines) + - tickOpts.padding - getScaleLabelHeight(options.scaleLabel); + maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); + labelRotation = helpers$1.toDegrees(Math.min( + Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)), + Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal) + )); + labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); + } + + me.labelRotation = labelRotation; + }, + afterCalculateTickRotation: function() { + helpers$1.callback(this.options.afterCalculateTickRotation, [this]); + }, + + // + + beforeFit: function() { + helpers$1.callback(this.options.beforeFit, [this]); + }, + fit: function() { + var me = this; + // Reset + var minSize = me.minSize = { + width: 0, + height: 0 + }; + + var chart = me.chart; + var opts = me.options; + var tickOpts = opts.ticks; + var scaleLabelOpts = opts.scaleLabel; + var gridLineOpts = opts.gridLines; + var display = me._isVisible(); + var isBottom = opts.position === 'bottom'; + var isHorizontal = me.isHorizontal(); + + // Width + if (isHorizontal) { + minSize.width = me.maxWidth; + } else if (display) { + minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); + } + + // height + if (!isHorizontal) { + minSize.height = me.maxHeight; // fill all the height + } else if (display) { + minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); + } + + // Don't bother fitting the ticks if we are not showing the labels + if (tickOpts.display && display) { + var tickFonts = parseTickFontOptions(tickOpts); + var labelSizes = me._getLabelSizes(); + var firstLabelSize = labelSizes.first; + var lastLabelSize = labelSizes.last; + var widestLabelSize = labelSizes.widest; + var highestLabelSize = labelSizes.highest; + var lineSpace = tickFonts.minor.lineHeight * 0.4; + var tickPadding = tickOpts.padding; + + if (isHorizontal) { + // A horizontal axis is more constrained by the height. + var isRotated = me.labelRotation !== 0; + var angleRadians = helpers$1.toRadians(me.labelRotation); + var cosRotation = Math.cos(angleRadians); + var sinRotation = Math.sin(angleRadians); + + var labelHeight = sinRotation * widestLabelSize.width + + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0)) + + (isRotated ? 0 : lineSpace); // padding + + minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); + + var offsetLeft = me.getPixelForTick(0) - me.left; + var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1); + var paddingLeft, paddingRight; + + // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned + // which means that the right padding is dominated by the font height + if (isRotated) { + paddingLeft = isBottom ? + cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : + sinRotation * (firstLabelSize.height - firstLabelSize.offset); + paddingRight = isBottom ? + sinRotation * (lastLabelSize.height - lastLabelSize.offset) : + cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; + } else { + paddingLeft = firstLabelSize.width / 2; + paddingRight = lastLabelSize.width / 2; + } + + // Adjust padding taking into account changes in offsets + // and add 3 px to move away from canvas edges + me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3; + me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3; + } else { + // A vertical axis is more constrained by the width. Labels are the + // dominant factor here, so get that length first and account for padding + var labelWidth = tickOpts.mirror ? 0 : + // use lineSpace for consistency with horizontal axis + // tickPadding is not implemented for horizontal + widestLabelSize.width + tickPadding + lineSpace; + + minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); + + me.paddingTop = firstLabelSize.height / 2; + me.paddingBottom = lastLabelSize.height / 2; + } + } + + me.handleMargins(); + + if (isHorizontal) { + me.width = me._length = chart.width - me.margins.left - me.margins.right; + me.height = minSize.height; + } else { + me.width = minSize.width; + me.height = me._length = chart.height - me.margins.top - me.margins.bottom; + } + }, + + /** + * Handle margins and padding interactions + * @private + */ + handleMargins: function() { + var me = this; + if (me.margins) { + me.margins.left = Math.max(me.paddingLeft, me.margins.left); + me.margins.top = Math.max(me.paddingTop, me.margins.top); + me.margins.right = Math.max(me.paddingRight, me.margins.right); + me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom); + } + }, + + afterFit: function() { + helpers$1.callback(this.options.afterFit, [this]); + }, + + // Shared Methods + isHorizontal: function() { + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + }, + isFullWidth: function() { + return this.options.fullWidth; + }, + + // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not + getRightValue: function(rawValue) { + // Null and undefined values first + if (isNullOrUndef(rawValue)) { + return NaN; + } + // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values + if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) { + return NaN; + } + + // If it is in fact an object, dive in one more level + if (rawValue) { + if (this.isHorizontal()) { + if (rawValue.x !== undefined) { + return this.getRightValue(rawValue.x); + } + } else if (rawValue.y !== undefined) { + return this.getRightValue(rawValue.y); + } + } + + // Value is good, return it + return rawValue; + }, + + _convertTicksToLabels: function(ticks) { + var me = this; + var labels, i, ilen; + + me.ticks = ticks.map(function(tick) { + return tick.value; + }); + + me.beforeTickToLabelConversion(); + + // New implementations should return the formatted tick labels but for BACKWARD + // COMPAT, we still support no return (`this.ticks` internally changed by calling + // this method and supposed to contain only string values). + labels = me.convertTicksToLabels(ticks) || me.ticks; + + me.afterTickToLabelConversion(); + + // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + ticks[i].label = labels[i]; + } + + return labels; + }, + + /** + * @private + */ + _getLabelSizes: function() { + var me = this; + var labelSizes = me._labelSizes; + + if (!labelSizes) { + me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache); + me.longestLabelWidth = labelSizes.widest.width; + } + + return labelSizes; + }, + + /** + * @private + */ + _parseValue: function(value) { + var start, end, min, max; + + if (isArray(value)) { + start = +this.getRightValue(value[0]); + end = +this.getRightValue(value[1]); + min = Math.min(start, end); + max = Math.max(start, end); + } else { + value = +this.getRightValue(value); + start = undefined; + end = value; + min = value; + max = value; + } + + return { + min: min, + max: max, + start: start, + end: end + }; + }, + + /** + * @private + */ + _getScaleLabel: function(rawValue) { + var v = this._parseValue(rawValue); + if (v.start !== undefined) { + return '[' + v.start + ', ' + v.end + ']'; + } + + return +this.getRightValue(rawValue); + }, + + /** + * Used to get the value to display in the tooltip for the data at the given index + * @param index + * @param datasetIndex + */ + getLabelForIndex: helpers$1.noop, + + /** + * Returns the location of the given data point. Value can either be an index or a numerical value + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param value + * @param index + * @param datasetIndex + */ + getPixelForValue: helpers$1.noop, + + /** + * Used to get the data value from a given pixel. This is the inverse of getPixelForValue + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param pixel + */ + getValueForPixel: helpers$1.noop, + + /** + * Returns the location of the tick at the given index + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForTick: function(index) { + var me = this; + var offset = me.options.offset; + var numTicks = me._ticks.length; + var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1); + + return index < 0 || index > numTicks - 1 + ? null + : me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0)); + }, + + /** + * Utility for getting the pixel location of a percentage of scale + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForDecimal: function(decimal) { + var me = this; + + if (me._reversePixels) { + decimal = 1 - decimal; + } + + return me._startPixel + decimal * me._length; + }, + + getDecimalForPixel: function(pixel) { + var decimal = (pixel - this._startPixel) / this._length; + return this._reversePixels ? 1 - decimal : decimal; + }, + + /** + * Returns the pixel for the minimum chart value + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getBasePixel: function() { + return this.getPixelForValue(this.getBaseValue()); + }, + + getBaseValue: function() { + var me = this; + var min = me.min; + var max = me.max; + + return me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0; + }, + + /** + * Returns a subset of ticks to be plotted to avoid overlapping labels. + * @private + */ + _autoSkip: function(ticks) { + var me = this; + var tickOpts = me.options.ticks; + var axisLength = me._length; + var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1; + var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; + var numMajorIndices = majorIndices.length; + var first = majorIndices[0]; + var last = majorIndices[numMajorIndices - 1]; + var i, ilen, spacing, avgMajorSpacing; + + // If there are too many major ticks to display them all + if (numMajorIndices > ticksLimit) { + skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit); + return nonSkipped(ticks); + } + + spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit); + + if (numMajorIndices > 0) { + for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) { + skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]); + } + avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null; + skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); + skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); + return nonSkipped(ticks); + } + skip(ticks, spacing); + return nonSkipped(ticks); + }, + + /** + * @private + */ + _tickSize: function() { + var me = this; + var optionTicks = me.options.ticks; + + // Calculate space needed by label in axis direction. + var rot = helpers$1.toRadians(me.labelRotation); + var cos = Math.abs(Math.cos(rot)); + var sin = Math.abs(Math.sin(rot)); + + var labelSizes = me._getLabelSizes(); + var padding = optionTicks.autoSkipPadding || 0; + var w = labelSizes ? labelSizes.widest.width + padding : 0; + var h = labelSizes ? labelSizes.highest.height + padding : 0; + + // Calculate space needed for 1 tick in axis direction. + return me.isHorizontal() + ? h * cos > w * sin ? w / cos : h / sin + : h * sin < w * cos ? h / cos : w / sin; + }, + + /** + * @private + */ + _isVisible: function() { + var me = this; + var chart = me.chart; + var display = me.options.display; + var i, ilen, meta; + + if (display !== 'auto') { + return !!display; + } + + // When 'auto', the scale is visible if at least one associated dataset is visible. + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + if (meta.xAxisID === me.id || meta.yAxisID === me.id) { + return true; + } + } + } + + return false; + }, + + /** + * @private + */ + _computeGridLineItems: function(chartArea) { + var me = this; + var chart = me.chart; + var options = me.options; + var gridLines = options.gridLines; + var position = options.position; + var offsetGridLines = gridLines.offsetGridLines; + var isHorizontal = me.isHorizontal(); + var ticks = me._ticksToDraw; + var ticksLength = ticks.length + (offsetGridLines ? 1 : 0); + + var tl = getTickMarkLength(gridLines); + var items = []; + var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; + var axisHalfWidth = axisWidth / 2; + var alignPixel = helpers$1._alignPixel; + var alignBorderValue = function(pixel) { + return alignPixel(chart, pixel, axisWidth); + }; + var borderValue, i, tick, lineValue, alignedLineValue; + var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset; + + if (position === 'top') { + borderValue = alignBorderValue(me.bottom); + ty1 = me.bottom - tl; + ty2 = borderValue - axisHalfWidth; + y1 = alignBorderValue(chartArea.top) + axisHalfWidth; + y2 = chartArea.bottom; + } else if (position === 'bottom') { + borderValue = alignBorderValue(me.top); + y1 = chartArea.top; + y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; + ty1 = borderValue + axisHalfWidth; + ty2 = me.top + tl; + } else if (position === 'left') { + borderValue = alignBorderValue(me.right); + tx1 = me.right - tl; + tx2 = borderValue - axisHalfWidth; + x1 = alignBorderValue(chartArea.left) + axisHalfWidth; + x2 = chartArea.right; + } else { + borderValue = alignBorderValue(me.left); + x1 = chartArea.left; + x2 = alignBorderValue(chartArea.right) - axisHalfWidth; + tx1 = borderValue + axisHalfWidth; + tx2 = me.left + tl; + } + + for (i = 0; i < ticksLength; ++i) { + tick = ticks[i] || {}; + + // autoskipper skipped this tick (#4635) + if (isNullOrUndef(tick.label) && i < ticks.length) { + continue; + } + + if (i === me.zeroLineIndex && options.offset === offsetGridLines) { + // Draw the first index specially + lineWidth = gridLines.zeroLineWidth; + lineColor = gridLines.zeroLineColor; + borderDash = gridLines.zeroLineBorderDash || []; + borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0; + } else { + lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1); + lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)'); + borderDash = gridLines.borderDash || []; + borderDashOffset = gridLines.borderDashOffset || 0.0; + } + + lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines); + + // Skip if the pixel is out of the range + if (lineValue === undefined) { + continue; + } + + alignedLineValue = alignPixel(chart, lineValue, lineWidth); + + if (isHorizontal) { + tx1 = tx2 = x1 = x2 = alignedLineValue; + } else { + ty1 = ty2 = y1 = y2 = alignedLineValue; + } + + items.push({ + tx1: tx1, + ty1: ty1, + tx2: tx2, + ty2: ty2, + x1: x1, + y1: y1, + x2: x2, + y2: y2, + width: lineWidth, + color: lineColor, + borderDash: borderDash, + borderDashOffset: borderDashOffset, + }); + } + + items.ticksLength = ticksLength; + items.borderValue = borderValue; + + return items; + }, + + /** + * @private + */ + _computeLabelItems: function() { + var me = this; + var options = me.options; + var optionTicks = options.ticks; + var position = options.position; + var isMirrored = optionTicks.mirror; + var isHorizontal = me.isHorizontal(); + var ticks = me._ticksToDraw; + var fonts = parseTickFontOptions(optionTicks); + var tickPadding = optionTicks.padding; + var tl = getTickMarkLength(options.gridLines); + var rotation = -helpers$1.toRadians(me.labelRotation); + var items = []; + var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; + + if (position === 'top') { + y = me.bottom - tl - tickPadding; + textAlign = !rotation ? 'center' : 'left'; + } else if (position === 'bottom') { + y = me.top + tl + tickPadding; + textAlign = !rotation ? 'center' : 'right'; + } else if (position === 'left') { + x = me.right - (isMirrored ? 0 : tl) - tickPadding; + textAlign = isMirrored ? 'left' : 'right'; + } else { + x = me.left + (isMirrored ? 0 : tl) + tickPadding; + textAlign = isMirrored ? 'right' : 'left'; + } + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + tick = ticks[i]; + label = tick.label; + + // autoskipper skipped this tick (#4635) + if (isNullOrUndef(label)) { + continue; + } + + pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset; + font = tick.major ? fonts.major : fonts.minor; + lineHeight = font.lineHeight; + lineCount = isArray(label) ? label.length : 1; + + if (isHorizontal) { + x = pixel; + textOffset = position === 'top' + ? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight + : (!rotation ? 0.5 : 0) * lineHeight; + } else { + y = pixel; + textOffset = (1 - lineCount) * lineHeight / 2; + } + + items.push({ + x: x, + y: y, + rotation: rotation, + label: label, + font: font, + textOffset: textOffset, + textAlign: textAlign + }); + } + + return items; + }, + + /** + * @private + */ + _drawGrid: function(chartArea) { + var me = this; + var gridLines = me.options.gridLines; + + if (!gridLines.display) { + return; + } + + var ctx = me.ctx; + var chart = me.chart; + var alignPixel = helpers$1._alignPixel; + var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; + var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea)); + var width, color, i, ilen, item; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + width = item.width; + color = item.color; + + if (width && color) { + ctx.save(); + ctx.lineWidth = width; + ctx.strokeStyle = color; + if (ctx.setLineDash) { + ctx.setLineDash(item.borderDash); + ctx.lineDashOffset = item.borderDashOffset; + } + + ctx.beginPath(); + + if (gridLines.drawTicks) { + ctx.moveTo(item.tx1, item.ty1); + ctx.lineTo(item.tx2, item.ty2); + } + + if (gridLines.drawOnChartArea) { + ctx.moveTo(item.x1, item.y1); + ctx.lineTo(item.x2, item.y2); + } + + ctx.stroke(); + ctx.restore(); + } + } + + if (axisWidth) { + // Draw the line at the edge of the axis + var firstLineWidth = axisWidth; + var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1); + var borderValue = items.borderValue; + var x1, x2, y1, y2; + + if (me.isHorizontal()) { + x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2; + x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2; + y1 = y2 = borderValue; + } else { + y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2; + y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2; + x1 = x2 = borderValue; + } + + ctx.lineWidth = axisWidth; + ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0); + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + } + }, + + /** + * @private + */ + _drawLabels: function() { + var me = this; + var optionTicks = me.options.ticks; + + if (!optionTicks.display) { + return; + } + + var ctx = me.ctx; + var items = me._labelItems || (me._labelItems = me._computeLabelItems()); + var i, j, ilen, jlen, item, tickFont, label, y; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + tickFont = item.font; + + // Make sure we draw text in the correct color and font + ctx.save(); + ctx.translate(item.x, item.y); + ctx.rotate(item.rotation); + ctx.font = tickFont.string; + ctx.fillStyle = tickFont.color; + ctx.textBaseline = 'middle'; + ctx.textAlign = item.textAlign; + + label = item.label; + y = item.textOffset; + if (isArray(label)) { + for (j = 0, jlen = label.length; j < jlen; ++j) { + // We just make sure the multiline element is a string here.. + ctx.fillText('' + label[j], 0, y); + y += tickFont.lineHeight; + } + } else { + ctx.fillText(label, 0, y); + } + ctx.restore(); + } + }, + + /** + * @private + */ + _drawTitle: function() { + var me = this; + var ctx = me.ctx; + var options = me.options; + var scaleLabel = options.scaleLabel; + + if (!scaleLabel.display) { + return; + } + + var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor); + var scaleLabelFont = helpers$1.options._parseFont(scaleLabel); + var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding); + var halfLineHeight = scaleLabelFont.lineHeight / 2; + var position = options.position; + var rotation = 0; + var scaleLabelX, scaleLabelY; + + if (me.isHorizontal()) { + scaleLabelX = me.left + me.width / 2; // midpoint of the width + scaleLabelY = position === 'bottom' + ? me.bottom - halfLineHeight - scaleLabelPadding.bottom + : me.top + halfLineHeight + scaleLabelPadding.top; + } else { + var isLeft = position === 'left'; + scaleLabelX = isLeft + ? me.left + halfLineHeight + scaleLabelPadding.top + : me.right - halfLineHeight - scaleLabelPadding.top; + scaleLabelY = me.top + me.height / 2; + rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; + } + + ctx.save(); + ctx.translate(scaleLabelX, scaleLabelY); + ctx.rotate(rotation); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = scaleLabelFontColor; // render in correct colour + ctx.font = scaleLabelFont.string; + ctx.fillText(scaleLabel.labelString, 0, 0); + ctx.restore(); + }, + + draw: function(chartArea) { + var me = this; + + if (!me._isVisible()) { + return; + } + + me._drawGrid(chartArea); + me._drawTitle(); + me._drawLabels(); + }, + + /** + * @private + */ + _layers: function() { + var me = this; + var opts = me.options; + var tz = opts.ticks && opts.ticks.z || 0; + var gz = opts.gridLines && opts.gridLines.z || 0; + + if (!me._isVisible() || tz === gz || me.draw !== me._draw) { + // backward compatibility: draw has been overridden by custom scale + return [{ + z: tz, + draw: function() { + me.draw.apply(me, arguments); + } + }]; + } + + return [{ + z: gz, + draw: function() { + me._drawGrid.apply(me, arguments); + me._drawTitle.apply(me, arguments); + } + }, { + z: tz, + draw: function() { + me._drawLabels.apply(me, arguments); + } + }]; + }, + + /** + * @private + */ + _getMatchingVisibleMetas: function(type) { + var me = this; + var isHorizontal = me.isHorizontal(); + return me.chart._getSortedVisibleDatasetMetas() + .filter(function(meta) { + return (!type || meta.type === type) + && (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id); + }); + } +}); + +Scale.prototype._draw = Scale.prototype.draw; + +var core_scale = Scale; + +var isNullOrUndef$1 = helpers$1.isNullOrUndef; + +var defaultConfig = { + position: 'bottom' +}; + +var scale_category = core_scale.extend({ + determineDataLimits: function() { + var me = this; + var labels = me._getLabels(); + var ticksOpts = me.options.ticks; + var min = ticksOpts.min; + var max = ticksOpts.max; + var minIndex = 0; + var maxIndex = labels.length - 1; + var findIndex; + + if (min !== undefined) { + // user specified min value + findIndex = labels.indexOf(min); + if (findIndex >= 0) { + minIndex = findIndex; + } + } + + if (max !== undefined) { + // user specified max value + findIndex = labels.indexOf(max); + if (findIndex >= 0) { + maxIndex = findIndex; + } + } + + me.minIndex = minIndex; + me.maxIndex = maxIndex; + me.min = labels[minIndex]; + me.max = labels[maxIndex]; + }, + + buildTicks: function() { + var me = this; + var labels = me._getLabels(); + var minIndex = me.minIndex; + var maxIndex = me.maxIndex; + + // If we are viewing some subset of labels, slice the original array + me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1); + }, + + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var chart = me.chart; + + if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) { + return me.getRightValue(chart.data.datasets[datasetIndex].data[index]); + } + + return me._getLabels()[index]; + }, + + _configure: function() { + var me = this; + var offset = me.options.offset; + var ticks = me.ticks; + + core_scale.prototype._configure.call(me); + + if (!me.isHorizontal()) { + // For backward compatibility, vertical category scale reverse is inverted. + me._reversePixels = !me._reversePixels; + } + + if (!ticks) { + return; + } + + me._startValue = me.minIndex - (offset ? 0.5 : 0); + me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1); + }, + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var valueCategory, labels, idx; + + if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) { + value = me.chart.data.datasets[datasetIndex].data[index]; + } + + // If value is a data object, then index is the index in the data array, + // not the index of the scale. We need to change that. + if (!isNullOrUndef$1(value)) { + valueCategory = me.isHorizontal() ? value.x : value.y; + } + if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { + labels = me._getLabels(); + value = helpers$1.valueOrDefault(valueCategory, value); + idx = labels.indexOf(value); + index = idx !== -1 ? idx : index; + if (isNaN(index)) { + index = value; + } + } + return me.getPixelForDecimal((index - me._startValue) / me._valueRange); + }, + + getPixelForTick: function(index) { + var ticks = this.ticks; + return index < 0 || index > ticks.length - 1 + ? null + : this.getPixelForValue(ticks[index], index + this.minIndex); + }, + + getValueForPixel: function(pixel) { + var me = this; + var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange); + return Math.min(Math.max(value, 0), me.ticks.length - 1); + }, + + getBasePixel: function() { + return this.bottom; + } +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults = defaultConfig; +scale_category._defaults = _defaults; + +var noop = helpers$1.noop; +var isNullOrUndef$2 = helpers$1.isNullOrUndef; + +/** + * Generate a set of linear ticks + * @param generationOptions the options used to generate the ticks + * @param dataRange the range of the data + * @returns {number[]} array of tick values + */ +function generateTicks(generationOptions, dataRange) { + var ticks = []; + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + var MIN_SPACING = 1e-14; + var stepSize = generationOptions.stepSize; + var unit = stepSize || 1; + var maxNumSpaces = generationOptions.maxTicks - 1; + var min = generationOptions.min; + var max = generationOptions.max; + var precision = generationOptions.precision; + var rmin = dataRange.min; + var rmax = dataRange.max; + var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; + var factor, niceMin, niceMax, numSpaces; + + // Beyond MIN_SPACING floating point numbers being to lose precision + // such that we can't do the math necessary to generate ticks + if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) { + return [rmin, rmax]; + } + + numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); + if (numSpaces > maxNumSpaces) { + // If the calculated num of spaces exceeds maxNumSpaces, recalculate it + spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; + } + + if (stepSize || isNullOrUndef$2(precision)) { + // If a precision is not specified, calculate factor based on spacing + factor = Math.pow(10, helpers$1._decimalPlaces(spacing)); + } else { + // If the user specified a precision, round to that number of decimal places + factor = Math.pow(10, precision); + spacing = Math.ceil(spacing * factor) / factor; + } + + niceMin = Math.floor(rmin / spacing) * spacing; + niceMax = Math.ceil(rmax / spacing) * spacing; + + // If min, max and stepSize is set and they make an evenly spaced scale use it. + if (stepSize) { + // If very close to our whole number, use it. + if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) { + niceMin = min; + } + if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) { + niceMax = max; + } + } + + numSpaces = (niceMax - niceMin) / spacing; + // If very close to our rounded value, use it. + if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } + + niceMin = Math.round(niceMin * factor) / factor; + niceMax = Math.round(niceMax * factor) / factor; + ticks.push(isNullOrUndef$2(min) ? niceMin : min); + for (var j = 1; j < numSpaces; ++j) { + ticks.push(Math.round((niceMin + j * spacing) * factor) / factor); + } + ticks.push(isNullOrUndef$2(max) ? niceMax : max); + + return ticks; +} + +var scale_linearbase = core_scale.extend({ + getRightValue: function(value) { + if (typeof value === 'string') { + return +value; + } + return core_scale.prototype.getRightValue.call(this, value); + }, + + handleTickRangeOptions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (tickOpts.beginAtZero) { + var minSign = helpers$1.sign(me.min); + var maxSign = helpers$1.sign(me.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + me.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the bottom down to 0 + me.min = 0; + } + } + + var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; + var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; + + if (tickOpts.min !== undefined) { + me.min = tickOpts.min; + } else if (tickOpts.suggestedMin !== undefined) { + if (me.min === null) { + me.min = tickOpts.suggestedMin; + } else { + me.min = Math.min(me.min, tickOpts.suggestedMin); + } + } + + if (tickOpts.max !== undefined) { + me.max = tickOpts.max; + } else if (tickOpts.suggestedMax !== undefined) { + if (me.max === null) { + me.max = tickOpts.suggestedMax; + } else { + me.max = Math.max(me.max, tickOpts.suggestedMax); + } + } + + if (setMin !== setMax) { + // We set the min or the max but not both. + // So ensure that our range is good + // Inverted or 0 length range can happen when + // ticks.min is set, and no datasets are visible + if (me.min >= me.max) { + if (setMin) { + me.max = me.min + 1; + } else { + me.min = me.max - 1; + } + } + } + + if (me.min === me.max) { + me.max++; + + if (!tickOpts.beginAtZero) { + me.min--; + } + } + }, + + getTickLimit: function() { + var me = this; + var tickOpts = me.options.ticks; + var stepSize = tickOpts.stepSize; + var maxTicksLimit = tickOpts.maxTicksLimit; + var maxTicks; + + if (stepSize) { + maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; + } else { + maxTicks = me._computeTickLimit(); + maxTicksLimit = maxTicksLimit || 11; + } + + if (maxTicksLimit) { + maxTicks = Math.min(maxTicksLimit, maxTicks); + } + + return maxTicks; + }, + + _computeTickLimit: function() { + return Number.POSITIVE_INFINITY; + }, + + handleDirectionalChanges: noop, + + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph. Make sure we always have at least 2 ticks + var maxTicks = me.getTickLimit(); + maxTicks = Math.max(2, maxTicks); + + var numericGeneratorOptions = { + maxTicks: maxTicks, + min: tickOpts.min, + max: tickOpts.max, + precision: tickOpts.precision, + stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) + }; + var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); + + me.handleDirectionalChanges(); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers$1.max(ticks); + me.min = helpers$1.min(ticks); + + if (tickOpts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, + + convertTicksToLabels: function() { + var me = this; + me.ticksAsNumbers = me.ticks.slice(); + me.zeroLineIndex = me.ticks.indexOf(0); + + core_scale.prototype.convertTicksToLabels.call(me); + }, + + _configure: function() { + var me = this; + var ticks = me.getTicks(); + var start = me.min; + var end = me.max; + var offset; + + core_scale.prototype._configure.call(me); + + if (me.options.offset && ticks.length) { + offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; + start -= offset; + end += offset; + } + me._startValue = start; + me._endValue = end; + me._valueRange = end - start; + } +}); + +var defaultConfig$1 = { + position: 'left', + ticks: { + callback: core_ticks.formatters.linear + } +}; + +var DEFAULT_MIN = 0; +var DEFAULT_MAX = 1; + +function getOrCreateStack(stacks, stacked, meta) { + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + stacked === undefined && meta.stack === undefined ? meta.index : '', + meta.stack + ].join('.'); + + if (stacks[key] === undefined) { + stacks[key] = { + pos: [], + neg: [] + }; + } + + return stacks[key]; +} + +function stackData(scale, stacks, meta, data) { + var opts = scale.options; + var stacked = opts.stacked; + var stack = getOrCreateStack(stacks, stacked, meta); + var pos = stack.pos; + var neg = stack.neg; + var ilen = data.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = scale._parseValue(data[i]); + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { + continue; + } + + pos[i] = pos[i] || 0; + neg[i] = neg[i] || 0; + + if (opts.relativePoints) { + pos[i] = 100; + } else if (value.min < 0 || value.max < 0) { + neg[i] += value.min; + } else { + pos[i] += value.max; + } + } +} + +function updateMinMax(scale, meta, data) { + var ilen = data.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = scale._parseValue(data[i]); + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { + continue; + } + + scale.min = Math.min(scale.min, value.min); + scale.max = Math.max(scale.max, value.max); + } +} + +var scale_linear = scale_linearbase.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var datasets = chart.data.datasets; + var metasets = me._getMatchingVisibleMetas(); + var hasStacks = opts.stacked; + var stacks = {}; + var ilen = metasets.length; + var i, meta, data, values; + + me.min = Number.POSITIVE_INFINITY; + me.max = Number.NEGATIVE_INFINITY; + + if (hasStacks === undefined) { + for (i = 0; !hasStacks && i < ilen; ++i) { + meta = metasets[i]; + hasStacks = meta.stack !== undefined; + } + } + + for (i = 0; i < ilen; ++i) { + meta = metasets[i]; + data = datasets[meta.index].data; + if (hasStacks) { + stackData(me, stacks, meta, data); + } else { + updateMinMax(me, meta, data); + } + } + + helpers$1.each(stacks, function(stackValues) { + values = stackValues.pos.concat(stackValues.neg); + me.min = Math.min(me.min, helpers$1.min(values)); + me.max = Math.max(me.max, helpers$1.max(values)); + }); + + me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; + me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, + + // Returns the maximum number of ticks based on the scale dimension + _computeTickLimit: function() { + var me = this; + var tickFont; + + if (me.isHorizontal()) { + return Math.ceil(me.width / 40); + } + tickFont = helpers$1.options._parseFont(me.options.ticks); + return Math.ceil(me.height / tickFont.lineHeight); + }, + + // Called after the ticks are built. We need + handleDirectionalChanges: function() { + if (!this.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + }, + + getLabelForIndex: function(index, datasetIndex) { + return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); + }, + + // Utils + getPixelForValue: function(value) { + var me = this; + return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange); + }, + + getValueForPixel: function(pixel) { + return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; + }, + + getPixelForTick: function(index) { + var ticks = this.ticksAsNumbers; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index]); + } +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults$1 = defaultConfig$1; +scale_linear._defaults = _defaults$1; + +var valueOrDefault$b = helpers$1.valueOrDefault; +var log10 = helpers$1.math.log10; + +/** + * Generate a set of logarithmic ticks + * @param generationOptions the options used to generate the ticks + * @param dataRange the range of the data + * @returns {number[]} array of tick values + */ +function generateTicks$1(generationOptions, dataRange) { + var ticks = []; + + var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); + + var endExp = Math.floor(log10(dataRange.max)); + var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); + var exp, significand; + + if (tickVal === 0) { + exp = Math.floor(log10(dataRange.minNotZero)); + significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); + + ticks.push(tickVal); + tickVal = significand * Math.pow(10, exp); + } else { + exp = Math.floor(log10(tickVal)); + significand = Math.floor(tickVal / Math.pow(10, exp)); + } + var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; + + do { + ticks.push(tickVal); + + ++significand; + if (significand === 10) { + significand = 1; + ++exp; + precision = exp >= 0 ? 1 : precision; + } + + tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; + } while (exp < endExp || (exp === endExp && significand < endSignificand)); + + var lastTick = valueOrDefault$b(generationOptions.max, tickVal); + ticks.push(lastTick); + + return ticks; +} + +var defaultConfig$2 = { + position: 'left', + + // label settings + ticks: { + callback: core_ticks.formatters.logarithmic + } +}; + +// TODO(v3): change this to positiveOrDefault +function nonNegativeOrDefault(value, defaultValue) { + return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue; +} + +var scale_logarithmic = core_scale.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var datasets = chart.data.datasets; + var isHorizontal = me.isHorizontal(); + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } + var datasetIndex, meta, value, data, i, ilen; + + // Calculate Range + me.min = Number.POSITIVE_INFINITY; + me.max = Number.NEGATIVE_INFINITY; + me.minNotZero = Number.POSITIVE_INFINITY; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { + meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + break; + } + } + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { + meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = []; + } + + data = datasets[datasetIndex].data; + for (i = 0, ilen = data.length; i < ilen; i++) { + var values = valuesPerStack[key]; + value = me._parseValue(data[i]); + // invalid, hidden and negative values are ignored + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { + continue; + } + values[i] = values[i] || 0; + values[i] += value.max; + } + } + } + + helpers$1.each(valuesPerStack, function(valuesForType) { + if (valuesForType.length > 0) { + var minVal = helpers$1.min(valuesForType); + var maxVal = helpers$1.max(valuesForType); + me.min = Math.min(me.min, minVal); + me.max = Math.max(me.max, maxVal); + } + }); + + } else { + for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { + meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + data = datasets[datasetIndex].data; + for (i = 0, ilen = data.length; i < ilen; i++) { + value = me._parseValue(data[i]); + // invalid, hidden and negative values are ignored + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { + continue; + } + + me.min = Math.min(value.min, me.min); + me.max = Math.max(value.max, me.max); + + if (value.min !== 0) { + me.minNotZero = Math.min(value.min, me.minNotZero); + } + } + } + } + } + + me.min = helpers$1.isFinite(me.min) ? me.min : null; + me.max = helpers$1.isFinite(me.max) ? me.max : null; + me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null; + + // Common base implementation to handle ticks.min, ticks.max + this.handleTickRangeOptions(); + }, + + handleTickRangeOptions: function() { + var me = this; + var tickOpts = me.options.ticks; + var DEFAULT_MIN = 1; + var DEFAULT_MAX = 10; + + me.min = nonNegativeOrDefault(tickOpts.min, me.min); + me.max = nonNegativeOrDefault(tickOpts.max, me.max); + + if (me.min === me.max) { + if (me.min !== 0 && me.min !== null) { + me.min = Math.pow(10, Math.floor(log10(me.min)) - 1); + me.max = Math.pow(10, Math.floor(log10(me.max)) + 1); + } else { + me.min = DEFAULT_MIN; + me.max = DEFAULT_MAX; + } + } + if (me.min === null) { + me.min = Math.pow(10, Math.floor(log10(me.max)) - 1); + } + if (me.max === null) { + me.max = me.min !== 0 + ? Math.pow(10, Math.floor(log10(me.min)) + 1) + : DEFAULT_MAX; + } + if (me.minNotZero === null) { + if (me.min > 0) { + me.minNotZero = me.min; + } else if (me.max < 1) { + me.minNotZero = Math.pow(10, Math.floor(log10(me.max))); + } else { + me.minNotZero = DEFAULT_MIN; + } + } + }, + + buildTicks: function() { + var me = this; + var tickOpts = me.options.ticks; + var reverse = !me.isHorizontal(); + + var generationOptions = { + min: nonNegativeOrDefault(tickOpts.min), + max: nonNegativeOrDefault(tickOpts.max) + }; + var ticks = me.ticks = generateTicks$1(generationOptions, me); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers$1.max(ticks); + me.min = helpers$1.min(ticks); + + if (tickOpts.reverse) { + reverse = !reverse; + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + if (reverse) { + ticks.reverse(); + } + }, + + convertTicksToLabels: function() { + this.tickValues = this.ticks.slice(); + + core_scale.prototype.convertTicksToLabels.call(this); + }, + + // Get the correct tooltip label + getLabelForIndex: function(index, datasetIndex) { + return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); + }, + + getPixelForTick: function(index) { + var ticks = this.tickValues; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index]); + }, + + /** + * Returns the value of the first tick. + * @param {number} value - The minimum not zero value. + * @return {number} The first tick value. + * @private + */ + _getFirstTickValue: function(value) { + var exp = Math.floor(log10(value)); + var significand = Math.floor(value / Math.pow(10, exp)); + + return significand * Math.pow(10, exp); + }, + + _configure: function() { + var me = this; + var start = me.min; + var offset = 0; + + core_scale.prototype._configure.call(me); + + if (start === 0) { + start = me._getFirstTickValue(me.minNotZero); + offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length; + } + + me._startValue = log10(start); + me._valueOffset = offset; + me._valueRange = (log10(me.max) - log10(start)) / (1 - offset); + }, + + getPixelForValue: function(value) { + var me = this; + var decimal = 0; + + value = +me.getRightValue(value); + + if (value > me.min && value > 0) { + decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset; + } + return me.getPixelForDecimal(decimal); + }, + + getValueForPixel: function(pixel) { + var me = this; + var decimal = me.getDecimalForPixel(pixel); + return decimal === 0 && me.min === 0 + ? 0 + : Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange); + } +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults$2 = defaultConfig$2; +scale_logarithmic._defaults = _defaults$2; + +var valueOrDefault$c = helpers$1.valueOrDefault; +var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault; +var resolve$4 = helpers$1.options.resolve; + +var defaultConfig$3 = { + display: true, + + // Boolean - Whether to animate scaling the chart from the centre + animate: true, + position: 'chartArea', + + angleLines: { + display: true, + color: 'rgba(0,0,0,0.1)', + lineWidth: 1, + borderDash: [], + borderDashOffset: 0.0 + }, + + gridLines: { + circular: false + }, + + // label settings + ticks: { + // Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', + + // Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + + callback: core_ticks.formatters.linear + }, + + pointLabels: { + // Boolean - if true, show point labels + display: true, + + // Number - Point label font size in pixels + fontSize: 10, + + // Function - Used to convert point labels + callback: function(label) { + return label; + } + } +}; + +function getTickBackdropHeight(opts) { + var tickOpts = opts.ticks; + + if (tickOpts.display && opts.display) { + return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; + } + return 0; +} + +function measureLabelSize(ctx, lineHeight, label) { + if (helpers$1.isArray(label)) { + return { + w: helpers$1.longestText(ctx, ctx.font, label), + h: label.length * lineHeight + }; + } + + return { + w: ctx.measureText(label).width, + h: lineHeight + }; +} + +function determineLimits(angle, pos, size, min, max) { + if (angle === min || angle === max) { + return { + start: pos - (size / 2), + end: pos + (size / 2) + }; + } else if (angle < min || angle > max) { + return { + start: pos - size, + end: pos + }; + } + + return { + start: pos, + end: pos + size + }; +} + +/** + * Helper function to fit a radial linear scale with point labels + */ +function fitWithPointLabels(scale) { + + // Right, this is really confusing and there is a lot of maths going on here + // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + // + // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + // + // Solution: + // + // We assume the radius of the polygon is half the size of the canvas at first + // at each index we check if the text overlaps. + // + // Where it does, we store that angle and that index. + // + // After finding the largest index and angle we calculate how much we need to remove + // from the shape radius to move the point inwards by that x. + // + // We average the left and right distances to get the maximum shape radius that can fit in the box + // along with labels. + // + // Once we have that, we can find the centre point for the chart, by taking the x text protrusion + // on each side, removing that from the size, halving it and adding the left x protrusion width. + // + // This will mean we have a shape fitted to the canvas, as large as it can be with the labels + // and position it in the most space efficient manner + // + // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + + var plFont = helpers$1.options._parseFont(scale.options.pointLabels); + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var furthestLimits = { + l: 0, + r: scale.width, + t: 0, + b: scale.height - scale.paddingTop + }; + var furthestAngles = {}; + var i, textSize, pointPosition; + + scale.ctx.font = plFont.string; + scale._pointLabelSizes = []; + + var valueCount = scale.chart.data.labels.length; + for (i = 0; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); + textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]); + scale._pointLabelSizes[i] = textSize; + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = scale.getIndexAngle(i); + var angle = helpers$1.toDegrees(angleRadians) % 360; + var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + + if (hLimits.start < furthestLimits.l) { + furthestLimits.l = hLimits.start; + furthestAngles.l = angleRadians; + } + + if (hLimits.end > furthestLimits.r) { + furthestLimits.r = hLimits.end; + furthestAngles.r = angleRadians; + } + + if (vLimits.start < furthestLimits.t) { + furthestLimits.t = vLimits.start; + furthestAngles.t = angleRadians; + } + + if (vLimits.end > furthestLimits.b) { + furthestLimits.b = vLimits.end; + furthestAngles.b = angleRadians; + } + } + + scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); +} + +function getTextAlignForAngle(angle) { + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } + + return 'right'; +} + +function fillText(ctx, text, position, lineHeight) { + var y = position.y + lineHeight / 2; + var i, ilen; + + if (helpers$1.isArray(text)) { + for (i = 0, ilen = text.length; i < ilen; ++i) { + ctx.fillText(text[i], position.x, y); + y += lineHeight; + } + } else { + ctx.fillText(text, position.x, y); + } +} + +function adjustPointPositionForLabelHeight(angle, textSize, position) { + if (angle === 90 || angle === 270) { + position.y -= (textSize.h / 2); + } else if (angle > 270 || angle < 90) { + position.y -= textSize.h; + } +} + +function drawPointLabels(scale) { + var ctx = scale.ctx; + var opts = scale.options; + var pointLabelOpts = opts.pointLabels; + var tickBackdropHeight = getTickBackdropHeight(opts); + var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + var plFont = helpers$1.options._parseFont(pointLabelOpts); + + ctx.save(); + + ctx.font = plFont.string; + ctx.textBaseline = 'middle'; + + for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) { + // Extra pixels out for some label spacing + var extra = (i === 0 ? tickBackdropHeight / 2 : 0); + var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); + + // Keep this in loop since we may support array properties here + var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor); + ctx.fillStyle = pointLabelFontColor; + + var angleRadians = scale.getIndexAngle(i); + var angle = helpers$1.toDegrees(angleRadians); + ctx.textAlign = getTextAlignForAngle(angle); + adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); + fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight); + } + ctx.restore(); +} + +function drawRadiusLine(scale, gridLineOpts, radius, index) { + var ctx = scale.ctx; + var circular = gridLineOpts.circular; + var valueCount = scale.chart.data.labels.length; + var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1); + var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1); + var pointPosition; + + if ((!circular && !valueCount) || !lineColor || !lineWidth) { + return; + } + + ctx.save(); + ctx.strokeStyle = lineColor; + ctx.lineWidth = lineWidth; + if (ctx.setLineDash) { + ctx.setLineDash(gridLineOpts.borderDash || []); + ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; + } + + ctx.beginPath(); + if (circular) { + // Draw circular arcs between the points + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); + } else { + // Draw straight lines connecting each index + pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + + for (var i = 1; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); + } + } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); +} + +function numberOrZero(param) { + return helpers$1.isNumber(param) ? param : 0; +} + +var scale_radialLinear = scale_linearbase.extend({ + setDimensions: function() { + var me = this; + + // Set the unconstrained dimension before label rotation + me.width = me.maxWidth; + me.height = me.maxHeight; + me.paddingTop = getTickBackdropHeight(me.options) / 2; + me.xCenter = Math.floor(me.width / 2); + me.yCenter = Math.floor((me.height - me.paddingTop) / 2); + me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; + }, + + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var min = Number.POSITIVE_INFINITY; + var max = Number.NEGATIVE_INFINITY; + + helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + + helpers$1.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + min = Math.min(value, min); + max = Math.max(value, max); + }); + } + }); + + me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); + me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, + + // Returns the maximum number of ticks based on the scale dimension + _computeTickLimit: function() { + return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); + }, + + convertTicksToLabels: function() { + var me = this; + + scale_linearbase.prototype.convertTicksToLabels.call(me); + + // Point labels + me.pointLabels = me.chart.data.labels.map(function() { + var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me); + return label || label === 0 ? label : ''; + }); + }, + + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + + fit: function() { + var me = this; + var opts = me.options; + + if (opts.display && opts.pointLabels.display) { + fitWithPointLabels(me); + } else { + me.setCenterPoint(0, 0, 0, 0); + } + }, + + /** + * Set radius reductions and determine new radius and center point + * @private + */ + setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { + var me = this; + var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); + var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); + var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); + var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); + + radiusReductionLeft = numberOrZero(radiusReductionLeft); + radiusReductionRight = numberOrZero(radiusReductionRight); + radiusReductionTop = numberOrZero(radiusReductionTop); + radiusReductionBottom = numberOrZero(radiusReductionBottom); + + me.drawingArea = Math.min( + Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); + }, + + setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { + var me = this; + var maxRight = me.width - rightMovement - me.drawingArea; + var maxLeft = leftMovement + me.drawingArea; + var maxTop = topMovement + me.drawingArea; + var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; + + me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); + me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); + }, + + getIndexAngle: function(index) { + var chart = this.chart; + var angleMultiplier = 360 / chart.data.labels.length; + var options = chart.options || {}; + var startAngle = options.startAngle || 0; + + // Start from the top instead of right, so remove a quarter of the circle + var angle = (index * angleMultiplier + startAngle) % 360; + + return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360; + }, + + getDistanceFromCenterForValue: function(value) { + var me = this; + + if (helpers$1.isNullOrUndef(value)) { + return NaN; + } + + // Take into account half font size + the yPadding of the top value + var scalingFactor = me.drawingArea / (me.max - me.min); + if (me.options.ticks.reverse) { + return (me.max - value) * scalingFactor; + } + return (value - me.min) * scalingFactor; + }, + + getPointPosition: function(index, distanceFromCenter) { + var me = this; + var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); + return { + x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter, + y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter + }; + }, + + getPointPositionForValue: function(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, + + getBasePosition: function(index) { + var me = this; + var min = me.min; + var max = me.max; + + return me.getPointPositionForValue(index || 0, + me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0); + }, + + /** + * @private + */ + _drawGrid: function() { + var me = this; + var ctx = me.ctx; + var opts = me.options; + var gridLineOpts = opts.gridLines; + var angleLineOpts = opts.angleLines; + var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth); + var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color); + var i, offset, position; + + if (opts.pointLabels.display) { + drawPointLabels(me); + } + + if (gridLineOpts.display) { + helpers$1.each(me.ticks, function(label, index) { + if (index !== 0) { + offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + drawRadiusLine(me, gridLineOpts, offset, index); + } + }); + } + + if (angleLineOpts.display && lineWidth && lineColor) { + ctx.save(); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = lineColor; + if (ctx.setLineDash) { + ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []])); + ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]); + } + + for (i = me.chart.data.labels.length - 1; i >= 0; i--) { + offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max); + position = me.getPointPosition(i, offset); + ctx.beginPath(); + ctx.moveTo(me.xCenter, me.yCenter); + ctx.lineTo(position.x, position.y); + ctx.stroke(); + } + + ctx.restore(); + } + }, + + /** + * @private + */ + _drawLabels: function() { + var me = this; + var ctx = me.ctx; + var opts = me.options; + var tickOpts = opts.ticks; + + if (!tickOpts.display) { + return; + } + + var startAngle = me.getIndexAngle(0); + var tickFont = helpers$1.options._parseFont(tickOpts); + var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor); + var offset, width; + + ctx.save(); + ctx.font = tickFont.string; + ctx.translate(me.xCenter, me.yCenter); + ctx.rotate(startAngle); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + helpers$1.each(me.ticks, function(label, index) { + if (index === 0 && !tickOpts.reverse) { + return; + } + + offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + + if (tickOpts.showLabelBackdrop) { + width = ctx.measureText(label).width; + ctx.fillStyle = tickOpts.backdropColor; + + ctx.fillRect( + -width / 2 - tickOpts.backdropPaddingX, + -offset - tickFont.size / 2 - tickOpts.backdropPaddingY, + width + tickOpts.backdropPaddingX * 2, + tickFont.size + tickOpts.backdropPaddingY * 2 + ); + } + + ctx.fillStyle = tickFontColor; + ctx.fillText(label, 0, -offset); + }); + + ctx.restore(); + }, + + /** + * @private + */ + _drawTitle: helpers$1.noop +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults$3 = defaultConfig$3; +scale_radialLinear._defaults = _defaults$3; + +var deprecated$1 = helpers$1._deprecated; +var resolve$5 = helpers$1.options.resolve; +var valueOrDefault$d = helpers$1.valueOrDefault; + +// Integer constants are from the ES6 spec. +var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; +var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + +var INTERVALS = { + millisecond: { + common: true, + size: 1, + steps: 1000 + }, + second: { + common: true, + size: 1000, + steps: 60 + }, + minute: { + common: true, + size: 60000, + steps: 60 + }, + hour: { + common: true, + size: 3600000, + steps: 24 + }, + day: { + common: true, + size: 86400000, + steps: 30 + }, + week: { + common: false, + size: 604800000, + steps: 4 + }, + month: { + common: true, + size: 2.628e9, + steps: 12 + }, + quarter: { + common: false, + size: 7.884e9, + steps: 4 + }, + year: { + common: true, + size: 3.154e10 + } +}; + +var UNITS = Object.keys(INTERVALS); + +function sorter(a, b) { + return a - b; +} + +function arrayUnique(items) { + var hash = {}; + var out = []; + var i, ilen, item; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + if (!hash[item]) { + hash[item] = true; + out.push(item); + } + } + + return out; +} + +function getMin(options) { + return helpers$1.valueOrDefault(options.time.min, options.ticks.min); +} + +function getMax(options) { + return helpers$1.valueOrDefault(options.time.max, options.ticks.max); +} + +/** + * Returns an array of {time, pos} objects used to interpolate a specific `time` or position + * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is + * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other + * extremity (left + width or top + height). Note that it would be more optimized to directly + * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need + * to create the lookup table. The table ALWAYS contains at least two items: min and max. + * + * @param {number[]} timestamps - timestamps sorted from lowest to highest. + * @param {string} distribution - If 'linear', timestamps will be spread linearly along the min + * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. + * If 'series', timestamps will be positioned at the same distance from each other. In this + * case, only timestamps that break the time linearity are registered, meaning that in the + * best case, all timestamps are linear, the table contains only min and max. + */ +function buildLookupTable(timestamps, min, max, distribution) { + if (distribution === 'linear' || !timestamps.length) { + return [ + {time: min, pos: 0}, + {time: max, pos: 1} + ]; + } + + var table = []; + var items = [min]; + var i, ilen, prev, curr, next; + + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + curr = timestamps[i]; + if (curr > min && curr < max) { + items.push(curr); + } + } + + items.push(max); + + for (i = 0, ilen = items.length; i < ilen; ++i) { + next = items[i + 1]; + prev = items[i - 1]; + curr = items[i]; + + // only add points that breaks the scale linearity + if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { + table.push({time: curr, pos: i / (ilen - 1)}); + } + } + + return table; +} + +// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ +function lookup(table, key, value) { + var lo = 0; + var hi = table.length - 1; + var mid, i0, i1; + + while (lo >= 0 && lo <= hi) { + mid = (lo + hi) >> 1; + i0 = table[mid - 1] || null; + i1 = table[mid]; + + if (!i0) { + // given value is outside table (before first item) + return {lo: null, hi: i1}; + } else if (i1[key] < value) { + lo = mid + 1; + } else if (i0[key] > value) { + hi = mid - 1; + } else { + return {lo: i0, hi: i1}; + } + } + + // given value is outside table (after last item) + return {lo: i1, hi: null}; +} + +/** + * Linearly interpolates the given source `value` using the table items `skey` values and + * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') + * returns the position for a timestamp equal to 42. If value is out of bounds, values at + * index [0, 1] or [n - 1, n] are used for the interpolation. + */ +function interpolate$1(table, skey, sval, tkey) { + var range = lookup(table, skey, sval); + + // Note: the lookup table ALWAYS contains at least 2 items (min and max) + var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; + var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; + + var span = next[skey] - prev[skey]; + var ratio = span ? (sval - prev[skey]) / span : 0; + var offset = (next[tkey] - prev[tkey]) * ratio; + + return prev[tkey] + offset; +} + +function toTimestamp(scale, input) { + var adapter = scale._adapter; + var options = scale.options.time; + var parser = options.parser; + var format = parser || options.format; + var value = input; + + if (typeof parser === 'function') { + value = parser(value); + } + + // Only parse if its not a timestamp already + if (!helpers$1.isFinite(value)) { + value = typeof format === 'string' + ? adapter.parse(value, format) + : adapter.parse(value); + } + + if (value !== null) { + return +value; + } + + // Labels are in an incompatible format and no `parser` has been provided. + // The user might still use the deprecated `format` option for parsing. + if (!parser && typeof format === 'function') { + value = format(input); + + // `format` could return something else than a timestamp, if so, parse it + if (!helpers$1.isFinite(value)) { + value = adapter.parse(value); + } + } + + return value; +} + +function parse(scale, input) { + if (helpers$1.isNullOrUndef(input)) { + return null; + } + + var options = scale.options.time; + var value = toTimestamp(scale, scale.getRightValue(input)); + if (value === null) { + return value; + } + + if (options.round) { + value = +scale._adapter.startOf(value, options.round); + } + + return value; +} + +/** + * Figures out what unit results in an appropriate number of auto-generated ticks + */ +function determineUnitForAutoTicks(minUnit, min, max, capacity) { + var ilen = UNITS.length; + var i, interval, factor; + + for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { + interval = INTERVALS[UNITS[i]]; + factor = interval.steps ? interval.steps : MAX_INTEGER; + + if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { + return UNITS[i]; + } + } + + return UNITS[ilen - 1]; +} + +/** + * Figures out what unit to format a set of ticks with + */ +function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { + var i, unit; + + for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) { + unit = UNITS[i]; + if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { + return unit; + } + } + + return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; +} + +function determineMajorUnit(unit) { + for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { + if (INTERVALS[UNITS[i]].common) { + return UNITS[i]; + } + } +} + +/** + * Generates a maximum of `capacity` timestamps between min and max, rounded to the + * `minor` unit using the given scale time `options`. + * Important: this method can return ticks outside the min and max range, it's the + * responsibility of the calling code to clamp values if needed. + */ +function generate(scale, min, max, capacity) { + var adapter = scale._adapter; + var options = scale.options; + var timeOpts = options.time; + var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); + var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]); + var weekday = minor === 'week' ? timeOpts.isoWeekday : false; + var first = min; + var ticks = []; + var time; + + // For 'week' unit, handle the first day of week option + if (weekday) { + first = +adapter.startOf(first, 'isoWeek', weekday); + } + + // Align first ticks on unit + first = +adapter.startOf(first, weekday ? 'day' : minor); + + // Prevent browser from freezing in case user options request millions of milliseconds + if (adapter.diff(max, min, minor) > 100000 * stepSize) { + throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor; + } + + for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) { + ticks.push(time); + } + + if (time === max || options.bounds === 'ticks') { + ticks.push(time); + } + + return ticks; +} + +/** + * Returns the start and end offsets from edges in the form of {start, end} + * where each value is a relative width to the scale and ranges between 0 and 1. + * They add extra margins on the both sides by scaling down the original scale. + * Offsets are added when the `offset` option is true. + */ +function computeOffsets(table, ticks, min, max, options) { + var start = 0; + var end = 0; + var first, last; + + if (options.offset && ticks.length) { + first = interpolate$1(table, 'time', ticks[0], 'pos'); + if (ticks.length === 1) { + start = 1 - first; + } else { + start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2; + } + last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos'); + if (ticks.length === 1) { + end = last; + } else { + end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2; + } + } + + return {start: start, end: end, factor: 1 / (start + 1 + end)}; +} + +function setMajorTicks(scale, ticks, map, majorUnit) { + var adapter = scale._adapter; + var first = +adapter.startOf(ticks[0].value, majorUnit); + var last = ticks[ticks.length - 1].value; + var major, index; + + for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) { + index = map[major]; + if (index >= 0) { + ticks[index].major = true; + } + } + return ticks; +} + +function ticksFromTimestamps(scale, values, majorUnit) { + var ticks = []; + var map = {}; + var ilen = values.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = values[i]; + map[value] = i; + + ticks.push({ + value: value, + major: false + }); + } + + // We set the major ticks separately from the above loop because calling startOf for every tick + // is expensive when there is a large number of ticks + return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit); +} + +var defaultConfig$4 = { + position: 'bottom', + + /** + * Data distribution along the scale: + * - 'linear': data are spread according to their time (distances can vary), + * - 'series': data are spread at the same distance from each other. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + distribution: 'linear', + + /** + * Scale boundary strategy (bypassed by min/max time options) + * - `data`: make sure data are fully visible, ticks outside are removed + * - `ticks`: make sure ticks are fully visible, data outside are truncated + * @see https://github.com/chartjs/Chart.js/pull/4556 + * @since 2.7.0 + */ + bounds: 'data', + + adapters: {}, + time: { + parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + displayFormat: false, // DEPRECATED + isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/ + minUnit: 'millisecond', + displayFormats: {} + }, + ticks: { + autoSkip: false, + + /** + * Ticks generation input values: + * - 'auto': generates "optimal" ticks based on scale size and time options. + * - 'data': generates ticks from data (including labels from data {t|x|y} objects). + * - 'labels': generates ticks from user given `data.labels` values ONLY. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + source: 'auto', + + major: { + enabled: false + } + } +}; + +var scale_time = core_scale.extend({ + initialize: function() { + this.mergeTicksOptions(); + core_scale.prototype.initialize.call(this); + }, + + update: function() { + var me = this; + var options = me.options; + var time = options.time || (options.time = {}); + var adapter = me._adapter = new core_adapters._date(options.adapters.date); + + // DEPRECATIONS: output a message only one time per update + deprecated$1('time scale', time.format, 'time.format', 'time.parser'); + deprecated$1('time scale', time.min, 'time.min', 'ticks.min'); + deprecated$1('time scale', time.max, 'time.max', 'ticks.max'); + + // Backward compatibility: before introducing adapter, `displayFormats` was + // supposed to contain *all* unit/string pairs but this can't be resolved + // when loading the scale (adapters are loaded afterward), so let's populate + // missing formats on update + helpers$1.mergeIf(time.displayFormats, adapter.formats()); + + return core_scale.prototype.update.apply(me, arguments); + }, + + /** + * Allows data to be referenced via 't' attribute + */ + getRightValue: function(rawValue) { + if (rawValue && rawValue.t !== undefined) { + rawValue = rawValue.t; + } + return core_scale.prototype.getRightValue.call(this, rawValue); + }, + + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var adapter = me._adapter; + var options = me.options; + var unit = options.time.unit || 'day'; + var min = MAX_INTEGER; + var max = MIN_INTEGER; + var timestamps = []; + var datasets = []; + var labels = []; + var i, j, ilen, jlen, data, timestamp, labelsAdded; + var dataLabels = me._getLabels(); + + for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { + labels.push(parse(me, dataLabels[i])); + } + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + data = chart.data.datasets[i].data; + + // Let's consider that all data have the same format. + if (helpers$1.isObject(data[0])) { + datasets[i] = []; + + for (j = 0, jlen = data.length; j < jlen; ++j) { + timestamp = parse(me, data[j]); + timestamps.push(timestamp); + datasets[i][j] = timestamp; + } + } else { + datasets[i] = labels.slice(0); + if (!labelsAdded) { + timestamps = timestamps.concat(labels); + labelsAdded = true; + } + } + } else { + datasets[i] = []; + } + } + + if (labels.length) { + min = Math.min(min, labels[0]); + max = Math.max(max, labels[labels.length - 1]); + } + + if (timestamps.length) { + timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter); + min = Math.min(min, timestamps[0]); + max = Math.max(max, timestamps[timestamps.length - 1]); + } + + min = parse(me, getMin(options)) || min; + max = parse(me, getMax(options)) || max; + + // In case there is no valid min/max, set limits based on unit time option + min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min; + max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max; + + // Make sure that max is strictly higher than min (required by the lookup table) + me.min = Math.min(min, max); + me.max = Math.max(min + 1, max); + + // PRIVATE + me._table = []; + me._timestamps = { + data: timestamps, + datasets: datasets, + labels: labels + }; + }, + + buildTicks: function() { + var me = this; + var min = me.min; + var max = me.max; + var options = me.options; + var tickOpts = options.ticks; + var timeOpts = options.time; + var timestamps = me._timestamps; + var ticks = []; + var capacity = me.getLabelCapacity(min); + var source = tickOpts.source; + var distribution = options.distribution; + var i, ilen, timestamp; + + if (source === 'data' || (source === 'auto' && distribution === 'series')) { + timestamps = timestamps.data; + } else if (source === 'labels') { + timestamps = timestamps.labels; + } else { + timestamps = generate(me, min, max, capacity); + } + + if (options.bounds === 'ticks' && timestamps.length) { + min = timestamps[0]; + max = timestamps[timestamps.length - 1]; + } + + // Enforce limits with user min/max options + min = parse(me, getMin(options)) || min; + max = parse(me, getMax(options)) || max; + + // Remove ticks outside the min/max range + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + timestamp = timestamps[i]; + if (timestamp >= min && timestamp <= max) { + ticks.push(timestamp); + } + } + + me.min = min; + me.max = max; + + // PRIVATE + // determineUnitForFormatting relies on the number of ticks so we don't use it when + // autoSkip is enabled because we don't yet know what the final number of ticks will be + me._unit = timeOpts.unit || (tickOpts.autoSkip + ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity) + : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max)); + me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined + : determineMajorUnit(me._unit); + me._table = buildLookupTable(me._timestamps.data, min, max, distribution); + me._offsets = computeOffsets(me._table, ticks, min, max, options); + + if (tickOpts.reverse) { + ticks.reverse(); + } + + return ticksFromTimestamps(me, ticks, me._majorUnit); + }, + + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var adapter = me._adapter; + var data = me.chart.data; + var timeOpts = me.options.time; + var label = data.labels && index < data.labels.length ? data.labels[index] : ''; + var value = data.datasets[datasetIndex].data[index]; + + if (helpers$1.isObject(value)) { + label = me.getRightValue(value); + } + if (timeOpts.tooltipFormat) { + return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat); + } + if (typeof label === 'string') { + return label; + } + return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime); + }, + + /** + * Function to format an individual tick mark + * @private + */ + tickFormatFunction: function(time, index, ticks, format) { + var me = this; + var adapter = me._adapter; + var options = me.options; + var formats = options.time.displayFormats; + var minorFormat = formats[me._unit]; + var majorUnit = me._majorUnit; + var majorFormat = formats[majorUnit]; + var tick = ticks[index]; + var tickOpts = options.ticks; + var major = majorUnit && majorFormat && tick && tick.major; + var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat); + var nestedTickOpts = major ? tickOpts.major : tickOpts.minor; + var formatter = resolve$5([ + nestedTickOpts.callback, + nestedTickOpts.userCallback, + tickOpts.callback, + tickOpts.userCallback + ]); + + return formatter ? formatter(label, index, ticks) : label; + }, + + convertTicksToLabels: function(ticks) { + var labels = []; + var i, ilen; + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(this.tickFormatFunction(ticks[i].value, i, ticks)); + } + + return labels; + }, + + /** + * @private + */ + getPixelForOffset: function(time) { + var me = this; + var offsets = me._offsets; + var pos = interpolate$1(me._table, 'time', time, 'pos'); + return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); + }, + + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var time = null; + + if (index !== undefined && datasetIndex !== undefined) { + time = me._timestamps.datasets[datasetIndex][index]; + } + + if (time === null) { + time = parse(me, value); + } + + if (time !== null) { + return me.getPixelForOffset(time); + } + }, + + getPixelForTick: function(index) { + var ticks = this.getTicks(); + return index >= 0 && index < ticks.length ? + this.getPixelForOffset(ticks[index].value) : + null; + }, + + getValueForPixel: function(pixel) { + var me = this; + var offsets = me._offsets; + var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; + var time = interpolate$1(me._table, 'pos', pos, 'time'); + + // DEPRECATION, we should return time directly + return me._adapter._create(time); + }, + + /** + * @private + */ + _getLabelSize: function(label) { + var me = this; + var ticksOpts = me.options.ticks; + var tickLabelWidth = me.ctx.measureText(label).width; + var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); + var cosRotation = Math.cos(angle); + var sinRotation = Math.sin(angle); + var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize); + + return { + w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation), + h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation) + }; + }, + + /** + * Crude approximation of what the label width might be + * @private + */ + getLabelWidth: function(label) { + return this._getLabelSize(label).w; + }, + + /** + * @private + */ + getLabelCapacity: function(exampleTime) { + var me = this; + var timeOpts = me.options.time; + var displayFormats = timeOpts.displayFormats; + + // pick the longest format (milliseconds) for guestimation + var format = displayFormats[timeOpts.unit] || displayFormats.millisecond; + var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format); + var size = me._getLabelSize(exampleLabel); + var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h); + + if (me.options.offset) { + capacity--; + } + + return capacity > 0 ? capacity : 1; + } +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults$4 = defaultConfig$4; +scale_time._defaults = _defaults$4; + +var scales = { + category: scale_category, + linear: scale_linear, + logarithmic: scale_logarithmic, + radialLinear: scale_radialLinear, + time: scale_time +}; + +var moment = createCommonjsModule(function (module, exports) { +(function (global, factory) { + module.exports = factory() ; +}(commonjsGlobal, (function () { + var hookCallback; + + function hooks () { + return hookCallback.apply(null, arguments); + } + + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback (callback) { + hookCallback = callback; + } + + function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; + } + + function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return input != null && Object.prototype.toString.call(input) === '[object Object]'; + } + + function isObjectEmpty(obj) { + if (Object.getOwnPropertyNames) { + return (Object.getOwnPropertyNames(obj).length === 0); + } else { + var k; + for (k in obj) { + if (obj.hasOwnProperty(k)) { + return false; + } + } + return true; + } + } + + function isUndefined(input) { + return input === void 0; + } + + function isNumber(input) { + return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]'; + } + + function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; + } + + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; + } + + function createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false, + parsedDateParts : [], + meridiem : null, + rfc2822 : false, + weekdayMismatch : false + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } + + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; + + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; + } + + function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }); + var isNowValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.weekdayMismatch && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } + else { + return isNowValid; + } + } + return m._isValid; + } + + function createInvalid (flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } + + return m; + } + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = hooks.momentProperties = []; + + function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i = 0; i < momentProperties.length; i++) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + + return to; + } + + var updateInProgress = false; + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); + } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } + } + + function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); + } + + function absFloor (number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function warn(msg) { + if (hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = []; + var arg; + for (var i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (var key in arguments[0]) { + arg += key + ': ' + arguments[0][key] + ', '; + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + var deprecations = {}; + + function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } + + hooks.suppressDeprecationWarnings = false; + hooks.deprecationHandler = null; + + function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; + } + + function set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. + // TODO: Remove "ordinalParse" fallback in next major release. + this._dayOfMonthOrdinalParseLenient = new RegExp( + (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + + '|' + (/\d{1,2}/).source); + } + + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if (hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop])) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; + } + + function Locale(config) { + if (config != null) { + this.set(config); + } + } + + var keys; + + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; + } + + var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }; + + function calendar (key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; + } + + var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' + }; + + function longDateFormat (key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; + + if (format || !formatUpper) { + return format; + } + + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + + return this._longDateFormat[key]; + } + + var defaultInvalidDate = 'Invalid date'; + + function invalidDate () { + return this._invalidDate; + } + + var defaultOrdinal = '%d'; + var defaultDayOfMonthOrdinalParse = /\d{1,2}/; + + function ordinal (number) { + return this._ordinal.replace('%d', number); + } + + var defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + ss : '%d seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }; + + function relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + } + + function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } + + var aliases = {}; + + function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } + + function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + var priorities = {}; + + function addUnitPriority(unit, priority) { + priorities[unit] = priority; + } + + function getPrioritizedUnits(unitsObj) { + var units = []; + for (var u in unitsObj) { + units.push({unit: u, priority: priorities[u]}); + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; + } + + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; + } + + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; + + var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; + + var formatFunctions = {}; + + var formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; + } + } + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = '', i; + for (i = 0; i < length; i++) { + output += isFunction(array[i]) ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); + + return formatFunctions[format](m); + } + + function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + var match1 = /\d/; // 0 - 9 + var match2 = /\d\d/; // 00 - 99 + var match3 = /\d{3}/; // 000 - 999 + var match4 = /\d{4}/; // 0000 - 9999 + var match6 = /[+-]?\d{6}/; // -999999 - 999999 + var match1to2 = /\d\d?/; // 0 - 99 + var match3to4 = /\d\d\d\d?/; // 999 - 9999 + var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 + var match1to3 = /\d{1,3}/; // 0 - 999 + var match1to4 = /\d{1,4}/; // 0 - 9999 + var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + + var matchUnsigned = /\d+/; // 0 - inf + var matchSigned = /[+-]?\d+/; // -inf - inf + + var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z + + var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i; + + var regexes = {}; + + function addRegexToken (token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; + } + + function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); + } + + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + var tokens = {}; + + function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } + } + + function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } + + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } + + var YEAR = 0; + var MONTH = 1; + var DATE = 2; + var HOUR = 3; + var MINUTE = 4; + var SECOND = 5; + var MILLISECOND = 6; + var WEEK = 7; + var WEEKDAY = 8; + + // FORMATTING + + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; + }); + + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + + // ALIASES + + addUnitAlias('year', 'y'); + + // PRIORITIES + + addUnitPriority('year', 1); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + // HOOKS + + hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + // MOMENTS + + var getSetYear = makeGetSet('FullYear', true); + + function getIsLeapYear () { + return isLeapYear(this.year()); + } + + function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; + } + + function get (mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; + } + + function set$1 (mom, unit, value) { + if (mom.isValid() && !isNaN(value)) { + if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month())); + } + else { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + } + + // MOMENTS + + function stringGet (units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; + } + + + function stringSet (units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units); + for (var i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; + } + + function mod(n, x) { + return ((n % x) + x) % x; + } + + var indexOf; + + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; + } + + function daysInMonth(year, month) { + if (isNaN(year) || isNaN(month)) { + return NaN; + } + var modMonth = mod(month, 12); + year += (month - modMonth) / 12; + return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2); + } + + // FORMATTING + + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); + + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); + + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); + + // ALIASES + + addUnitAlias('month', 'M'); + + // PRIORITY + + addUnitPriority('month', 8); + + // PARSING + + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); + + // LOCALES + + var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/; + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); + function localeMonths (m, format) { + if (!m) { + return isArray(this._months) ? this._months : + this._months['standalone']; + } + return isArray(this._months) ? this._months[m.month()] : + this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; + } + + var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); + function localeMonthsShort (m, format) { + if (!m) { + return isArray(this._monthsShort) ? this._monthsShort : + this._monthsShort['standalone']; + } + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; + } + + function handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; + + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } + + // MOMENTS + + function setMonth (mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; + } + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); + } + } + + function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); + } + + var defaultMonthsShortRegex = matchWord; + function monthsShortRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; + } + } + + var defaultMonthsRegex = matchWord; + function monthsRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; + } + } + + function computeMonthsParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + } + + function createDate (y, m, d, h, M, s, ms) { + // can't just apply() to create a date: + // https://stackoverflow.com/q/181348 + var date; + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + date = new Date(y + 400, m, d, h, M, s, ms); + if (isFinite(date.getFullYear())) { + date.setFullYear(y); + } + } else { + date = new Date(y, m, d, h, M, s, ms); + } + + return date; + } + + function createUTCDate (y) { + var date; + // the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + var args = Array.prototype.slice.call(arguments); + // preserve leap years using a full 400 year cycle, then reset + args[0] = y + 400; + date = new Date(Date.UTC.apply(null, args)); + if (isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + } else { + date = new Date(Date.UTC.apply(null, arguments)); + } + + return date; + } + + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + + return -fwdlw + fwd - 1; + } + + // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + + return { + year: resYear, + dayOfYear: resDayOfYear + }; + } + + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; + + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear + }; + } + + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } + + // FORMATTING + + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + + // ALIASES + + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); + + // PRIORITIES + + addUnitPriority('week', 5); + addUnitPriority('isoWeek', 5); + + // PARSING + + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); + + addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + }); + + // HELPERS + + // LOCALES + + function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } + + var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + }; + + function localeFirstDayOfWeek () { + return this._week.dow; + } + + function localeFirstDayOfYear () { + return this._week.doy; + } + + // MOMENTS + + function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + // FORMATTING + + addFormatToken('d', 0, 'do', 'day'); + + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); + + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); + + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); + + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); + + // ALIASES + + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); + + // PRIORITY + addUnitPriority('day', 11); + addUnitPriority('weekday', 11); + addUnitPriority('isoWeekday', 11); + + // PARSING + + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); + + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); + + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; + } + + function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; + } + + // LOCALES + function shiftWeekdays (ws, n) { + return ws.slice(n, 7).concat(ws.slice(0, n)); + } + + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); + function localeWeekdays (m, format) { + var weekdays = isArray(this._weekdays) ? this._weekdays : + this._weekdays[(m && m !== true && this._weekdays.isFormat.test(format)) ? 'format' : 'standalone']; + return (m === true) ? shiftWeekdays(weekdays, this._week.dow) + : (m) ? weekdays[m.day()] : weekdays; + } + + var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); + function localeWeekdaysShort (m) { + return (m === true) ? shiftWeekdays(this._weekdaysShort, this._week.dow) + : (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; + } + + var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); + function localeWeekdaysMin (m) { + return (m === true) ? shiftWeekdays(this._weekdaysMin, this._week.dow) + : (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; + } + + function handleStrictParse$1(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeWeekdaysParse (weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', 'i'); + } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } + + // MOMENTS + + function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } + + function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + } + + function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } + } + + var defaultWeekdaysRegex = matchWord; + function weekdaysRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; + } + } + + var defaultWeekdaysShortRegex = matchWord; + function weekdaysShortRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; + } + } + + var defaultWeekdaysMinRegex = matchWord; + function weekdaysMinRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } + } + + + function computeWeekdaysParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); + } + + // FORMATTING + + function hFormat() { + return this.hours() % 12 || 12; + } + + function kFormat() { + return this.hours() || 24; + } + + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); + + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + + addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); + + addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); + + function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); + } + + meridiem('a', true); + meridiem('A', false); + + // ALIASES + + addUnitAlias('hour', 'h'); + + // PRIORITY + addUnitPriority('hour', 13); + + // PARSING + + function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; + } + + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('k', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); + addRegexToken('kk', match1to2, match2); + + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); + + addParseToken(['H', 'HH'], HOUR); + addParseToken(['k', 'kk'], function (input, array, config) { + var kInput = toInt(input); + array[HOUR] = kInput === 24 ? 0 : kInput; + }); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + + // LOCALES + + function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + } + + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; + function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + } + + + // MOMENTS + + // Setting the hour should keep the time, because the user explicitly + // specified which hour they want. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + var getSetHour = makeGetSet('Hours', true); + + var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, + relativeTime: defaultRelativeTime, + + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + + week: defaultLocaleWeek, + + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + + meridiemParse: defaultLocaleMeridiemParse + }; + + // internal storage for locale config files + var locales = {}; + var localeFamilies = {}; + var globalLocale; + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return globalLocale; + } + + function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && ('object' !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + var aliasedRequire = commonjsRequire; + aliasedRequire('./locale/' + name); + getSetGlobalLocale(oldLocale); + } catch (e) {} + } + return locales[name]; + } + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function getSetGlobalLocale (key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } + else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + else { + if ((typeof console !== 'undefined') && console.warn) { + //warn user if arguments are passed but the locale could not be set + console.warn('Locale ' + key + ' not found. Did you forget to load it?'); + } + } + } + + return globalLocale._abbr; + } + + function defineLocale (name, config) { + if (config !== null) { + var locale, parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + locale = loadLocale(config.parentLocale); + if (locale != null) { + parentConfig = locale._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name, + config: config + }); + return null; + } + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); + + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); + } + + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); + + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } + + function updateLocale(name, config) { + if (config != null) { + var locale, tmpLocale, parentConfig = baseConfig; + // MERGE + tmpLocale = loadLocale(name); + if (tmpLocale != null) { + parentConfig = tmpLocale._config; + } + config = mergeConfigs(parentConfig, config); + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; + + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; + } + + // returns locale data + function getLocale (key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); + } + + function listLocales() { + return keys(locales); + } + + function checkOverflow (m) { + var overflow; + var a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; + } + + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } + + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray (config) { + var i, date, input = [], currentDate, expectedWeekday, yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear != null) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) { + getParsingFlags(config)._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay(); + + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } + + // check for mismatching day of week + if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) { + getParsingFlags(config).weekdayMismatch = true; + } + } + + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + var curWeek = weekOfYear(createLocal(), dow, doy); + + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); + + // Default to current week. + week = defaults(w.w, curWeek.week); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from beginning of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to beginning of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + + var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; + + var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] + ]; + + // iso time formats and regexes + var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] + ]; + + var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; + + // date from iso format + function configFromISO(config) { + var i, l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; + + if (match) { + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + + // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 + var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/; + + function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) { + var result = [ + untruncateYear(yearStr), + defaultLocaleMonthsShort.indexOf(monthStr), + parseInt(dayStr, 10), + parseInt(hourStr, 10), + parseInt(minuteStr, 10) + ]; + + if (secondStr) { + result.push(parseInt(secondStr, 10)); + } + + return result; + } + + function untruncateYear(yearStr) { + var year = parseInt(yearStr, 10); + if (year <= 49) { + return 2000 + year; + } else if (year <= 999) { + return 1900 + year; + } + return year; + } + + function preprocessRFC2822(s) { + // Remove comments and folding whitespace and replace multiple-spaces with a single space + return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + } + + function checkWeekday(weekdayStr, parsedInput, config) { + if (weekdayStr) { + // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check. + var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), + weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay(); + if (weekdayProvided !== weekdayActual) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return false; + } + } + return true; + } + + var obsOffsets = { + UT: 0, + GMT: 0, + EDT: -4 * 60, + EST: -5 * 60, + CDT: -5 * 60, + CST: -6 * 60, + MDT: -6 * 60, + MST: -7 * 60, + PDT: -7 * 60, + PST: -8 * 60 + }; + + function calculateOffset(obsOffset, militaryOffset, numOffset) { + if (obsOffset) { + return obsOffsets[obsOffset]; + } else if (militaryOffset) { + // the only allowed military tz is Z + return 0; + } else { + var hm = parseInt(numOffset, 10); + var m = hm % 100, h = (hm - m) / 100; + return h * 60 + m; + } + } + + // date and time from ref 2822 format + function configFromRFC2822(config) { + var match = rfc2822.exec(preprocessRFC2822(config._i)); + if (match) { + var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]); + if (!checkWeekday(match[1], parsedArray, config)) { + return; + } + + config._a = parsedArray; + config._tzm = calculateOffset(match[8], match[9], match[10]); + + config._d = createUTCDate.apply(null, config._a); + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + + getParsingFlags(config).rfc2822 = true; + } else { + config._isValid = false; + } + } + + // date from iso format or fallback + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + configFromRFC2822(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + // Final attempt, use Input Fallback + hooks.createFromInputFallback(config); + } + + hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + + 'discouraged and will be removed in an upcoming major release. Please refer to ' + + 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + // constant that refers to the ISO standard + hooks.ISO_8601 = function () {}; + + // constant that refers to the RFC 2822 form + hooks.RFC_2822 = function () {}; + + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + if (config._f === hooks.RFC_2822) { + configFromRFC2822(config); + return; + } + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } + else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if (config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; + } + + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); + + configFromArray(config); + checkOverflow(config); + } + + + function meridiemFixWrap (locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } + } + + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); + + configFromArray(config); + } + + function createFromConfig (config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; + } + + function prepareConfig (config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return createInvalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + + if (!isValid(config)) { + config._d = null; + } + + return config; + } + + function configFromInput(config) { + var input = config._i; + if (isUndefined(input)) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (isObject(input)) { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } + } + + function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; + + if (locale === true || locale === false) { + strict = locale; + locale = undefined; + } + + if ((isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0)) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); + } + + function createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } + + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } + ); + + var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + // TODO: Use [].sort instead? + function min () { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + } + + function max () { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + } + + var now = function () { + return Date.now ? Date.now() : +(new Date()); + }; + + var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond']; + + function isDurationValid(m) { + for (var key in m) { + if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) { + return false; + } + } + + var unitHasDecimal = false; + for (var i = 0; i < ordering.length; ++i) { + if (m[ordering[i]]) { + if (unitHasDecimal) { + return false; // only allow non-integers for smallest unit + } + if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { + unitHasDecimal = true; + } + } + } + + return true; + } + + function isValid$1() { + return this._isValid; + } + + function createInvalid$1() { + return createDuration(NaN); + } + + function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || normalizedInput.isoWeek || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + this._isValid = isDurationValid(normalizedInput); + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible to translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; + + this._data = {}; + + this._locale = getLocale(); + + this._bubble(); + } + + function isDuration (obj) { + return obj instanceof Duration; + } + + function absRound (number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } + } + + // FORMATTING + + function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); + } + + offset('Z', ':'); + offset('ZZ', ''); + + // PARSING + + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + + // HELPERS + + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; + + function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher); + + if (matches === null) { + return null; + } + + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); + + return minutes === 0 ? + 0 : + parts[0] === '+' ? minutes : -minutes; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } + } + + function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + } + + // HOOKS + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + hooks.updateOffset = function () {}; + + // MOMENTS + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset (input, keepLocalTime, keepMinutes) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16 && !keepMinutes) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract(this, createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } + + function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + + function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } + + function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } + + function setOffsetToParsedOffset () { + if (this._tzm != null) { + this.utcOffset(this._tzm, false, true); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } + else { + this.utcOffset(0, true); + } + } + return this; + } + + function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; + } + + function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } + + function isDaylightSavingTimeShifted () { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; + } + + function isLocal () { + return this.isValid() ? !this._isUTC : false; + } + + function isUtcOffset () { + return this.isValid() ? this._isUTC : false; + } + + function isUtc () { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } + + // ASP.NET json date format regex + var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; + + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; + + function createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (isNumber(input)) { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + w : parseIso(match[4], sign), + d : parseIso(match[5], sign), + h : parseIso(match[6], sign), + m : parseIso(match[7], sign), + s : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to)); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + return ret; + } + + createDuration.fn = Duration.prototype; + createDuration.invalid = createInvalid$1; + + function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } + + function positiveMomentsDifference(base, other) { + var res = {}; + + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); + + return res; + } + + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); + tmp = val; val = period; period = tmp; + } + + val = typeof val === 'string' ? +val : val; + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; + } + + function addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } + } + + var add = createAdder(1, 'add'); + var subtract = createAdder(-1, 'subtract'); + + function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + } + + function calendar$1 (time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse'; + + var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); + + return this.format(output || this.localeData().calendar(format, this, createLocal(now))); + } + + function clone () { + return new Moment(this); + } + + function isAfter (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } + + function isBefore (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } + + function isBetween (from, to, units, inclusivity) { + var localFrom = isMoment(from) ? from : createLocal(from), + localTo = isMoment(to) ? to : createLocal(to); + if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) { + return false; + } + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(localFrom, units) : !this.isBefore(localFrom, units)) && + (inclusivity[1] === ')' ? this.isBefore(localTo, units) : !this.isAfter(localTo, units)); + } + + function isSame (input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } + } + + function isSameOrAfter (input, units) { + return this.isSame(input, units) || this.isAfter(input, units); + } + + function isSameOrBefore (input, units) { + return this.isSame(input, units) || this.isBefore(input, units); + } + + function diff (input, units, asFloat) { + var that, + zoneDelta, + output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + switch (units) { + case 'year': output = monthDiff(this, that) / 12; break; + case 'month': output = monthDiff(this, that); break; + case 'quarter': output = monthDiff(this, that) / 3; break; + case 'second': output = (this - that) / 1e3; break; // 1000 + case 'minute': output = (this - that) / 6e4; break; // 1000 * 60 + case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60 + case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst + case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst + default: output = this - that; + } + + return asFloat ? output : absFloor(output); + } + + function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } + + hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + + function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + function toISOString(keepOffset) { + if (!this.isValid()) { + return null; + } + var utc = keepOffset !== true; + var m = utc ? this.clone().utc() : this; + if (m.year() < 0 || m.year() > 9999) { + return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'); + } + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + if (utc) { + return this.toDate().toISOString(); + } else { + return new Date(this.valueOf() + this.utcOffset() * 60 * 1000).toISOString().replace('Z', formatMoment(m, 'Z')); + } + } + return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'); + } + + /** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ + function inspect () { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; + } + var func = 'moment'; + var zone = ''; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + var prefix = '[' + func + '("]'; + var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY'; + var datetime = '-MM-DD[T]HH:mm:ss.SSS'; + var suffix = zone + '[")]'; + + return this.format(prefix + year + datetime + suffix); + } + + function format (inputString) { + if (!inputString) { + inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + + function from (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function fromNow (withoutSuffix) { + return this.from(createLocal(), withoutSuffix); + } + + function to (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function toNow (withoutSuffix) { + return this.to(createLocal(), withoutSuffix); + } + + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale (key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } + + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + + function localeData () { + return this._locale; + } + + var MS_PER_SECOND = 1000; + var MS_PER_MINUTE = 60 * MS_PER_SECOND; + var MS_PER_HOUR = 60 * MS_PER_MINUTE; + var MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; + + // actual modulo - handles negative numbers (for dates before 1970): + function mod$1(dividend, divisor) { + return (dividend % divisor + divisor) % divisor; + } + + function localStartOfDate(y, m, d) { + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return new Date(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return new Date(y, m, d).valueOf(); + } + } + + function utcStartOfDate(y, m, d) { + // Date.UTC remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return Date.UTC(y, m, d); + } + } + + function startOf (units) { + var time; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } + + var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + + switch (units) { + case 'year': + time = startOfDate(this.year(), 0, 1); + break; + case 'quarter': + time = startOfDate(this.year(), this.month() - this.month() % 3, 1); + break; + case 'month': + time = startOfDate(this.year(), this.month(), 1); + break; + case 'week': + time = startOfDate(this.year(), this.month(), this.date() - this.weekday()); + break; + case 'isoWeek': + time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1)); + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date()); + break; + case 'hour': + time = this._d.valueOf(); + time -= mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR); + break; + case 'minute': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_MINUTE); + break; + case 'second': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_SECOND); + break; + } + + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } + + function endOf (units) { + var time; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } + + var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + + switch (units) { + case 'year': + time = startOfDate(this.year() + 1, 0, 1) - 1; + break; + case 'quarter': + time = startOfDate(this.year(), this.month() - this.month() % 3 + 3, 1) - 1; + break; + case 'month': + time = startOfDate(this.year(), this.month() + 1, 1) - 1; + break; + case 'week': + time = startOfDate(this.year(), this.month(), this.date() - this.weekday() + 7) - 1; + break; + case 'isoWeek': + time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1) + 7) - 1; + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; + break; + case 'hour': + time = this._d.valueOf(); + time += MS_PER_HOUR - mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR) - 1; + break; + case 'minute': + time = this._d.valueOf(); + time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1; + break; + case 'second': + time = this._d.valueOf(); + time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1; + break; + } + + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } + + function valueOf () { + return this._d.valueOf() - ((this._offset || 0) * 60000); + } + + function unix () { + return Math.floor(this.valueOf() / 1000); + } + + function toDate () { + return new Date(this.valueOf()); + } + + function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; + } + + function toObject () { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; + } + + function toJSON () { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; + } + + function isValid$2 () { + return isValid(this); + } + + function parsingFlags () { + return extend({}, getParsingFlags(this)); + } + + function invalidAt () { + return getParsingFlags(this).overflow; + } + + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; + } + + // FORMATTING + + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); + + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); + } + + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + + // ALIASES + + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); + + // PRIORITY + + addUnitPriority('weekYear', 1); + addUnitPriority('isoWeekYear', 1); + + + // PARSING + + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); + + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + }); + + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); + }); + + // MOMENTS + + function getSetWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); + } + + function getSetISOWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); + } + + function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); + } + + function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } + + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } + + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } + + // FORMATTING + + addFormatToken('Q', 0, 'Qo', 'quarter'); + + // ALIASES + + addUnitAlias('quarter', 'Q'); + + // PRIORITY + + addUnitPriority('quarter', 7); + + // PARSING + + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + + // MOMENTS + + function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + } + + // FORMATTING + + addFormatToken('D', ['DD', 2], 'Do', 'date'); + + // ALIASES + + addUnitAlias('date', 'D'); + + // PRIORITY + addUnitPriority('date', 9); + + // PARSING + + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + // TODO: Remove "ordinalParse" fallback in next major release. + return isStrict ? + (locale._dayOfMonthOrdinalParse || locale._ordinalParse) : + locale._dayOfMonthOrdinalParseLenient; + }); + + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0]); + }); + + // MOMENTS + + var getSetDayOfMonth = makeGetSet('Date', true); + + // FORMATTING + + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + + // ALIASES + + addUnitAlias('dayOfYear', 'DDD'); + + // PRIORITY + addUnitPriority('dayOfYear', 4); + + // PARSING + + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); + + // HELPERS + + // MOMENTS + + function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); + } + + // FORMATTING + + addFormatToken('m', ['mm', 2], 0, 'minute'); + + // ALIASES + + addUnitAlias('minute', 'm'); + + // PRIORITY + + addUnitPriority('minute', 14); + + // PARSING + + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); + + // MOMENTS + + var getSetMinute = makeGetSet('Minutes', false); + + // FORMATTING + + addFormatToken('s', ['ss', 2], 0, 'second'); + + // ALIASES + + addUnitAlias('second', 's'); + + // PRIORITY + + addUnitPriority('second', 15); + + // PARSING + + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); + + // MOMENTS + + var getSetSecond = makeGetSet('Seconds', false); + + // FORMATTING + + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); + + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); + + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); + + + // ALIASES + + addUnitAlias('millisecond', 'ms'); + + // PRIORITY + + addUnitPriority('millisecond', 16); + + // PARSING + + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + + var token; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); + } + + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } + + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + // MOMENTS + + var getSetMillisecond = makeGetSet('Milliseconds', false); + + // FORMATTING + + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var proto = Moment.prototype; + + proto.add = add; + proto.calendar = calendar$1; + proto.clone = clone; + proto.diff = diff; + proto.endOf = endOf; + proto.format = format; + proto.from = from; + proto.fromNow = fromNow; + proto.to = to; + proto.toNow = toNow; + proto.get = stringGet; + proto.invalidAt = invalidAt; + proto.isAfter = isAfter; + proto.isBefore = isBefore; + proto.isBetween = isBetween; + proto.isSame = isSame; + proto.isSameOrAfter = isSameOrAfter; + proto.isSameOrBefore = isSameOrBefore; + proto.isValid = isValid$2; + proto.lang = lang; + proto.locale = locale; + proto.localeData = localeData; + proto.max = prototypeMax; + proto.min = prototypeMin; + proto.parsingFlags = parsingFlags; + proto.set = stringSet; + proto.startOf = startOf; + proto.subtract = subtract; + proto.toArray = toArray; + proto.toObject = toObject; + proto.toDate = toDate; + proto.toISOString = toISOString; + proto.inspect = inspect; + proto.toJSON = toJSON; + proto.toString = toString; + proto.unix = unix; + proto.valueOf = valueOf; + proto.creationData = creationData; + proto.year = getSetYear; + proto.isLeapYear = getIsLeapYear; + proto.weekYear = getSetWeekYear; + proto.isoWeekYear = getSetISOWeekYear; + proto.quarter = proto.quarters = getSetQuarter; + proto.month = getSetMonth; + proto.daysInMonth = getDaysInMonth; + proto.week = proto.weeks = getSetWeek; + proto.isoWeek = proto.isoWeeks = getSetISOWeek; + proto.weeksInYear = getWeeksInYear; + proto.isoWeeksInYear = getISOWeeksInYear; + proto.date = getSetDayOfMonth; + proto.day = proto.days = getSetDayOfWeek; + proto.weekday = getSetLocaleDayOfWeek; + proto.isoWeekday = getSetISODayOfWeek; + proto.dayOfYear = getSetDayOfYear; + proto.hour = proto.hours = getSetHour; + proto.minute = proto.minutes = getSetMinute; + proto.second = proto.seconds = getSetSecond; + proto.millisecond = proto.milliseconds = getSetMillisecond; + proto.utcOffset = getSetOffset; + proto.utc = setOffsetToUTC; + proto.local = setOffsetToLocal; + proto.parseZone = setOffsetToParsedOffset; + proto.hasAlignedHourOffset = hasAlignedHourOffset; + proto.isDST = isDaylightSavingTime; + proto.isLocal = isLocal; + proto.isUtcOffset = isUtcOffset; + proto.isUtc = isUtc; + proto.isUTC = isUtc; + proto.zoneAbbr = getZoneAbbr; + proto.zoneName = getZoneName; + proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); + proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); + proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); + proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); + proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); + + function createUnix (input) { + return createLocal(input * 1000); + } + + function createInZone () { + return createLocal.apply(null, arguments).parseZone(); + } + + function preParsePostFormat (string) { + return string; + } + + var proto$1 = Locale.prototype; + + proto$1.calendar = calendar; + proto$1.longDateFormat = longDateFormat; + proto$1.invalidDate = invalidDate; + proto$1.ordinal = ordinal; + proto$1.preparse = preParsePostFormat; + proto$1.postformat = preParsePostFormat; + proto$1.relativeTime = relativeTime; + proto$1.pastFuture = pastFuture; + proto$1.set = set; + + proto$1.months = localeMonths; + proto$1.monthsShort = localeMonthsShort; + proto$1.monthsParse = localeMonthsParse; + proto$1.monthsRegex = monthsRegex; + proto$1.monthsShortRegex = monthsShortRegex; + proto$1.week = localeWeek; + proto$1.firstDayOfYear = localeFirstDayOfYear; + proto$1.firstDayOfWeek = localeFirstDayOfWeek; + + proto$1.weekdays = localeWeekdays; + proto$1.weekdaysMin = localeWeekdaysMin; + proto$1.weekdaysShort = localeWeekdaysShort; + proto$1.weekdaysParse = localeWeekdaysParse; + + proto$1.weekdaysRegex = weekdaysRegex; + proto$1.weekdaysShortRegex = weekdaysShortRegex; + proto$1.weekdaysMinRegex = weekdaysMinRegex; + + proto$1.isPM = localeIsPM; + proto$1.meridiem = localeMeridiem; + + function get$1 (format, index, field, setter) { + var locale = getLocale(); + var utc = createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl (format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl (localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } + + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0; + + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } + + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; + } + + function listMonths (format, index) { + return listMonthsImpl(format, index, 'months'); + } + + function listMonthsShort (format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } + + function listWeekdays (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); + } + + function listWeekdaysShort (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); + } + + function listWeekdaysMin (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); + } + + getSetGlobalLocale('en', { + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + // Side effect imports + + hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); + hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); + + var mathAbs = Math.abs; + + function abs () { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; + } + + function addSubtract$1 (duration, input, value, direction) { + var other = createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); + } + + // supports only 2.0-style add(1, 's') or add(duration) + function add$1 (input, value) { + return addSubtract$1(this, input, value, 1); + } + + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function subtract$1 (input, value) { + return addSubtract$1(this, input, value, -1); + } + + function absCeil (number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } + + function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; + + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; + } + + function daysToMonths (days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; + } + + function monthsToDays (months) { + // the reverse of daysToMonths + return months * 146097 / 4800; + } + + function as (units) { + if (!this.isValid()) { + return NaN; + } + var days; + var months; + var milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'quarter' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + switch (units) { + case 'month': return months; + case 'quarter': return months / 3; + case 'year': return months / 12; + } + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 1440 + milliseconds / 6e4; + case 'second' : return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } + } + + // TODO: Use this.as('ms')? + function valueOf$1 () { + if (!this.isValid()) { + return NaN; + } + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } + + function makeAs (alias) { + return function () { + return this.as(alias); + }; + } + + var asMilliseconds = makeAs('ms'); + var asSeconds = makeAs('s'); + var asMinutes = makeAs('m'); + var asHours = makeAs('h'); + var asDays = makeAs('d'); + var asWeeks = makeAs('w'); + var asMonths = makeAs('M'); + var asQuarters = makeAs('Q'); + var asYears = makeAs('y'); + + function clone$1 () { + return createDuration(this); + } + + function get$2 (units) { + units = normalizeUnits(units); + return this.isValid() ? this[units + 's']() : NaN; + } + + function makeGetter(name) { + return function () { + return this.isValid() ? this._data[name] : NaN; + }; + } + + var milliseconds = makeGetter('milliseconds'); + var seconds = makeGetter('seconds'); + var minutes = makeGetter('minutes'); + var hours = makeGetter('hours'); + var days = makeGetter('days'); + var months = makeGetter('months'); + var years = makeGetter('years'); + + function weeks () { + return absFloor(this.days() / 7); + } + + var round = Math.round; + var thresholds = { + ss: 44, // a few seconds to seconds + s : 45, // seconds to minute + m : 45, // minutes to hour + h : 22, // hours to day + d : 26, // days to month + M : 11 // months to year + }; + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime$1 (posNegDuration, withoutSuffix, locale) { + var duration = createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); + + var a = seconds <= thresholds.ss && ['s', seconds] || + seconds < thresholds.s && ['ss', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set the rounding function for relative time strings + function getSetRelativeTimeRounding (roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof(roundingFunction) === 'function') { + round = roundingFunction; + return true; + } + return false; + } + + // This function allows you to set a threshold for relative time strings + function getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + if (threshold === 's') { + thresholds.ss = limit - 1; + } + return true; + } + + function humanize (withSuffix) { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var locale = this.localeData(); + var output = relativeTime$1(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var abs$1 = Math.abs; + + function sign(x) { + return ((x > 0) - (x < 0)) || +x; + } + + function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var seconds = abs$1(this._milliseconds) / 1000; + var days = abs$1(this._days); + var months = abs$1(this._months); + var minutes, hours, years; + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : ''; + var total = this.asSeconds(); + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + var totalSign = total < 0 ? '-' : ''; + var ymSign = sign(this._months) !== sign(total) ? '-' : ''; + var daysSign = sign(this._days) !== sign(total) ? '-' : ''; + var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : ''; + + return totalSign + 'P' + + (Y ? ymSign + Y + 'Y' : '') + + (M ? ymSign + M + 'M' : '') + + (D ? daysSign + D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? hmsSign + h + 'H' : '') + + (m ? hmsSign + m + 'M' : '') + + (s ? hmsSign + s + 'S' : ''); + } + + var proto$2 = Duration.prototype; + + proto$2.isValid = isValid$1; + proto$2.abs = abs; + proto$2.add = add$1; + proto$2.subtract = subtract$1; + proto$2.as = as; + proto$2.asMilliseconds = asMilliseconds; + proto$2.asSeconds = asSeconds; + proto$2.asMinutes = asMinutes; + proto$2.asHours = asHours; + proto$2.asDays = asDays; + proto$2.asWeeks = asWeeks; + proto$2.asMonths = asMonths; + proto$2.asQuarters = asQuarters; + proto$2.asYears = asYears; + proto$2.valueOf = valueOf$1; + proto$2._bubble = bubble; + proto$2.clone = clone$1; + proto$2.get = get$2; + proto$2.milliseconds = milliseconds; + proto$2.seconds = seconds; + proto$2.minutes = minutes; + proto$2.hours = hours; + proto$2.days = days; + proto$2.weeks = weeks; + proto$2.months = months; + proto$2.years = years; + proto$2.humanize = humanize; + proto$2.toISOString = toISOString$1; + proto$2.toString = toISOString$1; + proto$2.toJSON = toISOString$1; + proto$2.locale = locale; + proto$2.localeData = localeData; + + proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1); + proto$2.lang = lang; + + // Side effect imports + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); + + // Side effect imports + + + hooks.version = '2.24.0'; + + setHookCallback(createLocal); + + hooks.fn = proto; + hooks.min = min; + hooks.max = max; + hooks.now = now; + hooks.utc = createUTC; + hooks.unix = createUnix; + hooks.months = listMonths; + hooks.isDate = isDate; + hooks.locale = getSetGlobalLocale; + hooks.invalid = createInvalid; + hooks.duration = createDuration; + hooks.isMoment = isMoment; + hooks.weekdays = listWeekdays; + hooks.parseZone = createInZone; + hooks.localeData = getLocale; + hooks.isDuration = isDuration; + hooks.monthsShort = listMonthsShort; + hooks.weekdaysMin = listWeekdaysMin; + hooks.defineLocale = defineLocale; + hooks.updateLocale = updateLocale; + hooks.locales = listLocales; + hooks.weekdaysShort = listWeekdaysShort; + hooks.normalizeUnits = normalizeUnits; + hooks.relativeTimeRounding = getSetRelativeTimeRounding; + hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; + hooks.calendarFormat = getCalendarFormat; + hooks.prototype = proto; + + // currently HTML5 input type only supports 24-hour formats + hooks.HTML5_FMT = { + DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" /> + DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" /> + DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" /> + DATE: 'YYYY-MM-DD', // <input type="date" /> + TIME: 'HH:mm', // <input type="time" /> + TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" /> + TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" /> + WEEK: 'GGGG-[W]WW', // <input type="week" /> + MONTH: 'YYYY-MM' // <input type="month" /> + }; + + return hooks; + +}))); +}); + +var FORMATS = { + datetime: 'MMM D, YYYY, h:mm:ss a', + millisecond: 'h:mm:ss.SSS a', + second: 'h:mm:ss a', + minute: 'h:mm a', + hour: 'hA', + day: 'MMM D', + week: 'll', + month: 'MMM YYYY', + quarter: '[Q]Q - YYYY', + year: 'YYYY' +}; + +core_adapters._date.override(typeof moment === 'function' ? { + _id: 'moment', // DEBUG ONLY + + formats: function() { + return FORMATS; + }, + + parse: function(value, format) { + if (typeof value === 'string' && typeof format === 'string') { + value = moment(value, format); + } else if (!(value instanceof moment)) { + value = moment(value); + } + return value.isValid() ? value.valueOf() : null; + }, + + format: function(time, format) { + return moment(time).format(format); + }, + + add: function(time, amount, unit) { + return moment(time).add(amount, unit).valueOf(); + }, + + diff: function(max, min, unit) { + return moment(max).diff(moment(min), unit); + }, + + startOf: function(time, unit, weekday) { + time = moment(time); + if (unit === 'isoWeek') { + return time.isoWeekday(weekday).valueOf(); + } + return time.startOf(unit).valueOf(); + }, + + endOf: function(time, unit) { + return moment(time).endOf(unit).valueOf(); + }, + + // DEPRECATIONS + + /** + * Provided for backward compatibility with scale.getValueForPixel(). + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ + _create: function(time) { + return moment(time); + }, +} : {}); + +core_defaults._set('global', { + plugins: { + filler: { + propagate: true + } + } +}); + +var mappers = { + dataset: function(source) { + var index = source.fill; + var chart = source.chart; + var meta = chart.getDatasetMeta(index); + var visible = meta && chart.isDatasetVisible(index); + var points = (visible && meta.dataset._children) || []; + var length = points.length || 0; + + return !length ? null : function(point, i) { + return (i < length && points[i]._view) || null; + }; + }, + + boundary: function(source) { + var boundary = source.boundary; + var x = boundary ? boundary.x : null; + var y = boundary ? boundary.y : null; + + if (helpers$1.isArray(boundary)) { + return function(point, i) { + return boundary[i]; + }; + } + + return function(point) { + return { + x: x === null ? point.x : x, + y: y === null ? point.y : y, + }; + }; + } +}; + +// @todo if (fill[0] === '#') +function decodeFill(el, index, count) { + var model = el._model || {}; + var fill = model.fill; + var target; + + if (fill === undefined) { + fill = !!model.backgroundColor; + } + + if (fill === false || fill === null) { + return false; + } + + if (fill === true) { + return 'origin'; + } + + target = parseFloat(fill, 10); + if (isFinite(target) && Math.floor(target) === target) { + if (fill[0] === '-' || fill[0] === '+') { + target = index + target; + } + + if (target === index || target < 0 || target >= count) { + return false; + } + + return target; + } + + switch (fill) { + // compatibility + case 'bottom': + return 'start'; + case 'top': + return 'end'; + case 'zero': + return 'origin'; + // supported boundaries + case 'origin': + case 'start': + case 'end': + return fill; + // invalid fill values + default: + return false; + } +} + +function computeLinearBoundary(source) { + var model = source.el._model || {}; + var scale = source.el._scale || {}; + var fill = source.fill; + var target = null; + var horizontal; + + if (isFinite(fill)) { + return null; + } + + // Backward compatibility: until v3, we still need to support boundary values set on + // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and + // controllers might still use it (e.g. the Smith chart). + + if (fill === 'start') { + target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; + } else if (fill === 'end') { + target = model.scaleTop === undefined ? scale.top : model.scaleTop; + } else if (model.scaleZero !== undefined) { + target = model.scaleZero; + } else if (scale.getBasePixel) { + target = scale.getBasePixel(); + } + + if (target !== undefined && target !== null) { + if (target.x !== undefined && target.y !== undefined) { + return target; + } + + if (helpers$1.isFinite(target)) { + horizontal = scale.isHorizontal(); + return { + x: horizontal ? target : null, + y: horizontal ? null : target + }; + } + } + + return null; +} + +function computeCircularBoundary(source) { + var scale = source.el._scale; + var options = scale.options; + var length = scale.chart.data.labels.length; + var fill = source.fill; + var target = []; + var start, end, center, i, point; + + if (!length) { + return null; + } + + start = options.ticks.reverse ? scale.max : scale.min; + end = options.ticks.reverse ? scale.min : scale.max; + center = scale.getPointPositionForValue(0, start); + for (i = 0; i < length; ++i) { + point = fill === 'start' || fill === 'end' + ? scale.getPointPositionForValue(i, fill === 'start' ? start : end) + : scale.getBasePosition(i); + if (options.gridLines.circular) { + point.cx = center.x; + point.cy = center.y; + point.angle = scale.getIndexAngle(i) - Math.PI / 2; + } + target.push(point); + } + return target; +} + +function computeBoundary(source) { + var scale = source.el._scale || {}; + + if (scale.getPointPositionForValue) { + return computeCircularBoundary(source); + } + return computeLinearBoundary(source); +} + +function resolveTarget(sources, index, propagate) { + var source = sources[index]; + var fill = source.fill; + var visited = [index]; + var target; + + if (!propagate) { + return fill; + } + + while (fill !== false && visited.indexOf(fill) === -1) { + if (!isFinite(fill)) { + return fill; + } + + target = sources[fill]; + if (!target) { + return false; + } + + if (target.visible) { + return fill; + } + + visited.push(fill); + fill = target.fill; + } + + return false; +} + +function createMapper(source) { + var fill = source.fill; + var type = 'dataset'; + + if (fill === false) { + return null; + } + + if (!isFinite(fill)) { + type = 'boundary'; + } + + return mappers[type](source); +} + +function isDrawable(point) { + return point && !point.skip; +} + +function drawArea(ctx, curve0, curve1, len0, len1) { + var i, cx, cy, r; + + if (!len0 || !len1) { + return; + } + + // building first area curve (normal) + ctx.moveTo(curve0[0].x, curve0[0].y); + for (i = 1; i < len0; ++i) { + helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); + } + + if (curve1[0].angle !== undefined) { + cx = curve1[0].cx; + cy = curve1[0].cy; + r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2)); + for (i = len1 - 1; i > 0; --i) { + ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true); + } + return; + } + + // joining the two area curves + ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); + + // building opposite area curve (reverse) + for (i = len1 - 1; i > 0; --i) { + helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); + } +} + +function doFill(ctx, points, mapper, view, color, loop) { + var count = points.length; + var span = view.spanGaps; + var curve0 = []; + var curve1 = []; + var len0 = 0; + var len1 = 0; + var i, ilen, index, p0, p1, d0, d1, loopOffset; + + ctx.beginPath(); + + for (i = 0, ilen = count; i < ilen; ++i) { + index = i % count; + p0 = points[index]._view; + p1 = mapper(p0, index, view); + d0 = isDrawable(p0); + d1 = isDrawable(p1); + + if (loop && loopOffset === undefined && d0) { + loopOffset = i + 1; + ilen = count + loopOffset; + } + + if (d0 && d1) { + len0 = curve0.push(p0); + len1 = curve1.push(p1); + } else if (len0 && len1) { + if (!span) { + drawArea(ctx, curve0, curve1, len0, len1); + len0 = len1 = 0; + curve0 = []; + curve1 = []; + } else { + if (d0) { + curve0.push(p0); + } + if (d1) { + curve1.push(p1); + } + } + } + } + + drawArea(ctx, curve0, curve1, len0, len1); + + ctx.closePath(); + ctx.fillStyle = color; + ctx.fill(); +} + +var plugin_filler = { + id: 'filler', + + afterDatasetsUpdate: function(chart, options) { + var count = (chart.data.datasets || []).length; + var propagate = options.propagate; + var sources = []; + var meta, i, el, source; + + for (i = 0; i < count; ++i) { + meta = chart.getDatasetMeta(i); + el = meta.dataset; + source = null; + + if (el && el._model && el instanceof elements.Line) { + source = { + visible: chart.isDatasetVisible(i), + fill: decodeFill(el, i, count), + chart: chart, + el: el + }; + } + + meta.$filler = source; + sources.push(source); + } + + for (i = 0; i < count; ++i) { + source = sources[i]; + if (!source) { + continue; + } + + source.fill = resolveTarget(sources, i, propagate); + source.boundary = computeBoundary(source); + source.mapper = createMapper(source); + } + }, + + beforeDatasetsDraw: function(chart) { + var metasets = chart._getSortedVisibleDatasetMetas(); + var ctx = chart.ctx; + var meta, i, el, view, points, mapper, color; + + for (i = metasets.length - 1; i >= 0; --i) { + meta = metasets[i].$filler; + + if (!meta || !meta.visible) { + continue; + } + + el = meta.el; + view = el._view; + points = el._children || []; + mapper = meta.mapper; + color = view.backgroundColor || core_defaults.global.defaultColor; + + if (mapper && color && points.length) { + helpers$1.canvas.clipArea(ctx, chart.chartArea); + doFill(ctx, points, mapper, view, color, el._loop); + helpers$1.canvas.unclipArea(ctx); + } + } + } +}; + +var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter; +var noop$1 = helpers$1.noop; +var valueOrDefault$e = helpers$1.valueOrDefault; + +core_defaults._set('global', { + legend: { + display: true, + position: 'top', + align: 'center', + fullWidth: true, + reverse: false, + weight: 1000, + + // a callback that will handle + onClick: function(e, legendItem) { + var index = legendItem.datasetIndex; + var ci = this.chart; + var meta = ci.getDatasetMeta(index); + + // See controller.isDatasetVisible comment + meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; + + // We hid a dataset ... rerender the chart + ci.update(); + }, + + onHover: null, + onLeave: null, + + labels: { + boxWidth: 40, + padding: 10, + // Generates labels shown in the legend + // Valid properties to return: + // text : text to display + // fillStyle : fill of coloured box + // strokeStyle: stroke of coloured box + // hidden : if this legend item refers to a hidden item + // lineCap : cap style for line + // lineDash + // lineDashOffset : + // lineJoin : + // lineWidth : + generateLabels: function(chart) { + var datasets = chart.data.datasets; + var options = chart.options.legend || {}; + var usePointStyle = options.labels && options.labels.usePointStyle; + + return chart._getSortedDatasetMetas().map(function(meta) { + var style = meta.controller.getStyle(usePointStyle ? 0 : undefined); + + return { + text: datasets[meta.index].label, + fillStyle: style.backgroundColor, + hidden: !chart.isDatasetVisible(meta.index), + lineCap: style.borderCapStyle, + lineDash: style.borderDash, + lineDashOffset: style.borderDashOffset, + lineJoin: style.borderJoinStyle, + lineWidth: style.borderWidth, + strokeStyle: style.borderColor, + pointStyle: style.pointStyle, + rotation: style.rotation, + + // Below is extra data used for toggling the datasets + datasetIndex: meta.index + }; + }, this); + } + } + }, + + legendCallback: function(chart) { + var list = document.createElement('ul'); + var datasets = chart.data.datasets; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + + for (i = 0, ilen = datasets.length; i < ilen; i++) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[i].backgroundColor; + if (datasets[i].label) { + listItem.appendChild(document.createTextNode(datasets[i].label)); + } + } + + return list.outerHTML; + } +}); + +/** + * Helper function to get the box width based on the usePointStyle option + * @param {object} labelopts - the label options on the legend + * @param {number} fontSize - the label font size + * @return {number} width of the color box area + */ +function getBoxWidth(labelOpts, fontSize) { + return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ? + fontSize : + labelOpts.boxWidth; +} + +/** + * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! + */ +var Legend = core_element.extend({ + + initialize: function(config) { + var me = this; + helpers$1.extend(me, config); + + // Contains hit boxes for each dataset (in dataset order) + me.legendHitBoxes = []; + + /** + * @private + */ + me._hoveredItem = null; + + // Are we in doughnut mode which has a different data type + me.doughnutMode = false; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + // Any function defined here is inherited by all legend types. + // Any function can be extended by the legend type + + beforeUpdate: noop$1, + update: function(maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + }, + afterUpdate: noop$1, + + // + + beforeSetDimensions: noop$1, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop$1, + + // + + beforeBuildLabels: noop$1, + buildLabels: function() { + var me = this; + var labelOpts = me.options.labels || {}; + var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || []; + + if (labelOpts.filter) { + legendItems = legendItems.filter(function(item) { + return labelOpts.filter(item, me.chart.data); + }); + } + + if (me.options.reverse) { + legendItems.reverse(); + } + + me.legendItems = legendItems; + }, + afterBuildLabels: noop$1, + + // + + beforeFit: noop$1, + fit: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var display = opts.display; + + var ctx = me.ctx; + + var labelFont = helpers$1.options._parseFont(labelOpts); + var fontSize = labelFont.size; + + // Reset hit boxes + var hitboxes = me.legendHitBoxes = []; + + var minSize = me.minSize; + var isHorizontal = me.isHorizontal(); + + if (isHorizontal) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = display ? 10 : 0; + } else { + minSize.width = display ? 10 : 0; + minSize.height = me.maxHeight; // fill all the height + } + + // Increase sizes here + if (!display) { + me.width = minSize.width = me.height = minSize.height = 0; + return; + } + ctx.font = labelFont.string; + + if (isHorizontal) { + // Labels + + // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one + var lineWidths = me.lineWidths = [0]; + var totalHeight = 0; + + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + + helpers$1.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) { + totalHeight += fontSize + labelOpts.padding; + lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; + } + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: width, + height: fontSize + }; + + lineWidths[lineWidths.length - 1] += width + labelOpts.padding; + }); + + minSize.height += totalHeight; + + } else { + var vPadding = labelOpts.padding; + var columnWidths = me.columnWidths = []; + var columnHeights = me.columnHeights = []; + var totalWidth = labelOpts.padding; + var currentColWidth = 0; + var currentColHeight = 0; + + helpers$1.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + // If too tall, go to new column + if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) { + totalWidth += currentColWidth + labelOpts.padding; + columnWidths.push(currentColWidth); // previous column width + columnHeights.push(currentColHeight); + currentColWidth = 0; + currentColHeight = 0; + } + + // Get max width + currentColWidth = Math.max(currentColWidth, itemWidth); + currentColHeight += fontSize + vPadding; + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: itemWidth, + height: fontSize + }; + }); + + totalWidth += currentColWidth; + columnWidths.push(currentColWidth); + columnHeights.push(currentColHeight); + minSize.width += totalWidth; + } + + me.width = minSize.width; + me.height = minSize.height; + }, + afterFit: noop$1, + + // Shared Methods + isHorizontal: function() { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + + // Actually draw the legend on the canvas + draw: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var globalDefaults = core_defaults.global; + var defaultColor = globalDefaults.defaultColor; + var lineDefault = globalDefaults.elements.line; + var legendHeight = me.height; + var columnHeights = me.columnHeights; + var legendWidth = me.width; + var lineWidths = me.lineWidths; + + if (!opts.display) { + return; + } + + var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width); + var ctx = me.ctx; + var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor); + var labelFont = helpers$1.options._parseFont(labelOpts); + var fontSize = labelFont.size; + var cursor; + + // Canvas setup + ctx.textAlign = rtlHelper.textAlign('left'); + ctx.textBaseline = 'middle'; + ctx.lineWidth = 0.5; + ctx.strokeStyle = fontColor; // for strikethrough effect + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = labelFont.string; + + var boxWidth = getBoxWidth(labelOpts, fontSize); + var hitboxes = me.legendHitBoxes; + + // current position + var drawLegendBox = function(x, y, legendItem) { + if (isNaN(boxWidth) || boxWidth <= 0) { + return; + } + + // Set the ctx for the box + ctx.save(); + + var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth); + ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor); + ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle); + ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset); + ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor); + + if (ctx.setLineDash) { + // IE 9 and 10 do not support line dash + ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash)); + } + + if (labelOpts && labelOpts.usePointStyle) { + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + var radius = boxWidth * Math.SQRT2 / 2; + var centerX = rtlHelper.xPlus(x, boxWidth / 2); + var centerY = y + fontSize / 2; + + // Draw pointStyle as legend symbol + helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation); + } else { + // Draw box as legend symbol + ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); + if (lineWidth !== 0) { + ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); + } + } + + ctx.restore(); + }; + + var fillText = function(x, y, legendItem, textWidth) { + var halfFontSize = fontSize / 2; + var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize); + var yMiddle = y + halfFontSize; + + ctx.fillText(legendItem.text, xLeft, yMiddle); + + if (legendItem.hidden) { + // Strikethrough the text if hidden + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(xLeft, yMiddle); + ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle); + ctx.stroke(); + } + }; + + var alignmentOffset = function(dimension, blockSize) { + switch (opts.align) { + case 'start': + return labelOpts.padding; + case 'end': + return dimension - blockSize; + default: // center + return (dimension - blockSize + labelOpts.padding) / 2; + } + }; + + // Horizontal + var isHorizontal = me.isHorizontal(); + if (isHorizontal) { + cursor = { + x: me.left + alignmentOffset(legendWidth, lineWidths[0]), + y: me.top + labelOpts.padding, + line: 0 + }; + } else { + cursor = { + x: me.left + labelOpts.padding, + y: me.top + alignmentOffset(legendHeight, columnHeights[0]), + line: 0 + }; + } + + helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection); + + var itemHeight = fontSize + labelOpts.padding; + helpers$1.each(me.legendItems, function(legendItem, i) { + var textWidth = ctx.measureText(legendItem.text).width; + var width = boxWidth + (fontSize / 2) + textWidth; + var x = cursor.x; + var y = cursor.y; + + rtlHelper.setWidth(me.minSize.width); + + // Use (me.left + me.minSize.width) and (me.top + me.minSize.height) + // instead of me.right and me.bottom because me.width and me.height + // may have been changed since me.minSize was calculated + if (isHorizontal) { + if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) { + y = cursor.y += itemHeight; + cursor.line++; + x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]); + } + } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) { + x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; + cursor.line++; + y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]); + } + + var realX = rtlHelper.x(x); + + drawLegendBox(realX, y, legendItem); + + hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width); + hitboxes[i].top = y; + + // Fill the actual label + fillText(realX, y, legendItem, textWidth); + + if (isHorizontal) { + cursor.x += width + labelOpts.padding; + } else { + cursor.y += itemHeight; + } + }); + + helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection); + }, + + /** + * @private + */ + _getLegendItemAt: function(x, y) { + var me = this; + var i, hitBox, lh; + + if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { + // See if we are touching one of the dataset boxes + lh = me.legendHitBoxes; + for (i = 0; i < lh.length; ++i) { + hitBox = lh[i]; + + if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { + // Touching an element + return me.legendItems[i]; + } + } + } + + return null; + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + */ + handleEvent: function(e) { + var me = this; + var opts = me.options; + var type = e.type === 'mouseup' ? 'click' : e.type; + var hoveredItem; + + if (type === 'mousemove') { + if (!opts.onHover && !opts.onLeave) { + return; + } + } else if (type === 'click') { + if (!opts.onClick) { + return; + } + } else { + return; + } + + // Chart event already has relative position in it + hoveredItem = me._getLegendItemAt(e.x, e.y); + + if (type === 'click') { + if (hoveredItem && opts.onClick) { + // use e.native for backwards compatibility + opts.onClick.call(me, e.native, hoveredItem); + } + } else { + if (opts.onLeave && hoveredItem !== me._hoveredItem) { + if (me._hoveredItem) { + opts.onLeave.call(me, e.native, me._hoveredItem); + } + me._hoveredItem = hoveredItem; + } + + if (opts.onHover && hoveredItem) { + // use e.native for backwards compatibility + opts.onHover.call(me, e.native, hoveredItem); + } + } + } +}); + +function createNewLegendAndAttach(chart, legendOpts) { + var legend = new Legend({ + ctx: chart.ctx, + options: legendOpts, + chart: chart + }); + + core_layouts.configure(chart, legend, legendOpts); + core_layouts.addBox(chart, legend); + chart.legend = legend; +} + +var plugin_legend = { + id: 'legend', + + /** + * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making + * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of + * the plugin, which one will be re-exposed in the chart.js file. + * https://github.com/chartjs/Chart.js/pull/2640 + * @private + */ + _element: Legend, + + beforeInit: function(chart) { + var legendOpts = chart.options.legend; + + if (legendOpts) { + createNewLegendAndAttach(chart, legendOpts); + } + }, + + beforeUpdate: function(chart) { + var legendOpts = chart.options.legend; + var legend = chart.legend; + + if (legendOpts) { + helpers$1.mergeIf(legendOpts, core_defaults.global.legend); + + if (legend) { + core_layouts.configure(chart, legend, legendOpts); + legend.options = legendOpts; + } else { + createNewLegendAndAttach(chart, legendOpts); + } + } else if (legend) { + core_layouts.removeBox(chart, legend); + delete chart.legend; + } + }, + + afterEvent: function(chart, e) { + var legend = chart.legend; + if (legend) { + legend.handleEvent(e); + } + } +}; + +var noop$2 = helpers$1.noop; + +core_defaults._set('global', { + title: { + display: false, + fontStyle: 'bold', + fullWidth: true, + padding: 10, + position: 'top', + text: '', + weight: 2000 // by default greater than legend (1000) to be above + } +}); + +/** + * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! + */ +var Title = core_element.extend({ + initialize: function(config) { + var me = this; + helpers$1.extend(me, config); + + // Contains hit boxes for each dataset (in dataset order) + me.legendHitBoxes = []; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + + beforeUpdate: noop$2, + update: function(maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + + }, + afterUpdate: noop$2, + + // + + beforeSetDimensions: noop$2, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop$2, + + // + + beforeBuildLabels: noop$2, + buildLabels: noop$2, + afterBuildLabels: noop$2, + + // + + beforeFit: noop$2, + fit: function() { + var me = this; + var opts = me.options; + var minSize = me.minSize = {}; + var isHorizontal = me.isHorizontal(); + var lineCount, textSize; + + if (!opts.display) { + me.width = minSize.width = me.height = minSize.height = 0; + return; + } + + lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1; + textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2; + + me.width = minSize.width = isHorizontal ? me.maxWidth : textSize; + me.height = minSize.height = isHorizontal ? textSize : me.maxHeight; + }, + afterFit: noop$2, + + // Shared Methods + isHorizontal: function() { + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + }, + + // Actually draw the title block on the canvas + draw: function() { + var me = this; + var ctx = me.ctx; + var opts = me.options; + + if (!opts.display) { + return; + } + + var fontOpts = helpers$1.options._parseFont(opts); + var lineHeight = fontOpts.lineHeight; + var offset = lineHeight / 2 + opts.padding; + var rotation = 0; + var top = me.top; + var left = me.left; + var bottom = me.bottom; + var right = me.right; + var maxWidth, titleX, titleY; + + ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour + ctx.font = fontOpts.string; + + // Horizontal + if (me.isHorizontal()) { + titleX = left + ((right - left) / 2); // midpoint of the width + titleY = top + offset; + maxWidth = right - left; + } else { + titleX = opts.position === 'left' ? left + offset : right - offset; + titleY = top + ((bottom - top) / 2); + maxWidth = bottom - top; + rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); + } + + ctx.save(); + ctx.translate(titleX, titleY); + ctx.rotate(rotation); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + var text = opts.text; + if (helpers$1.isArray(text)) { + var y = 0; + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], 0, y, maxWidth); + y += lineHeight; + } + } else { + ctx.fillText(text, 0, 0, maxWidth); + } + + ctx.restore(); + } +}); + +function createNewTitleBlockAndAttach(chart, titleOpts) { + var title = new Title({ + ctx: chart.ctx, + options: titleOpts, + chart: chart + }); + + core_layouts.configure(chart, title, titleOpts); + core_layouts.addBox(chart, title); + chart.titleBlock = title; +} + +var plugin_title = { + id: 'title', + + /** + * Backward compatibility: since 2.1.5, the title is registered as a plugin, making + * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of + * the plugin, which one will be re-exposed in the chart.js file. + * https://github.com/chartjs/Chart.js/pull/2640 + * @private + */ + _element: Title, + + beforeInit: function(chart) { + var titleOpts = chart.options.title; + + if (titleOpts) { + createNewTitleBlockAndAttach(chart, titleOpts); + } + }, + + beforeUpdate: function(chart) { + var titleOpts = chart.options.title; + var titleBlock = chart.titleBlock; + + if (titleOpts) { + helpers$1.mergeIf(titleOpts, core_defaults.global.title); + + if (titleBlock) { + core_layouts.configure(chart, titleBlock, titleOpts); + titleBlock.options = titleOpts; + } else { + createNewTitleBlockAndAttach(chart, titleOpts); + } + } else if (titleBlock) { + core_layouts.removeBox(chart, titleBlock); + delete chart.titleBlock; + } + } +}; + +var plugins = {}; +var filler = plugin_filler; +var legend = plugin_legend; +var title = plugin_title; +plugins.filler = filler; +plugins.legend = legend; +plugins.title = title; + +/** + * @namespace Chart + */ + + +core_controller.helpers = helpers$1; + +// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! +core_helpers(); + +core_controller._adapters = core_adapters; +core_controller.Animation = core_animation; +core_controller.animationService = core_animations; +core_controller.controllers = controllers; +core_controller.DatasetController = core_datasetController; +core_controller.defaults = core_defaults; +core_controller.Element = core_element; +core_controller.elements = elements; +core_controller.Interaction = core_interaction; +core_controller.layouts = core_layouts; +core_controller.platform = platform; +core_controller.plugins = core_plugins; +core_controller.Scale = core_scale; +core_controller.scaleService = core_scaleService; +core_controller.Ticks = core_ticks; +core_controller.Tooltip = core_tooltip; + +// Register built-in scales + +core_controller.helpers.each(scales, function(scale, type) { + core_controller.scaleService.registerScaleType(type, scale, scale._defaults); +}); + +// Load to register built-in adapters (as side effects) + + +// Loading built-in plugins + +for (var k in plugins) { + if (plugins.hasOwnProperty(k)) { + core_controller.plugins.register(plugins[k]); + } +} + +core_controller.platform.initialize(); + +var src = core_controller; +if (typeof window !== 'undefined') { + window.Chart = core_controller; +} + +// DEPRECATIONS + +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Chart + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ +core_controller.Chart = core_controller; + +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Legend + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +core_controller.Legend = plugins.legend._element; + +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Title + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +core_controller.Title = plugins.title._element; + +/** + * Provided for backward compatibility, use Chart.plugins instead + * @namespace Chart.pluginService + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +core_controller.pluginService = core_controller.plugins; + +/** + * Provided for backward compatibility, inheriting from Chart.PlugingBase has no + * effect, instead simply create/register plugins via plain JavaScript objects. + * @interface Chart.PluginBase + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ +core_controller.PluginBase = core_controller.Element.extend({}); + +/** + * Provided for backward compatibility, use Chart.helpers.canvas instead. + * @namespace Chart.canvasHelpers + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +core_controller.canvasHelpers = core_controller.helpers.canvas; + +/** + * Provided for backward compatibility, use Chart.layouts instead. + * @namespace Chart.layoutService + * @deprecated since version 2.7.3 + * @todo remove at version 3 + * @private + */ +core_controller.layoutService = core_controller.layouts; + +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart.LinearScaleBase + * @deprecated since version 2.8 + * @todo remove at version 3 + * @private + */ +core_controller.LinearScaleBase = scale_linearbase; + +/** + * Provided for backward compatibility, instead we should create a new Chart + * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`). + * @deprecated since version 2.8.0 + * @todo remove at version 3 + */ +core_controller.helpers.each( + [ + 'Bar', + 'Bubble', + 'Doughnut', + 'Line', + 'PolarArea', + 'Radar', + 'Scatter' + ], + function(klass) { + core_controller[klass] = function(ctx, cfg) { + return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, { + type: klass.charAt(0).toLowerCase() + klass.slice(1) + })); + }; + } +); + +return src; + +}))); diff --git a/lib/web/chartjs/Chart.bundle.min.js b/lib/web/chartjs/Chart.bundle.min.js new file mode 100644 index 0000000000000..55d9eb03f821a --- /dev/null +++ b/lib/web/chartjs/Chart.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Chart.js v2.9.3 + * https://www.chartjs.org + * (c) 2019 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Chart=e()}(this,(function(){"use strict";"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function t(){throw new Error("Dynamic requires are not currently supported by rollup-plugin-commonjs")}function e(t,e){return t(e={exports:{}},e.exports),e.exports}var n={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},i=e((function(t){var e={};for(var i in n)n.hasOwnProperty(i)&&(e[n[i]]=i);var a=t.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var r in a)if(a.hasOwnProperty(r)){if(!("channels"in a[r]))throw new Error("missing channels property: "+r);if(!("labels"in a[r]))throw new Error("missing channel labels property: "+r);if(a[r].labels.length!==a[r].channels)throw new Error("channel and label counts mismatch: "+r);var o=a[r].channels,s=a[r].labels;delete a[r].channels,delete a[r].labels,Object.defineProperty(a[r],"channels",{value:o}),Object.defineProperty(a[r],"labels",{value:s})}a.rgb.hsl=function(t){var e,n,i=t[0]/255,a=t[1]/255,r=t[2]/255,o=Math.min(i,a,r),s=Math.max(i,a,r),l=s-o;return s===o?e=0:i===s?e=(a-r)/l:a===s?e=2+(r-i)/l:r===s&&(e=4+(i-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),n=(o+s)/2,[e,100*(s===o?0:n<=.5?l/(s+o):l/(2-s-o)),100*n]},a.rgb.hsv=function(t){var e,n,i,a,r,o=t[0]/255,s=t[1]/255,l=t[2]/255,u=Math.max(o,s,l),d=u-Math.min(o,s,l),h=function(t){return(u-t)/6/d+.5};return 0===d?a=r=0:(r=d/u,e=h(o),n=h(s),i=h(l),o===u?a=i-n:s===u?a=1/3+e-i:l===u&&(a=2/3+n-e),a<0?a+=1:a>1&&(a-=1)),[360*a,100*r,100*u]},a.rgb.hwb=function(t){var e=t[0],n=t[1],i=t[2];return[a.rgb.hsl(t)[0],100*(1/255*Math.min(e,Math.min(n,i))),100*(i=1-1/255*Math.max(e,Math.max(n,i)))]},a.rgb.cmyk=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255;return[100*((1-n-(e=Math.min(1-n,1-i,1-a)))/(1-e)||0),100*((1-i-e)/(1-e)||0),100*((1-a-e)/(1-e)||0),100*e]},a.rgb.keyword=function(t){var i=e[t];if(i)return i;var a,r,o,s=1/0;for(var l in n)if(n.hasOwnProperty(l)){var u=n[l],d=(r=t,o=u,Math.pow(r[0]-o[0],2)+Math.pow(r[1]-o[1],2)+Math.pow(r[2]-o[2],2));d<s&&(s=d,a=l)}return a},a.keyword.rgb=function(t){return n[t]},a.rgb.xyz=function(t){var e=t[0]/255,n=t[1]/255,i=t[2]/255;return[100*(.4124*(e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]},a.rgb.lab=function(t){var e=a.rgb.xyz(t),n=e[0],i=e[1],r=e[2];return i/=100,r/=108.883,n=(n/=95.047)>.008856?Math.pow(n,1/3):7.787*n+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(n-i),200*(i-(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116))]},a.hsl.rgb=function(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0===s)return[r=255*l,r,r];e=2*l-(n=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(i=o+1/3*-(u-1))<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a},a.hsl.hsv=function(t){var e=t[0],n=t[1]/100,i=t[2]/100,a=n,r=Math.max(i,.01);return n*=(i*=2)<=1?i:2-i,a*=r<=1?r:2-r,[e,100*(0===i?2*a/(r+a):2*n/(i+n)),100*((i+n)/2)]},a.hsv.rgb=function(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r));switch(i*=255,a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}},a.hsv.hsl=function(t){var e,n,i,a=t[0],r=t[1]/100,o=t[2]/100,s=Math.max(o,.01);return i=(2-r)*o,n=r*s,[a,100*(n=(n/=(e=(2-r)*s)<=1?e:2-e)||0),100*(i/=2)]},a.hwb.rgb=function(t){var e,n,i,a,r,o,s,l=t[0]/360,u=t[1]/100,d=t[2]/100,h=u+d;switch(h>1&&(u/=h,d/=h),i=6*l-(e=Math.floor(6*l)),0!=(1&e)&&(i=1-i),a=u+i*((n=1-d)-u),e){default:case 6:case 0:r=n,o=a,s=u;break;case 1:r=a,o=n,s=u;break;case 2:r=u,o=n,s=a;break;case 3:r=u,o=a,s=n;break;case 4:r=a,o=u,s=n;break;case 5:r=n,o=u,s=a}return[255*r,255*o,255*s]},a.cmyk.rgb=function(t){var e=t[0]/100,n=t[1]/100,i=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a))]},a.xyz.rgb=function(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return n=-.9689*a+1.8758*r+.0415*o,i=.0557*a+-.204*r+1.057*o,e=(e=3.2406*a+-1.5372*r+-.4986*o)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:12.92*i,[255*(e=Math.min(Math.max(0,e),1)),255*(n=Math.min(Math.max(0,n),1)),255*(i=Math.min(Math.max(0,i),1))]},a.xyz.lab=function(t){var e=t[0],n=t[1],i=t[2];return n/=100,i/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(e-n),200*(n-(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116))]},a.lab.xyz=function(t){var e,n,i,a=t[0];e=t[1]/500+(n=(a+16)/116),i=n-t[2]/200;var r=Math.pow(n,3),o=Math.pow(e,3),s=Math.pow(i,3);return n=r>.008856?r:(n-16/116)/7.787,e=o>.008856?o:(e-16/116)/7.787,i=s>.008856?s:(i-16/116)/7.787,[e*=95.047,n*=100,i*=108.883]},a.lab.lch=function(t){var e,n=t[0],i=t[1],a=t[2];return(e=360*Math.atan2(a,i)/2/Math.PI)<0&&(e+=360),[n,Math.sqrt(i*i+a*a),e]},a.lch.lab=function(t){var e,n=t[0],i=t[1];return e=t[2]/360*2*Math.PI,[n,i*Math.cos(e),i*Math.sin(e)]},a.rgb.ansi16=function(t){var e=t[0],n=t[1],i=t[2],r=1 in arguments?arguments[1]:a.rgb.hsv(t)[2];if(0===(r=Math.round(r/50)))return 30;var o=30+(Math.round(i/255)<<2|Math.round(n/255)<<1|Math.round(e/255));return 2===r&&(o+=60),o},a.hsv.ansi16=function(t){return a.rgb.ansi16(a.hsv.rgb(t),t[2])},a.rgb.ansi256=function(t){var e=t[0],n=t[1],i=t[2];return e===n&&n===i?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(n/255*5)+Math.round(i/255*5)},a.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var n=.5*(1+~~(t>50));return[(1&e)*n*255,(e>>1&1)*n*255,(e>>2&1)*n*255]},a.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var n;return t-=16,[Math.floor(t/36)/5*255,Math.floor((n=t%36)/6)/5*255,n%6/5*255]},a.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},a.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var n=e[0];3===e[0].length&&(n=n.split("").map((function(t){return t+t})).join(""));var i=parseInt(n,16);return[i>>16&255,i>>8&255,255&i]},a.rgb.hcg=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255,r=Math.max(Math.max(n,i),a),o=Math.min(Math.min(n,i),a),s=r-o;return e=s<=0?0:r===n?(i-a)/s%6:r===i?2+(a-n)/s:4+(n-i)/s+4,e/=6,[360*(e%=1),100*s,100*(s<1?o/(1-s):0)]},a.hsl.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=1,a=0;return(i=n<.5?2*e*n:2*e*(1-n))<1&&(a=(n-.5*i)/(1-i)),[t[0],100*i,100*a]},a.hsv.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=e*n,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.hcg.rgb=function(t){var e=t[0]/360,n=t[1]/100,i=t[2]/100;if(0===n)return[255*i,255*i,255*i];var a,r=[0,0,0],o=e%1*6,s=o%1,l=1-s;switch(Math.floor(o)){case 0:r[0]=1,r[1]=s,r[2]=0;break;case 1:r[0]=l,r[1]=1,r[2]=0;break;case 2:r[0]=0,r[1]=1,r[2]=s;break;case 3:r[0]=0,r[1]=l,r[2]=1;break;case 4:r[0]=s,r[1]=0,r[2]=1;break;default:r[0]=1,r[1]=0,r[2]=l}return a=(1-n)*i,[255*(n*r[0]+a),255*(n*r[1]+a),255*(n*r[2]+a)]},a.hcg.hsv=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e),i=0;return n>0&&(i=e/n),[t[0],100*i,100*n]},a.hcg.hsl=function(t){var e=t[1]/100,n=t[2]/100*(1-e)+.5*e,i=0;return n>0&&n<.5?i=e/(2*n):n>=.5&&n<1&&(i=e/(2*(1-n))),[t[0],100*i,100*n]},a.hcg.hwb=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e);return[t[0],100*(n-e),100*(1-n)]},a.hwb.hcg=function(t){var e=t[1]/100,n=1-t[2]/100,i=n-e,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},a.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},a.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},a.gray.hsl=a.gray.hsv=function(t){return[0,0,t[0]]},a.gray.hwb=function(t){return[0,100,t[0]]},a.gray.cmyk=function(t){return[0,0,0,t[0]]},a.gray.lab=function(t){return[t[0],0,0]},a.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),n=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(n.length)+n},a.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}}));i.rgb,i.hsl,i.hsv,i.hwb,i.cmyk,i.xyz,i.lab,i.lch,i.hex,i.keyword,i.ansi16,i.ansi256,i.hcg,i.apple,i.gray;function a(t){var e=function(){for(var t={},e=Object.keys(i),n=e.length,a=0;a<n;a++)t[e[a]]={distance:-1,parent:null};return t}(),n=[t];for(e[t].distance=0;n.length;)for(var a=n.pop(),r=Object.keys(i[a]),o=r.length,s=0;s<o;s++){var l=r[s],u=e[l];-1===u.distance&&(u.distance=e[a].distance+1,u.parent=a,n.unshift(l))}return e}function r(t,e){return function(n){return e(t(n))}}function o(t,e){for(var n=[e[t].parent,t],a=i[e[t].parent][t],o=e[t].parent;e[o].parent;)n.unshift(e[o].parent),a=r(i[e[o].parent][o],a),o=e[o].parent;return a.conversion=n,a}var s={};Object.keys(i).forEach((function(t){s[t]={},Object.defineProperty(s[t],"channels",{value:i[t].channels}),Object.defineProperty(s[t],"labels",{value:i[t].labels});var e=function(t){for(var e=a(t),n={},i=Object.keys(e),r=i.length,s=0;s<r;s++){var l=i[s];null!==e[l].parent&&(n[l]=o(l,e))}return n}(t);Object.keys(e).forEach((function(n){var i=e[n];s[t][n]=function(t){var e=function(e){if(null==e)return e;arguments.length>1&&(e=Array.prototype.slice.call(arguments));var n=t(e);if("object"==typeof n)for(var i=n.length,a=0;a<i;a++)n[a]=Math.round(n[a]);return n};return"conversion"in t&&(e.conversion=t.conversion),e}(i),s[t][n].raw=function(t){var e=function(e){return null==e?e:(arguments.length>1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}(i)}))}));var l=s,u={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},d={getRgba:h,getHsla:c,getRgb:function(t){var e=h(t);return e&&e.slice(0,3)},getHsl:function(t){var e=c(t);return e&&e.slice(0,3)},getHwb:f,getAlpha:function(t){var e=h(t);if(e)return e[3];if(e=c(t))return e[3];if(e=f(t))return e[3]},hexString:function(t,e){e=void 0!==e&&3===t.length?e:t[3];return"#"+b(t[0])+b(t[1])+b(t[2])+(e>=0&&e<1?b(Math.round(255*e)):"")},rgbString:function(t,e){if(e<1||t[3]&&t[3]<1)return g(t,e);return"rgb("+t[0]+", "+t[1]+", "+t[2]+")"},rgbaString:g,percentString:function(t,e){if(e<1||t[3]&&t[3]<1)return m(t,e);var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+n+"%, "+i+"%, "+a+"%)"},percentaString:m,hslString:function(t,e){if(e<1||t[3]&&t[3]<1)return p(t,e);return"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"},hslaString:p,hwbString:function(t,e){void 0===e&&(e=void 0!==t[3]?t[3]:1);return"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"},keyword:function(t){return y[t.slice(0,3)]}};function h(t){if(t){var e=[0,0,0],n=1,i=t.match(/^#([a-fA-F0-9]{3,4})$/i),a="";if(i){a=(i=i[1])[3];for(var r=0;r<e.length;r++)e[r]=parseInt(i[r]+i[r],16);a&&(n=Math.round(parseInt(a+a,16)/255*100)/100)}else if(i=t.match(/^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i)){a=i[2],i=i[1];for(r=0;r<e.length;r++)e[r]=parseInt(i.slice(2*r,2*r+2),16);a&&(n=Math.round(parseInt(a,16)/255*100)/100)}else if(i=t.match(/^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i)){for(r=0;r<e.length;r++)e[r]=parseInt(i[r+1]);n=parseFloat(i[4])}else if(i=t.match(/^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i)){for(r=0;r<e.length;r++)e[r]=Math.round(2.55*parseFloat(i[r+1]));n=parseFloat(i[4])}else if(i=t.match(/(\w+)/)){if("transparent"==i[1])return[0,0,0,0];if(!(e=u[i[1]]))return}for(r=0;r<e.length;r++)e[r]=v(e[r],0,255);return n=n||0==n?v(n,0,1):1,e[3]=n,e}}function c(t){if(t){var e=t.match(/^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/);if(e){var n=parseFloat(e[4]);return[v(parseInt(e[1]),0,360),v(parseFloat(e[2]),0,100),v(parseFloat(e[3]),0,100),v(isNaN(n)?1:n,0,1)]}}}function f(t){if(t){var e=t.match(/^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/);if(e){var n=parseFloat(e[4]);return[v(parseInt(e[1]),0,360),v(parseFloat(e[2]),0,100),v(parseFloat(e[3]),0,100),v(isNaN(n)?1:n,0,1)]}}}function g(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function m(t,e){return"rgba("+Math.round(t[0]/255*100)+"%, "+Math.round(t[1]/255*100)+"%, "+Math.round(t[2]/255*100)+"%, "+(e||t[3]||1)+")"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function v(t,e,n){return Math.min(Math.max(e,t),n)}function b(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var y={};for(var x in u)y[u[x]]=x;var _=function(t){return t instanceof _?t:this instanceof _?(this.valid=!1,this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},void("string"==typeof t?(e=d.getRgba(t))?this.setValues("rgb",e):(e=d.getHsla(t))?this.setValues("hsl",e):(e=d.getHwb(t))&&this.setValues("hwb",e):"object"==typeof t&&(void 0!==(e=t).r||void 0!==e.red?this.setValues("rgb",e):void 0!==e.l||void 0!==e.lightness?this.setValues("hsl",e):void 0!==e.v||void 0!==e.value?this.setValues("hsv",e):void 0!==e.w||void 0!==e.whiteness?this.setValues("hwb",e):void 0===e.c&&void 0===e.cyan||this.setValues("cmyk",e)))):new _(t);var e};_.prototype={isValid:function(){return this.valid},rgb:function(){return this.setSpace("rgb",arguments)},hsl:function(){return this.setSpace("hsl",arguments)},hsv:function(){return this.setSpace("hsv",arguments)},hwb:function(){return this.setSpace("hwb",arguments)},cmyk:function(){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){var t=this.values;return 1!==t.alpha?t.hwb.concat([t.alpha]):t.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values;return t.rgb.concat([t.alpha])},hslaArray:function(){var t=this.values;return t.hsl.concat([t.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return t&&(t=(t%=360)<0?360+t:t),this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return d.hexString(this.values.rgb)},rgbString:function(){return d.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return d.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return d.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return d.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return d.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return d.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return d.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){var t=this.values.rgb;return t[0]<<16|t[1]<<8|t[2]},luminosity:function(){for(var t=this.values.rgb,e=[],n=0;n<t.length;n++){var i=t[n]/255;e[n]=i<=.03928?i/12.92:Math.pow((i+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),n=t.luminosity();return e>n?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=t,i=void 0===e?.5:e,a=2*i-1,r=this.alpha()-n.alpha(),o=((a*r==-1?a:(a+r)/(1+a*r))+1)/2,s=1-o;return this.rgb(o*this.red()+s*n.red(),o*this.green()+s*n.green(),o*this.blue()+s*n.blue()).alpha(this.alpha()*i+n.alpha()*(1-i))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new _,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},_.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},_.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},_.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i<t.length;i++)n[t.charAt(i)]=e[t][i];return 1!==e.alpha&&(n.a=e.alpha),n},_.prototype.setValues=function(t,e){var n,i,a=this.values,r=this.spaces,o=this.maxes,s=1;if(this.valid=!0,"alpha"===t)s=e;else if(e.length)a[t]=e.slice(0,t.length),s=e[t.length];else if(void 0!==e[t.charAt(0)]){for(n=0;n<t.length;n++)a[t][n]=e[t.charAt(n)];s=e.a}else if(void 0!==e[r[t][0]]){var u=r[t];for(n=0;n<t.length;n++)a[t][n]=e[u[n]];s=e.alpha}if(a.alpha=Math.max(0,Math.min(1,void 0===s?a.alpha:s)),"alpha"===t)return!1;for(n=0;n<t.length;n++)i=Math.max(0,Math.min(o[t][n],a[t][n])),a[t][n]=Math.round(i);for(var d in r)d!==t&&(a[d]=l[t][d](a[t]));return!0},_.prototype.setSpace=function(t,e){var n=e[0];return void 0===n?this.getValues(t):("number"==typeof n&&(n=Array.prototype.slice.call(e)),this.setValues(t,n),this)},_.prototype.setChannel=function(t,e,n){var i=this.values[t];return void 0===n?i[e]:n===i[e]?this:(i[e]=n,this.setValues(t,i),this)},"undefined"!=typeof window&&(window.Color=_);var w,k=_,M={noop:function(){},uid:(w=0,function(){return w++}),isNullOrUndef:function(t){return null==t},isArray:function(t){if(Array.isArray&&Array.isArray(t))return!0;var e=Object.prototype.toString.call(t);return"[object"===e.substr(0,7)&&"Array]"===e.substr(-6)},isObject:function(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)},isFinite:function(t){return("number"==typeof t||t instanceof Number)&&isFinite(t)},valueOrDefault:function(t,e){return void 0===t?e:t},valueAtIndexOrDefault:function(t,e,n){return M.valueOrDefault(M.isArray(t)?t[e]:t,n)},callback:function(t,e,n){if(t&&"function"==typeof t.call)return t.apply(n,e)},each:function(t,e,n,i){var a,r,o;if(M.isArray(t))if(r=t.length,i)for(a=r-1;a>=0;a--)e.call(n,t[a],a);else for(a=0;a<r;a++)e.call(n,t[a],a);else if(M.isObject(t))for(r=(o=Object.keys(t)).length,a=0;a<r;a++)e.call(n,t[o[a]],o[a])},arrayEquals:function(t,e){var n,i,a,r;if(!t||!e||t.length!==e.length)return!1;for(n=0,i=t.length;n<i;++n)if(a=t[n],r=e[n],a instanceof Array&&r instanceof Array){if(!M.arrayEquals(a,r))return!1}else if(a!==r)return!1;return!0},clone:function(t){if(M.isArray(t))return t.map(M.clone);if(M.isObject(t)){for(var e={},n=Object.keys(t),i=n.length,a=0;a<i;++a)e[n[a]]=M.clone(t[n[a]]);return e}return t},_merger:function(t,e,n,i){var a=e[t],r=n[t];M.isObject(a)&&M.isObject(r)?M.merge(a,r,i):e[t]=M.clone(r)},_mergerIf:function(t,e,n){var i=e[t],a=n[t];M.isObject(i)&&M.isObject(a)?M.mergeIf(i,a):e.hasOwnProperty(t)||(e[t]=M.clone(a))},merge:function(t,e,n){var i,a,r,o,s,l=M.isArray(e)?e:[e],u=l.length;if(!M.isObject(t))return t;for(i=(n=n||{}).merger||M._merger,a=0;a<u;++a)if(e=l[a],M.isObject(e))for(s=0,o=(r=Object.keys(e)).length;s<o;++s)i(r[s],t,e,n);return t},mergeIf:function(t,e){return M.merge(t,e,{merger:M._mergerIf})},extend:Object.assign||function(t){return M.merge(t,[].slice.call(arguments,1),{merger:function(t,e,n){e[t]=n[t]}})},inherits:function(t){var e=this,n=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},i=function(){this.constructor=n};return i.prototype=e.prototype,n.prototype=new i,n.extend=M.inherits,t&&M.extend(n.prototype,t),n.__super__=e.prototype,n},_deprecated:function(t,e,n,i){void 0!==e&&console.warn(t+': "'+n+'" is deprecated. Please use "'+i+'" instead')}},S=M;M.callCallback=M.callback,M.indexOf=function(t,e,n){return Array.prototype.indexOf.call(t,e,n)},M.getValueOrDefault=M.valueOrDefault,M.getValueAtIndexOrDefault=M.valueAtIndexOrDefault;var D={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return(t-=1)*t*t+1},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-((t-=1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return t*t*t*t*t},easeOutQuint:function(t){return(t-=1)*t*t*t*t+1},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return 1-Math.cos(t*(Math.PI/2))},easeOutSine:function(t){return Math.sin(t*(Math.PI/2))},easeInOutSine:function(t){return-.5*(Math.cos(Math.PI*t)-1)},easeInExpo:function(t){return 0===t?0:Math.pow(2,10*(t-1))},easeOutExpo:function(t){return 1===t?1:1-Math.pow(2,-10*t)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(2-Math.pow(2,-10*--t))},easeInCirc:function(t){return t>=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-D.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*D.easeInBounce(2*t):.5*D.easeOutBounce(2*t-1)+.5}},C={effects:D};S.easingEffects=D;var P=Math.PI,T=P/180,O=2*P,A=P/2,F=P/4,I=2*P/3,L={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,r){if(r){var o=Math.min(r,a/2,i/2),s=e+o,l=n+o,u=e+i-o,d=n+a-o;t.moveTo(e,l),s<u&&l<d?(t.arc(s,l,o,-P,-A),t.arc(u,l,o,-A,0),t.arc(u,d,o,0,A),t.arc(s,d,o,A,P)):s<u?(t.moveTo(s,n),t.arc(u,l,o,-A,A),t.arc(s,l,o,A,P+A)):l<d?(t.arc(s,l,o,-P,0),t.arc(s,d,o,0,P)):t.arc(s,l,o,-P,P),t.closePath(),t.moveTo(e,n)}else t.rect(e,n,i,a)},drawPoint:function(t,e,n,i,a,r){var o,s,l,u,d,h=(r||0)*T;if(e&&"object"==typeof e&&("[object HTMLImageElement]"===(o=e.toString())||"[object HTMLCanvasElement]"===o))return t.save(),t.translate(i,a),t.rotate(h),t.drawImage(e,-e.width/2,-e.height/2,e.width,e.height),void t.restore();if(!(isNaN(n)||n<=0)){switch(t.beginPath(),e){default:t.arc(i,a,n,0,O),t.closePath();break;case"triangle":t.moveTo(i+Math.sin(h)*n,a-Math.cos(h)*n),h+=I,t.lineTo(i+Math.sin(h)*n,a-Math.cos(h)*n),h+=I,t.lineTo(i+Math.sin(h)*n,a-Math.cos(h)*n),t.closePath();break;case"rectRounded":u=n-(d=.516*n),s=Math.cos(h+F)*u,l=Math.sin(h+F)*u,t.arc(i-s,a-l,d,h-P,h-A),t.arc(i+l,a-s,d,h-A,h),t.arc(i+s,a+l,d,h,h+A),t.arc(i-l,a+s,d,h+A,h+P),t.closePath();break;case"rect":if(!r){u=Math.SQRT1_2*n,t.rect(i-u,a-u,2*u,2*u);break}h+=F;case"rectRot":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+l,a-s),t.lineTo(i+s,a+l),t.lineTo(i-l,a+s),t.closePath();break;case"crossRot":h+=F;case"cross":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s);break;case"star":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s),h+=F,s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s);break;case"line":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l);break;case"dash":t.moveTo(i,a),t.lineTo(i+Math.cos(h)*n,a+Math.sin(h)*n)}t.fill(),t.stroke()}},_isPointInArea:function(t,e){return t.x>e.left-1e-6&&t.x<e.right+1e-6&&t.y>e.top-1e-6&&t.y<e.bottom+1e-6},clipArea:function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},unclipArea:function(t){t.restore()},lineTo:function(t,e,n,i){var a=n.steppedLine;if(a){if("middle"===a){var r=(e.x+n.x)/2;t.lineTo(r,i?n.y:e.y),t.lineTo(r,i?e.y:n.y)}else"after"===a&&!i||"after"!==a&&i?t.lineTo(e.x,n.y):t.lineTo(n.x,e.y);t.lineTo(n.x,n.y)}else n.tension?t.bezierCurveTo(i?e.controlPointPreviousX:e.controlPointNextX,i?e.controlPointPreviousY:e.controlPointNextY,i?n.controlPointNextX:n.controlPointPreviousX,i?n.controlPointNextY:n.controlPointPreviousY,n.x,n.y):t.lineTo(n.x,n.y)}},R=L;S.clear=L.clear,S.drawRoundedRectangle=function(t){t.beginPath(),L.roundedRect.apply(L,arguments)};var N={_set:function(t,e){return S.merge(this[t]||(this[t]={}),e)}};N._set("global",{defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",defaultLineHeight:1.2,showLines:!0});var W=N,Y=S.valueOrDefault;var z={toLineHeight:function(t,e){var n=(""+t).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);if(!n||"normal"===n[1])return 1.2*e;switch(t=+n[2],n[3]){case"px":return t;case"%":t/=100}return e*t},toPadding:function(t){var e,n,i,a;return S.isObject(t)?(e=+t.top||0,n=+t.right||0,i=+t.bottom||0,a=+t.left||0):e=n=i=a=+t||0,{top:e,right:n,bottom:i,left:a,height:e+i,width:a+n}},_parseFont:function(t){var e=W.global,n=Y(t.fontSize,e.defaultFontSize),i={family:Y(t.fontFamily,e.defaultFontFamily),lineHeight:S.options.toLineHeight(Y(t.lineHeight,e.defaultLineHeight),n),size:n,style:Y(t.fontStyle,e.defaultFontStyle),weight:null,string:""};return i.string=function(t){return!t||S.isNullOrUndef(t.size)||S.isNullOrUndef(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}(i),i},resolve:function(t,e,n,i){var a,r,o,s=!0;for(a=0,r=t.length;a<r;++a)if(void 0!==(o=t[a])&&(void 0!==e&&"function"==typeof o&&(o=o(e),s=!1),void 0!==n&&S.isArray(o)&&(o=o[n],s=!1),void 0!==o))return i&&!s&&(i.cacheable=!1),o}},E={_factorize:function(t){var e,n=[],i=Math.sqrt(t);for(e=1;e<i;e++)t%e==0&&(n.push(e),n.push(t/e));return i===(0|i)&&n.push(i),n.sort((function(t,e){return t-e})).pop(),n},log10:Math.log10||function(t){var e=Math.log(t)*Math.LOG10E,n=Math.round(e);return t===Math.pow(10,n)?n:e}},V=E;S.log10=E.log10;var H=S,B=C,j=R,U=z,G=V,q={getRtlAdapter:function(t,e,n){return t?function(t,e){return{x:function(n){return t+t+e-n},setWidth:function(t){e=t},textAlign:function(t){return"center"===t?t:"right"===t?"left":"right"},xPlus:function(t,e){return t-e},leftForLtr:function(t,e){return t-e}}}(e,n):{x:function(t){return t},setWidth:function(t){},textAlign:function(t){return t},xPlus:function(t,e){return t+e},leftForLtr:function(t,e){return t}}},overrideTextDirection:function(t,e){var n,i;"ltr"!==e&&"rtl"!==e||(i=[(n=t.canvas.style).getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",e,"important"),t.prevTextDirection=i)},restoreTextDirection:function(t){var e=t.prevTextDirection;void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}};H.easing=B,H.canvas=j,H.options=U,H.math=G,H.rtl=q;var Z=function(t){H.extend(this,t),this.initialize.apply(this,arguments)};H.extend(Z.prototype,{_type:void 0,initialize:function(){this.hidden=!1},pivot:function(){var t=this;return t._view||(t._view=H.extend({},t._model)),t._start={},t},transition:function(t){var e=this,n=e._model,i=e._start,a=e._view;return n&&1!==t?(a||(a=e._view={}),i||(i=e._start={}),function(t,e,n,i){var a,r,o,s,l,u,d,h,c,f=Object.keys(n);for(a=0,r=f.length;a<r;++a)if(u=n[o=f[a]],e.hasOwnProperty(o)||(e[o]=u),(s=e[o])!==u&&"_"!==o[0]){if(t.hasOwnProperty(o)||(t[o]=s),(d=typeof u)===typeof(l=t[o]))if("string"===d){if((h=k(l)).valid&&(c=k(u)).valid){e[o]=c.mix(h,i).rgbString();continue}}else if(H.isFinite(l)&&H.isFinite(u)){e[o]=l+(u-l)*i;continue}e[o]=u}}(i,a,n,t),e):(e._view=H.extend({},n),e._start=null,e)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return H.isNumber(this._model.x)&&H.isNumber(this._model.y)}}),Z.extend=H.inherits;var $=Z,X=$.extend({chart:null,currentStep:0,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),K=X;Object.defineProperty(X.prototype,"animationObject",{get:function(){return this}}),Object.defineProperty(X.prototype,"chartInstance",{get:function(){return this.chart},set:function(t){this.chart=t}}),W._set("global",{animation:{duration:1e3,easing:"easeOutQuart",onProgress:H.noop,onComplete:H.noop}});var J={animations:[],request:null,addAnimation:function(t,e,n,i){var a,r,o=this.animations;for(e.chart=t,e.startTime=Date.now(),e.duration=n,i||(t.animating=!0),a=0,r=o.length;a<r;++a)if(o[a].chart===t)return void(o[a]=e);o.push(e),1===o.length&&this.requestAnimationFrame()},cancelAnimation:function(t){var e=H.findIndex(this.animations,(function(e){return e.chart===t}));-1!==e&&(this.animations.splice(e,1),t.animating=!1)},requestAnimationFrame:function(){var t=this;null===t.request&&(t.request=H.requestAnimFrame.call(window,(function(){t.request=null,t.startDigest()})))},startDigest:function(){this.advance(),this.animations.length>0&&this.requestAnimationFrame()},advance:function(){for(var t,e,n,i,a=this.animations,r=0;r<a.length;)e=(t=a[r]).chart,n=t.numSteps,i=Math.floor((Date.now()-t.startTime)/t.duration*n)+1,t.currentStep=Math.min(i,n),H.callback(t.render,[e,t],e),H.callback(t.onAnimationProgress,[t],e),t.currentStep>=n?(H.callback(t.onAnimationComplete,[t],e),e.animating=!1,a.splice(r,1)):++r}},Q=H.options.resolve,tt=["push","pop","shift","splice","unshift"];function et(t,e){var n=t._chartjs;if(n){var i=n.listeners,a=i.indexOf(e);-1!==a&&i.splice(a,1),i.length>0||(tt.forEach((function(e){delete t[e]})),delete t._chartjs)}}var nt=function(t,e){this.initialize(t,e)};H.extend(nt.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements(),n._type=n.getMeta().type},updateIndex:function(t){this.index=t},linkScales:function(){var t=this.getMeta(),e=this.chart,n=e.scales,i=this.getDataset(),a=e.options.scales;null!==t.xAxisID&&t.xAxisID in n&&!i.xAxisID||(t.xAxisID=i.xAxisID||a.xAxes[0].id),null!==t.yAxisID&&t.yAxisID in n&&!i.yAxisID||(t.yAxisID=i.yAxisID||a.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&et(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,n=this.getMeta(),i=this.getDataset().data||[],a=n.data;for(t=0,e=i.length;t<e;++t)a[t]=a[t]||this.createMetaData(t);n.dataset=n.dataset||this.createMetaDataset()},addElementAndReset:function(t){var e=this.createMetaData(t);this.getMeta().data.splice(t,0,e),this.updateElement(e,t,!0)},buildOrUpdateElements:function(){var t,e,n=this,i=n.getDataset(),a=i.data||(i.data=[]);n._data!==a&&(n._data&&et(n._data,n),a&&Object.isExtensible(a)&&(e=n,(t=a)._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),tt.forEach((function(e){var n="onData"+e.charAt(0).toUpperCase()+e.slice(1),i=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value:function(){var e=Array.prototype.slice.call(arguments),a=i.apply(this,e);return H.each(t._chartjs.listeners,(function(t){"function"==typeof t[n]&&t[n].apply(t,e)})),a}})})))),n._data=a),n.resyncElements()},_configure:function(){this._config=H.merge({},[this.chart.options.datasets[this._type],this.getDataset()],{merger:function(t,e,n){"_meta"!==t&&"data"!==t&&H._merger(t,e,n)}})},_update:function(t){this._configure(),this._cachedDataOpts=null,this.update(t)},update:H.noop,transition:function(t){for(var e=this.getMeta(),n=e.data||[],i=n.length,a=0;a<i;++a)n[a].transition(t);e.dataset&&e.dataset.transition(t)},draw:function(){var t=this.getMeta(),e=t.data||[],n=e.length,i=0;for(t.dataset&&t.dataset.draw();i<n;++i)e[i].draw()},getStyle:function(t){var e,n=this.getMeta(),i=n.dataset;return this._configure(),i&&void 0===t?e=this._resolveDatasetElementOptions(i||{}):(t=t||0,e=this._resolveDataElementOptions(n.data[t]||{},t)),!1!==e.fill&&null!==e.fill||(e.backgroundColor=e.borderColor),e},_resolveDatasetElementOptions:function(t,e){var n,i,a,r,o=this,s=o.chart,l=o._config,u=t.custom||{},d=s.options.elements[o.datasetElementType.prototype._type]||{},h=o._datasetElementOptions,c={},f={chart:s,dataset:o.getDataset(),datasetIndex:o.index,hover:e};for(n=0,i=h.length;n<i;++n)a=h[n],r=e?"hover"+a.charAt(0).toUpperCase()+a.slice(1):a,c[a]=Q([u[r],l[r],d[r]],f);return c},_resolveDataElementOptions:function(t,e){var n=this,i=t&&t.custom,a=n._cachedDataOpts;if(a&&!i)return a;var r,o,s,l,u=n.chart,d=n._config,h=u.options.elements[n.dataElementType.prototype._type]||{},c=n._dataElementOptions,f={},g={chart:u,dataIndex:e,dataset:n.getDataset(),datasetIndex:n.index},m={cacheable:!i};if(i=i||{},H.isArray(c))for(o=0,s=c.length;o<s;++o)f[l=c[o]]=Q([i[l],d[l],h[l]],g,e,m);else for(o=0,s=(r=Object.keys(c)).length;o<s;++o)f[l=r[o]]=Q([i[l],d[c[l]],d[l],h[l]],g,e,m);return m.cacheable&&(n._cachedDataOpts=Object.freeze(f)),f},removeHoverStyle:function(t){H.merge(t._model,t.$previousStyle||{}),delete t.$previousStyle},setHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],n=t._index,i=t.custom||{},a=t._model,r=H.getHoverColor;t.$previousStyle={backgroundColor:a.backgroundColor,borderColor:a.borderColor,borderWidth:a.borderWidth},a.backgroundColor=Q([i.hoverBackgroundColor,e.hoverBackgroundColor,r(a.backgroundColor)],void 0,n),a.borderColor=Q([i.hoverBorderColor,e.hoverBorderColor,r(a.borderColor)],void 0,n),a.borderWidth=Q([i.hoverBorderWidth,e.hoverBorderWidth,a.borderWidth],void 0,n)},_removeDatasetHoverStyle:function(){var t=this.getMeta().dataset;t&&this.removeHoverStyle(t)},_setDatasetHoverStyle:function(){var t,e,n,i,a,r,o=this.getMeta().dataset,s={};if(o){for(r=o._model,a=this._resolveDatasetElementOptions(o,!0),t=0,e=(i=Object.keys(a)).length;t<e;++t)s[n=i[t]]=r[n],r[n]=a[n];o.$previousStyle=s}},resyncElements:function(){var t=this.getMeta(),e=this.getDataset().data,n=t.data.length,i=e.length;i<n?t.data.splice(i,n-i):i>n&&this.insertElements(n,i-n)},insertElements:function(t,e){for(var n=0;n<e;++n)this.addElementAndReset(t+n)},onDataPush:function(){var t=arguments.length;this.insertElements(this.getDataset().data.length-t,t)},onDataPop:function(){this.getMeta().data.pop()},onDataShift:function(){this.getMeta().data.shift()},onDataSplice:function(t,e){this.getMeta().data.splice(t,e),this.insertElements(t,arguments.length-2)},onDataUnshift:function(){this.insertElements(0,arguments.length)}}),nt.extend=H.inherits;var it=nt,at=2*Math.PI;function rt(t,e){var n=e.startAngle,i=e.endAngle,a=e.pixelMargin,r=a/e.outerRadius,o=e.x,s=e.y;t.beginPath(),t.arc(o,s,e.outerRadius,n-r,i+r),e.innerRadius>a?(r=a/e.innerRadius,t.arc(o,s,e.innerRadius-a,i+r,n-r,!0)):t.arc(o,s,a,i+Math.PI/2,n-Math.PI/2),t.closePath(),t.clip()}function ot(t,e,n){var i="inner"===e.borderAlign;i?(t.lineWidth=2*e.borderWidth,t.lineJoin="round"):(t.lineWidth=e.borderWidth,t.lineJoin="bevel"),n.fullCircles&&function(t,e,n,i){var a,r=n.endAngle;for(i&&(n.endAngle=n.startAngle+at,rt(t,n),n.endAngle=r,n.endAngle===n.startAngle&&n.fullCircles&&(n.endAngle+=at,n.fullCircles--)),t.beginPath(),t.arc(n.x,n.y,n.innerRadius,n.startAngle+at,n.startAngle,!0),a=0;a<n.fullCircles;++a)t.stroke();for(t.beginPath(),t.arc(n.x,n.y,e.outerRadius,n.startAngle,n.startAngle+at),a=0;a<n.fullCircles;++a)t.stroke()}(t,e,n,i),i&&rt(t,n),t.beginPath(),t.arc(n.x,n.y,e.outerRadius,n.startAngle,n.endAngle),t.arc(n.x,n.y,n.innerRadius,n.endAngle,n.startAngle,!0),t.closePath(),t.stroke()}W._set("global",{elements:{arc:{backgroundColor:W.global.defaultColor,borderColor:"#fff",borderWidth:2,borderAlign:"center"}}});var st=$.extend({_type:"arc",inLabelRange:function(t){var e=this._view;return!!e&&Math.pow(t-e.x,2)<Math.pow(e.radius+e.hoverRadius,2)},inRange:function(t,e){var n=this._view;if(n){for(var i=H.getAngleFromPoint(n,{x:t,y:e}),a=i.angle,r=i.distance,o=n.startAngle,s=n.endAngle;s<o;)s+=at;for(;a>s;)a-=at;for(;a<o;)a+=at;var l=a>=o&&a<=s,u=r>=n.innerRadius&&r<=n.outerRadius;return l&&u}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t,e=this._chart.ctx,n=this._view,i="inner"===n.borderAlign?.33:0,a={x:n.x,y:n.y,innerRadius:n.innerRadius,outerRadius:Math.max(n.outerRadius-i,0),pixelMargin:i,startAngle:n.startAngle,endAngle:n.endAngle,fullCircles:Math.floor(n.circumference/at)};if(e.save(),e.fillStyle=n.backgroundColor,e.strokeStyle=n.borderColor,a.fullCircles){for(a.endAngle=a.startAngle+at,e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),t=0;t<a.fullCircles;++t)e.fill();a.endAngle=a.startAngle+n.circumference%at}e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),e.fill(),n.borderWidth&&ot(e,n,a),e.restore()}}),lt=H.valueOrDefault,ut=W.global.defaultColor;W._set("global",{elements:{line:{tension:.4,backgroundColor:ut,borderWidth:3,borderColor:ut,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0}}});var dt=$.extend({_type:"line",draw:function(){var t,e,n,i=this,a=i._view,r=i._chart.ctx,o=a.spanGaps,s=i._children.slice(),l=W.global,u=l.elements.line,d=-1,h=i._loop;if(s.length){if(i._loop){for(t=0;t<s.length;++t)if(e=H.previousItem(s,t),!s[t]._view.skip&&e._view.skip){s=s.slice(t).concat(s.slice(0,t)),h=o;break}h&&s.push(s[0])}for(r.save(),r.lineCap=a.borderCapStyle||u.borderCapStyle,r.setLineDash&&r.setLineDash(a.borderDash||u.borderDash),r.lineDashOffset=lt(a.borderDashOffset,u.borderDashOffset),r.lineJoin=a.borderJoinStyle||u.borderJoinStyle,r.lineWidth=lt(a.borderWidth,u.borderWidth),r.strokeStyle=a.borderColor||l.defaultColor,r.beginPath(),(n=s[0]._view).skip||(r.moveTo(n.x,n.y),d=0),t=1;t<s.length;++t)n=s[t]._view,e=-1===d?H.previousItem(s,t):s[d],n.skip||(d!==t-1&&!o||-1===d?r.moveTo(n.x,n.y):H.canvas.lineTo(r,e._view,n),d=t);h&&r.closePath(),r.stroke(),r.restore()}}}),ht=H.valueOrDefault,ct=W.global.defaultColor;function ft(t){var e=this._view;return!!e&&Math.abs(t-e.x)<e.radius+e.hitRadius}W._set("global",{elements:{point:{radius:3,pointStyle:"circle",backgroundColor:ct,borderColor:ct,borderWidth:1,hitRadius:1,hoverRadius:4,hoverBorderWidth:1}}});var gt=$.extend({_type:"point",inRange:function(t,e){var n=this._view;return!!n&&Math.pow(t-n.x,2)+Math.pow(e-n.y,2)<Math.pow(n.hitRadius+n.radius,2)},inLabelRange:ft,inXRange:ft,inYRange:function(t){var e=this._view;return!!e&&Math.abs(t-e.y)<e.radius+e.hitRadius},getCenterPoint:function(){var t=this._view;return{x:t.x,y:t.y}},getArea:function(){return Math.PI*Math.pow(this._view.radius,2)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y,padding:t.radius+t.borderWidth}},draw:function(t){var e=this._view,n=this._chart.ctx,i=e.pointStyle,a=e.rotation,r=e.radius,o=e.x,s=e.y,l=W.global,u=l.defaultColor;e.skip||(void 0===t||H.canvas._isPointInArea(e,t))&&(n.strokeStyle=e.borderColor||u,n.lineWidth=ht(e.borderWidth,l.elements.point.borderWidth),n.fillStyle=e.backgroundColor||u,H.canvas.drawPoint(n,i,r,o,s,a))}}),mt=W.global.defaultColor;function pt(t){return t&&void 0!==t.width}function vt(t){var e,n,i,a,r;return pt(t)?(r=t.width/2,e=t.x-r,n=t.x+r,i=Math.min(t.y,t.base),a=Math.max(t.y,t.base)):(r=t.height/2,e=Math.min(t.x,t.base),n=Math.max(t.x,t.base),i=t.y-r,a=t.y+r),{left:e,top:i,right:n,bottom:a}}function bt(t,e,n){return t===e?n:t===n?e:t}function yt(t,e,n){var i,a,r,o,s=t.borderWidth,l=function(t){var e=t.borderSkipped,n={};return e?(t.horizontal?t.base>t.x&&(e=bt(e,"left","right")):t.base<t.y&&(e=bt(e,"bottom","top")),n[e]=!0,n):n}(t);return H.isObject(s)?(i=+s.top||0,a=+s.right||0,r=+s.bottom||0,o=+s.left||0):i=a=r=o=+s||0,{t:l.top||i<0?0:i>n?n:i,r:l.right||a<0?0:a>e?e:a,b:l.bottom||r<0?0:r>n?n:r,l:l.left||o<0?0:o>e?e:o}}function xt(t,e,n){var i=null===e,a=null===n,r=!(!t||i&&a)&&vt(t);return r&&(i||e>=r.left&&e<=r.right)&&(a||n>=r.top&&n<=r.bottom)}W._set("global",{elements:{rectangle:{backgroundColor:mt,borderColor:mt,borderSkipped:"bottom",borderWidth:0}}});var _t=$.extend({_type:"rectangle",draw:function(){var t=this._chart.ctx,e=this._view,n=function(t){var e=vt(t),n=e.right-e.left,i=e.bottom-e.top,a=yt(t,n/2,i/2);return{outer:{x:e.left,y:e.top,w:n,h:i},inner:{x:e.left+a.l,y:e.top+a.t,w:n-a.l-a.r,h:i-a.t-a.b}}}(e),i=n.outer,a=n.inner;t.fillStyle=e.backgroundColor,t.fillRect(i.x,i.y,i.w,i.h),i.w===a.w&&i.h===a.h||(t.save(),t.beginPath(),t.rect(i.x,i.y,i.w,i.h),t.clip(),t.fillStyle=e.borderColor,t.rect(a.x,a.y,a.w,a.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return xt(this._view,t,e)},inLabelRange:function(t,e){var n=this._view;return pt(n)?xt(n,t,null):xt(n,null,e)},inXRange:function(t){return xt(this._view,t,null)},inYRange:function(t){return xt(this._view,null,t)},getCenterPoint:function(){var t,e,n=this._view;return pt(n)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return pt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),wt={},kt=st,Mt=dt,St=gt,Dt=_t;wt.Arc=kt,wt.Line=Mt,wt.Point=St,wt.Rectangle=Dt;var Ct=H._deprecated,Pt=H.valueOrDefault;function Tt(t,e,n){var i,a,r=n.barThickness,o=e.stackCount,s=e.pixels[t],l=H.isNullOrUndef(r)?function(t,e){var n,i,a,r,o=t._length;for(a=1,r=e.length;a<r;++a)o=Math.min(o,Math.abs(e[a]-e[a-1]));for(a=0,r=t.getTicks().length;a<r;++a)i=t.getPixelForTick(a),o=a>0?Math.min(o,Math.abs(i-n)):o,n=i;return o}(e.scale,e.pixels):-1;return H.isNullOrUndef(r)?(i=l*n.categoryPercentage,a=n.barPercentage):(i=r*o,a=1),{chunk:i/o,ratio:a,start:s-i/2}}W._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),W._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var Ot=it.extend({dataElementType:wt.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var t,e,n=this;it.prototype.initialize.apply(n,arguments),(t=n.getMeta()).stack=n.getDataset().stack,t.bar=!0,e=n._getIndexScale().options,Ct("bar chart",e.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),Ct("bar chart",e.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),Ct("bar chart",e.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),Ct("bar chart",n._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),Ct("bar chart",e.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(t){var e,n,i=this.getMeta().data;for(this._ruler=this.getRuler(),e=0,n=i.length;e<n;++e)this.updateElement(i[e],e,t)},updateElement:function(t,e,n){var i=this,a=i.getMeta(),r=i.getDataset(),o=i._resolveDataElementOptions(t,e);t._xScale=i.getScaleForId(a.xAxisID),t._yScale=i.getScaleForId(a.yAxisID),t._datasetIndex=i.index,t._index=e,t._model={backgroundColor:o.backgroundColor,borderColor:o.borderColor,borderSkipped:o.borderSkipped,borderWidth:o.borderWidth,datasetLabel:r.label,label:i.chart.data.labels[e]},H.isArray(r.data[e])&&(t._model.borderSkipped=null),i._updateElementGeometry(t,e,n,o),t.pivot()},_updateElementGeometry:function(t,e,n,i){var a=this,r=t._model,o=a._getValueScale(),s=o.getBasePixel(),l=o.isHorizontal(),u=a._ruler||a.getRuler(),d=a.calculateBarValuePixels(a.index,e,i),h=a.calculateBarIndexPixels(a.index,e,u,i);r.horizontal=l,r.base=n?s:d.base,r.x=l?n?s:d.head:h.center,r.y=l?h.center:n?s:d.head,r.height=l?h.size:void 0,r.width=l?void 0:h.size},_getStacks:function(t){var e,n,i=this._getIndexScale(),a=i._getMatchingVisibleMetas(this._type),r=i.options.stacked,o=a.length,s=[];for(e=0;e<o&&(n=a[e],(!1===r||-1===s.indexOf(n.stack)||void 0===r&&void 0===n.stack)&&s.push(n.stack),n.index!==t);++e);return s},getStackCount:function(){return this._getStacks().length},getStackIndex:function(t,e){var n=this._getStacks(t),i=void 0!==e?n.indexOf(e):-1;return-1===i?n.length-1:i},getRuler:function(){var t,e,n=this._getIndexScale(),i=[];for(t=0,e=this.getMeta().data.length;t<e;++t)i.push(n.getPixelForValue(null,t,this.index));return{pixels:i,start:n._startPixel,end:n._endPixel,stackCount:this.getStackCount(),scale:n}},calculateBarValuePixels:function(t,e,n){var i,a,r,o,s,l,u,d=this.chart,h=this._getValueScale(),c=h.isHorizontal(),f=d.data.datasets,g=h._getMatchingVisibleMetas(this._type),m=h._parseValue(f[t].data[e]),p=n.minBarLength,v=h.options.stacked,b=this.getMeta().stack,y=void 0===m.start?0:m.max>=0&&m.min>=0?m.min:m.max,x=void 0===m.start?m.end:m.max>=0&&m.min>=0?m.max-m.min:m.min-m.max,_=g.length;if(v||void 0===v&&void 0!==b)for(i=0;i<_&&(a=g[i]).index!==t;++i)a.stack===b&&(r=void 0===(u=h._parseValue(f[a.index].data[e])).start?u.end:u.min>=0&&u.max>=0?u.max:u.min,(m.min<0&&r<0||m.max>=0&&r>0)&&(y+=r));return o=h.getPixelForValue(y),l=(s=h.getPixelForValue(y+x))-o,void 0!==p&&Math.abs(l)<p&&(l=p,s=x>=0&&!c||x<0&&c?o-p:o+p),{size:l,base:o,head:s,center:s+l/2}},calculateBarIndexPixels:function(t,e,n,i){var a="flex"===i.barThickness?function(t,e,n){var i,a=e.pixels,r=a[t],o=t>0?a[t-1]:null,s=t<a.length-1?a[t+1]:null,l=n.categoryPercentage;return null===o&&(o=r-(null===s?e.end-e.start:s-r)),null===s&&(s=r+r-o),i=r-(r-Math.min(o,s))/2*l,{chunk:Math.abs(s-o)/2*l/e.stackCount,ratio:n.barPercentage,start:i}}(e,n,i):Tt(e,n,i),r=this.getStackIndex(t,this.getMeta().stack),o=a.start+a.chunk*r+a.chunk/2,s=Math.min(Pt(i.maxBarThickness,1/0),a.chunk*a.ratio);return{base:o-s/2,head:o+s/2,center:o,size:s}},draw:function(){var t=this.chart,e=this._getValueScale(),n=this.getMeta().data,i=this.getDataset(),a=n.length,r=0;for(H.canvas.clipArea(t.ctx,t.chartArea);r<a;++r){var o=e._parseValue(i.data[r]);isNaN(o.min)||isNaN(o.max)||n[r].draw()}H.canvas.unclipArea(t.ctx)},_resolveDataElementOptions:function(){var t=this,e=H.extend({},it.prototype._resolveDataElementOptions.apply(t,arguments)),n=t._getIndexScale().options,i=t._getValueScale().options;return e.barPercentage=Pt(n.barPercentage,e.barPercentage),e.barThickness=Pt(n.barThickness,e.barThickness),e.categoryPercentage=Pt(n.categoryPercentage,e.categoryPercentage),e.maxBarThickness=Pt(n.maxBarThickness,e.maxBarThickness),e.minBarLength=Pt(i.minBarLength,e.minBarLength),e}}),At=H.valueOrDefault,Ft=H.options.resolve;W._set("bubble",{hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(){return""},label:function(t,e){var n=e.datasets[t.datasetIndex].label||"",i=e.datasets[t.datasetIndex].data[t.index];return n+": ("+t.xLabel+", "+t.yLabel+", "+i.r+")"}}}});var It=it.extend({dataElementType:wt.Point,_dataElementOptions:["backgroundColor","borderColor","borderWidth","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth","hoverRadius","hitRadius","pointStyle","rotation"],update:function(t){var e=this,n=e.getMeta().data;H.each(n,(function(n,i){e.updateElement(n,i,t)}))},updateElement:function(t,e,n){var i=this,a=i.getMeta(),r=t.custom||{},o=i.getScaleForId(a.xAxisID),s=i.getScaleForId(a.yAxisID),l=i._resolveDataElementOptions(t,e),u=i.getDataset().data[e],d=i.index,h=n?o.getPixelForDecimal(.5):o.getPixelForValue("object"==typeof u?u:NaN,e,d),c=n?s.getBasePixel():s.getPixelForValue(u,e,d);t._xScale=o,t._yScale=s,t._options=l,t._datasetIndex=d,t._index=e,t._model={backgroundColor:l.backgroundColor,borderColor:l.borderColor,borderWidth:l.borderWidth,hitRadius:l.hitRadius,pointStyle:l.pointStyle,rotation:l.rotation,radius:n?0:l.radius,skip:r.skip||isNaN(h)||isNaN(c),x:h,y:c},t.pivot()},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=At(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=At(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=At(n.hoverBorderWidth,n.borderWidth),e.radius=n.radius+n.hoverRadius},_resolveDataElementOptions:function(t,e){var n=this,i=n.chart,a=n.getDataset(),r=t.custom||{},o=a.data[e]||{},s=it.prototype._resolveDataElementOptions.apply(n,arguments),l={chart:i,dataIndex:e,dataset:a,datasetIndex:n.index};return n._cachedDataOpts===s&&(s=H.extend({},s)),s.radius=Ft([r.radius,o.r,n._config.radius,i.options.elements.point.radius],l,e),s}}),Lt=H.valueOrDefault,Rt=Math.PI,Nt=2*Rt,Wt=Rt/2;W._set("doughnut",{animation:{animateRotate:!0,animateScale:!1},hover:{mode:"single"},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data,o=r.datasets,s=r.labels;if(a.setAttribute("class",t.id+"-legend"),o.length)for(e=0,n=o[0].data.length;e<n;++e)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=o[0].backgroundColor[e],s[e]&&i.appendChild(document.createTextNode(s[e]));return a.outerHTML},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((function(n,i){var a=t.getDatasetMeta(0),r=a.controller.getStyle(i);return{text:n,fillStyle:r.backgroundColor,strokeStyle:r.borderColor,lineWidth:r.borderWidth,hidden:isNaN(e.datasets[0].data[i])||a.data[i].hidden,index:i}})):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n<i;++n)(a=o.getDatasetMeta(n)).data[r]&&(a.data[r].hidden=!a.data[r].hidden);o.update()}},cutoutPercentage:50,rotation:-Wt,circumference:Nt,tooltips:{callbacks:{title:function(){return""},label:function(t,e){var n=e.labels[t.index],i=": "+e.datasets[t.datasetIndex].data[t.index];return H.isArray(n)?(n=n.slice())[0]+=i:n+=i,n}}}});var Yt=it.extend({dataElementType:wt.Arc,linkScales:H.noop,_dataElementOptions:["backgroundColor","borderColor","borderWidth","borderAlign","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth"],getRingIndex:function(t){for(var e=0,n=0;n<t;++n)this.chart.isDatasetVisible(n)&&++e;return e},update:function(t){var e,n,i,a,r=this,o=r.chart,s=o.chartArea,l=o.options,u=1,d=1,h=0,c=0,f=r.getMeta(),g=f.data,m=l.cutoutPercentage/100||0,p=l.circumference,v=r._getRingWeight(r.index);if(p<Nt){var b=l.rotation%Nt,y=(b+=b>=Rt?-Nt:b<-Rt?Nt:0)+p,x=Math.cos(b),_=Math.sin(b),w=Math.cos(y),k=Math.sin(y),M=b<=0&&y>=0||y>=Nt,S=b<=Wt&&y>=Wt||y>=Nt+Wt,D=b<=-Wt&&y>=-Wt||y>=Rt+Wt,C=b===-Rt||y>=Rt?-1:Math.min(x,x*m,w,w*m),P=D?-1:Math.min(_,_*m,k,k*m),T=M?1:Math.max(x,x*m,w,w*m),O=S?1:Math.max(_,_*m,k,k*m);u=(T-C)/2,d=(O-P)/2,h=-(T+C)/2,c=-(O+P)/2}for(i=0,a=g.length;i<a;++i)g[i]._options=r._resolveDataElementOptions(g[i],i);for(o.borderWidth=r.getMaxBorderWidth(),e=(s.right-s.left-o.borderWidth)/u,n=(s.bottom-s.top-o.borderWidth)/d,o.outerRadius=Math.max(Math.min(e,n)/2,0),o.innerRadius=Math.max(o.outerRadius*m,0),o.radiusLength=(o.outerRadius-o.innerRadius)/(r._getVisibleDatasetWeightTotal()||1),o.offsetX=h*o.outerRadius,o.offsetY=c*o.outerRadius,f.total=r.calculateTotal(),r.outerRadius=o.outerRadius-o.radiusLength*r._getRingWeightOffset(r.index),r.innerRadius=Math.max(r.outerRadius-o.radiusLength*v,0),i=0,a=g.length;i<a;++i)r.updateElement(g[i],i,t)},updateElement:function(t,e,n){var i=this,a=i.chart,r=a.chartArea,o=a.options,s=o.animation,l=(r.left+r.right)/2,u=(r.top+r.bottom)/2,d=o.rotation,h=o.rotation,c=i.getDataset(),f=n&&s.animateRotate?0:t.hidden?0:i.calculateCircumference(c.data[e])*(o.circumference/Nt),g=n&&s.animateScale?0:i.innerRadius,m=n&&s.animateScale?0:i.outerRadius,p=t._options||{};H.extend(t,{_datasetIndex:i.index,_index:e,_model:{backgroundColor:p.backgroundColor,borderColor:p.borderColor,borderWidth:p.borderWidth,borderAlign:p.borderAlign,x:l+a.offsetX,y:u+a.offsetY,startAngle:d,endAngle:h,circumference:f,outerRadius:m,innerRadius:g,label:H.valueAtIndexOrDefault(c.label,e,a.data.labels[e])}});var v=t._model;n&&s.animateRotate||(v.startAngle=0===e?o.rotation:i.getMeta().data[e-1]._model.endAngle,v.endAngle=v.startAngle+v.circumference),t.pivot()},calculateTotal:function(){var t,e=this.getDataset(),n=this.getMeta(),i=0;return H.each(n.data,(function(n,a){t=e.data[a],isNaN(t)||n.hidden||(i+=Math.abs(t))})),i},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?Nt*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,n,i,a,r,o,s,l,u=0,d=this.chart;if(!t)for(e=0,n=d.data.datasets.length;e<n;++e)if(d.isDatasetVisible(e)){t=(i=d.getDatasetMeta(e)).data,e!==this.index&&(r=i.controller);break}if(!t)return 0;for(e=0,n=t.length;e<n;++e)a=t[e],r?(r._configure(),o=r._resolveDataElementOptions(a,e)):o=a._options,"inner"!==o.borderAlign&&(s=o.borderWidth,u=(l=o.hoverBorderWidth)>(u=s>u?s:u)?l:u);return u},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=Lt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Lt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Lt(n.hoverBorderWidth,n.borderWidth)},_getRingWeightOffset:function(t){for(var e=0,n=0;n<t;++n)this.chart.isDatasetVisible(n)&&(e+=this._getRingWeight(n));return e},_getRingWeight:function(t){return Math.max(Lt(this.chart.data.datasets[t].weight,1),0)},_getVisibleDatasetWeightTotal:function(){return this._getRingWeightOffset(this.chart.data.datasets.length)}});W._set("horizontalBar",{hover:{mode:"index",axis:"y"},scales:{xAxes:[{type:"linear",position:"bottom"}],yAxes:[{type:"category",position:"left",offset:!0,gridLines:{offsetGridLines:!0}}]},elements:{rectangle:{borderSkipped:"left"}},tooltips:{mode:"index",axis:"y"}}),W._set("global",{datasets:{horizontalBar:{categoryPercentage:.8,barPercentage:.9}}});var zt=Ot.extend({_getValueScaleId:function(){return this.getMeta().xAxisID},_getIndexScaleId:function(){return this.getMeta().yAxisID}}),Et=H.valueOrDefault,Vt=H.options.resolve,Ht=H.canvas._isPointInArea;function Bt(t,e){var n=t&&t.options.ticks||{},i=n.reverse,a=void 0===n.min?e:0,r=void 0===n.max?e:0;return{start:i?r:a,end:i?a:r}}function jt(t,e,n){var i=n/2,a=Bt(t,i),r=Bt(e,i);return{top:r.end,right:a.end,bottom:r.start,left:a.start}}function Ut(t){var e,n,i,a;return H.isObject(t)?(e=t.top,n=t.right,i=t.bottom,a=t.left):e=n=i=a=t,{top:e,right:n,bottom:i,left:a}}W._set("line",{showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}});var Gt=it.extend({datasetElementType:wt.Line,dataElementType:wt.Point,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth","cubicInterpolationMode","fill"],_dataElementOptions:{backgroundColor:"pointBackgroundColor",borderColor:"pointBorderColor",borderWidth:"pointBorderWidth",hitRadius:"pointHitRadius",hoverBackgroundColor:"pointHoverBackgroundColor",hoverBorderColor:"pointHoverBorderColor",hoverBorderWidth:"pointHoverBorderWidth",hoverRadius:"pointHoverRadius",pointStyle:"pointStyle",radius:"pointRadius",rotation:"pointRotation"},update:function(t){var e,n,i=this,a=i.getMeta(),r=a.dataset,o=a.data||[],s=i.chart.options,l=i._config,u=i._showLine=Et(l.showLine,s.showLines);for(i._xScale=i.getScaleForId(a.xAxisID),i._yScale=i.getScaleForId(a.yAxisID),u&&(void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),r._scale=i._yScale,r._datasetIndex=i.index,r._children=o,r._model=i._resolveDatasetElementOptions(r),r.pivot()),e=0,n=o.length;e<n;++e)i.updateElement(o[e],e,t);for(u&&0!==r._model.tension&&i.updateBezierControlPoints(),e=0,n=o.length;e<n;++e)o[e].pivot()},updateElement:function(t,e,n){var i,a,r=this,o=r.getMeta(),s=t.custom||{},l=r.getDataset(),u=r.index,d=l.data[e],h=r._xScale,c=r._yScale,f=o.dataset._model,g=r._resolveDataElementOptions(t,e);i=h.getPixelForValue("object"==typeof d?d:NaN,e,u),a=n?c.getBasePixel():r.calculatePointY(d,e,u),t._xScale=h,t._yScale=c,t._options=g,t._datasetIndex=u,t._index=e,t._model={x:i,y:a,skip:s.skip||isNaN(i)||isNaN(a),radius:g.radius,pointStyle:g.pointStyle,rotation:g.rotation,backgroundColor:g.backgroundColor,borderColor:g.borderColor,borderWidth:g.borderWidth,tension:Et(s.tension,f?f.tension:0),steppedLine:!!f&&f.steppedLine,hitRadius:g.hitRadius}},_resolveDatasetElementOptions:function(t){var e=this,n=e._config,i=t.custom||{},a=e.chart.options,r=a.elements.line,o=it.prototype._resolveDatasetElementOptions.apply(e,arguments);return o.spanGaps=Et(n.spanGaps,a.spanGaps),o.tension=Et(n.lineTension,r.tension),o.steppedLine=Vt([i.steppedLine,n.steppedLine,r.stepped]),o.clip=Ut(Et(n.clip,jt(e._xScale,e._yScale,o.borderWidth))),o},calculatePointY:function(t,e,n){var i,a,r,o,s,l,u,d=this.chart,h=this._yScale,c=0,f=0;if(h.options.stacked){for(s=+h.getRightValue(t),u=(l=d._getSortedVisibleDatasetMetas()).length,i=0;i<u&&(r=l[i]).index!==n;++i)a=d.data.datasets[r.index],"line"===r.type&&r.yAxisID===h.id&&((o=+h.getRightValue(a.data[e]))<0?f+=o||0:c+=o||0);return s<0?h.getPixelForValue(f+s):h.getPixelForValue(c+s)}return h.getPixelForValue(t)},updateBezierControlPoints:function(){var t,e,n,i,a=this.chart,r=this.getMeta(),o=r.dataset._model,s=a.chartArea,l=r.data||[];function u(t,e,n){return Math.max(Math.min(t,n),e)}if(o.spanGaps&&(l=l.filter((function(t){return!t._model.skip}))),"monotone"===o.cubicInterpolationMode)H.splineCurveMonotone(l);else for(t=0,e=l.length;t<e;++t)n=l[t]._model,i=H.splineCurve(H.previousItem(l,t)._model,n,H.nextItem(l,t)._model,o.tension),n.controlPointPreviousX=i.previous.x,n.controlPointPreviousY=i.previous.y,n.controlPointNextX=i.next.x,n.controlPointNextY=i.next.y;if(a.options.elements.line.capBezierPoints)for(t=0,e=l.length;t<e;++t)n=l[t]._model,Ht(n,s)&&(t>0&&Ht(l[t-1]._model,s)&&(n.controlPointPreviousX=u(n.controlPointPreviousX,s.left,s.right),n.controlPointPreviousY=u(n.controlPointPreviousY,s.top,s.bottom)),t<l.length-1&&Ht(l[t+1]._model,s)&&(n.controlPointNextX=u(n.controlPointNextX,s.left,s.right),n.controlPointNextY=u(n.controlPointNextY,s.top,s.bottom)))},draw:function(){var t,e=this.chart,n=this.getMeta(),i=n.data||[],a=e.chartArea,r=e.canvas,o=0,s=i.length;for(this._showLine&&(t=n.dataset._model.clip,H.canvas.clipArea(e.ctx,{left:!1===t.left?0:a.left-t.left,right:!1===t.right?r.width:a.right+t.right,top:!1===t.top?0:a.top-t.top,bottom:!1===t.bottom?r.height:a.bottom+t.bottom}),n.dataset.draw(),H.canvas.unclipArea(e.ctx));o<s;++o)i[o].draw(a)},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=Et(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Et(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Et(n.hoverBorderWidth,n.borderWidth),e.radius=Et(n.hoverRadius,n.radius)}}),qt=H.options.resolve;W._set("polarArea",{scale:{type:"radialLinear",angleLines:{display:!1},gridLines:{circular:!0},pointLabels:{display:!1},ticks:{beginAtZero:!0}},animation:{animateRotate:!0,animateScale:!0},startAngle:-.5*Math.PI,legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data,o=r.datasets,s=r.labels;if(a.setAttribute("class",t.id+"-legend"),o.length)for(e=0,n=o[0].data.length;e<n;++e)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=o[0].backgroundColor[e],s[e]&&i.appendChild(document.createTextNode(s[e]));return a.outerHTML},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((function(n,i){var a=t.getDatasetMeta(0),r=a.controller.getStyle(i);return{text:n,fillStyle:r.backgroundColor,strokeStyle:r.borderColor,lineWidth:r.borderWidth,hidden:isNaN(e.datasets[0].data[i])||a.data[i].hidden,index:i}})):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n<i;++n)(a=o.getDatasetMeta(n)).data[r].hidden=!a.data[r].hidden;o.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}});var Zt=it.extend({dataElementType:wt.Arc,linkScales:H.noop,_dataElementOptions:["backgroundColor","borderColor","borderWidth","borderAlign","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth"],_getIndexScaleId:function(){return this.chart.scale.id},_getValueScaleId:function(){return this.chart.scale.id},update:function(t){var e,n,i,a=this,r=a.getDataset(),o=a.getMeta(),s=a.chart.options.startAngle||0,l=a._starts=[],u=a._angles=[],d=o.data;for(a._updateRadius(),o.count=a.countVisibleElements(),e=0,n=r.data.length;e<n;e++)l[e]=s,i=a._computeAngle(e),u[e]=i,s+=i;for(e=0,n=d.length;e<n;++e)d[e]._options=a._resolveDataElementOptions(d[e],e),a.updateElement(d[e],e,t)},_updateRadius:function(){var t=this,e=t.chart,n=e.chartArea,i=e.options,a=Math.min(n.right-n.left,n.bottom-n.top);e.outerRadius=Math.max(a/2,0),e.innerRadius=Math.max(i.cutoutPercentage?e.outerRadius/100*i.cutoutPercentage:1,0),e.radiusLength=(e.outerRadius-e.innerRadius)/e.getVisibleDatasetCount(),t.outerRadius=e.outerRadius-e.radiusLength*t.index,t.innerRadius=t.outerRadius-e.radiusLength},updateElement:function(t,e,n){var i=this,a=i.chart,r=i.getDataset(),o=a.options,s=o.animation,l=a.scale,u=a.data.labels,d=l.xCenter,h=l.yCenter,c=o.startAngle,f=t.hidden?0:l.getDistanceFromCenterForValue(r.data[e]),g=i._starts[e],m=g+(t.hidden?0:i._angles[e]),p=s.animateScale?0:l.getDistanceFromCenterForValue(r.data[e]),v=t._options||{};H.extend(t,{_datasetIndex:i.index,_index:e,_scale:l,_model:{backgroundColor:v.backgroundColor,borderColor:v.borderColor,borderWidth:v.borderWidth,borderAlign:v.borderAlign,x:d,y:h,innerRadius:0,outerRadius:n?p:f,startAngle:n&&s.animateRotate?c:g,endAngle:n&&s.animateRotate?c:m,label:H.valueAtIndexOrDefault(u,e,u[e])}}),t.pivot()},countVisibleElements:function(){var t=this.getDataset(),e=this.getMeta(),n=0;return H.each(e.data,(function(e,i){isNaN(t.data[i])||e.hidden||n++})),n},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor,a=H.valueOrDefault;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=a(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=a(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=a(n.hoverBorderWidth,n.borderWidth)},_computeAngle:function(t){var e=this,n=this.getMeta().count,i=e.getDataset(),a=e.getMeta();if(isNaN(i.data[t])||a.data[t].hidden)return 0;var r={chart:e.chart,dataIndex:t,dataset:i,datasetIndex:e.index};return qt([e.chart.options.elements.arc.angle,2*Math.PI/n],r,t)}});W._set("pie",H.clone(W.doughnut)),W._set("pie",{cutoutPercentage:0});var $t=Yt,Xt=H.valueOrDefault;W._set("radar",{spanGaps:!1,scale:{type:"radialLinear"},elements:{line:{fill:"start",tension:0}}});var Kt=it.extend({datasetElementType:wt.Line,dataElementType:wt.Point,linkScales:H.noop,_datasetElementOptions:["backgroundColor","borderWidth","borderColor","borderCapStyle","borderDash","borderDashOffset","borderJoinStyle","fill"],_dataElementOptions:{backgroundColor:"pointBackgroundColor",borderColor:"pointBorderColor",borderWidth:"pointBorderWidth",hitRadius:"pointHitRadius",hoverBackgroundColor:"pointHoverBackgroundColor",hoverBorderColor:"pointHoverBorderColor",hoverBorderWidth:"pointHoverBorderWidth",hoverRadius:"pointHoverRadius",pointStyle:"pointStyle",radius:"pointRadius",rotation:"pointRotation"},_getIndexScaleId:function(){return this.chart.scale.id},_getValueScaleId:function(){return this.chart.scale.id},update:function(t){var e,n,i=this,a=i.getMeta(),r=a.dataset,o=a.data||[],s=i.chart.scale,l=i._config;for(void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),r._scale=s,r._datasetIndex=i.index,r._children=o,r._loop=!0,r._model=i._resolveDatasetElementOptions(r),r.pivot(),e=0,n=o.length;e<n;++e)i.updateElement(o[e],e,t);for(i.updateBezierControlPoints(),e=0,n=o.length;e<n;++e)o[e].pivot()},updateElement:function(t,e,n){var i=this,a=t.custom||{},r=i.getDataset(),o=i.chart.scale,s=o.getPointPositionForValue(e,r.data[e]),l=i._resolveDataElementOptions(t,e),u=i.getMeta().dataset._model,d=n?o.xCenter:s.x,h=n?o.yCenter:s.y;t._scale=o,t._options=l,t._datasetIndex=i.index,t._index=e,t._model={x:d,y:h,skip:a.skip||isNaN(d)||isNaN(h),radius:l.radius,pointStyle:l.pointStyle,rotation:l.rotation,backgroundColor:l.backgroundColor,borderColor:l.borderColor,borderWidth:l.borderWidth,tension:Xt(a.tension,u?u.tension:0),hitRadius:l.hitRadius}},_resolveDatasetElementOptions:function(){var t=this,e=t._config,n=t.chart.options,i=it.prototype._resolveDatasetElementOptions.apply(t,arguments);return i.spanGaps=Xt(e.spanGaps,n.spanGaps),i.tension=Xt(e.lineTension,n.elements.line.tension),i},updateBezierControlPoints:function(){var t,e,n,i,a=this.getMeta(),r=this.chart.chartArea,o=a.data||[];function s(t,e,n){return Math.max(Math.min(t,n),e)}for(a.dataset._model.spanGaps&&(o=o.filter((function(t){return!t._model.skip}))),t=0,e=o.length;t<e;++t)n=o[t]._model,i=H.splineCurve(H.previousItem(o,t,!0)._model,n,H.nextItem(o,t,!0)._model,n.tension),n.controlPointPreviousX=s(i.previous.x,r.left,r.right),n.controlPointPreviousY=s(i.previous.y,r.top,r.bottom),n.controlPointNextX=s(i.next.x,r.left,r.right),n.controlPointNextY=s(i.next.y,r.top,r.bottom)},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=Xt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Xt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Xt(n.hoverBorderWidth,n.borderWidth),e.radius=Xt(n.hoverRadius,n.radius)}});W._set("scatter",{hover:{mode:"single"},scales:{xAxes:[{id:"x-axis-1",type:"linear",position:"bottom"}],yAxes:[{id:"y-axis-1",type:"linear",position:"left"}]},tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}}),W._set("global",{datasets:{scatter:{showLine:!1}}});var Jt={bar:Ot,bubble:It,doughnut:Yt,horizontalBar:zt,line:Gt,polarArea:Zt,pie:$t,radar:Kt,scatter:Gt};function Qt(t,e){return t.native?{x:t.x,y:t.y}:H.getRelativePosition(t,e)}function te(t,e){var n,i,a,r,o,s,l=t._getSortedVisibleDatasetMetas();for(i=0,r=l.length;i<r;++i)for(a=0,o=(n=l[i].data).length;a<o;++a)(s=n[a])._view.skip||e(s)}function ee(t,e){var n=[];return te(t,(function(t){t.inRange(e.x,e.y)&&n.push(t)})),n}function ne(t,e,n,i){var a=Number.POSITIVE_INFINITY,r=[];return te(t,(function(t){if(!n||t.inRange(e.x,e.y)){var o=t.getCenterPoint(),s=i(e,o);s<a?(r=[t],a=s):s===a&&r.push(t)}})),r}function ie(t){var e=-1!==t.indexOf("x"),n=-1!==t.indexOf("y");return function(t,i){var a=e?Math.abs(t.x-i.x):0,r=n?Math.abs(t.y-i.y):0;return Math.sqrt(Math.pow(a,2)+Math.pow(r,2))}}function ae(t,e,n){var i=Qt(e,t);n.axis=n.axis||"x";var a=ie(n.axis),r=n.intersect?ee(t,i):ne(t,i,!1,a),o=[];return r.length?(t._getSortedVisibleDatasetMetas().forEach((function(t){var e=t.data[r[0]._index];e&&!e._view.skip&&o.push(e)})),o):[]}var re={modes:{single:function(t,e){var n=Qt(e,t),i=[];return te(t,(function(t){if(t.inRange(n.x,n.y))return i.push(t),i})),i.slice(0,1)},label:ae,index:ae,dataset:function(t,e,n){var i=Qt(e,t);n.axis=n.axis||"xy";var a=ie(n.axis),r=n.intersect?ee(t,i):ne(t,i,!1,a);return r.length>0&&(r=t.getDatasetMeta(r[0]._datasetIndex).data),r},"x-axis":function(t,e){return ae(t,e,{intersect:!1})},point:function(t,e){return ee(t,Qt(e,t))},nearest:function(t,e,n){var i=Qt(e,t);n.axis=n.axis||"xy";var a=ie(n.axis);return ne(t,i,n.intersect,a)},x:function(t,e,n){var i=Qt(e,t),a=[],r=!1;return te(t,(function(t){t.inXRange(i.x)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a},y:function(t,e,n){var i=Qt(e,t),a=[],r=!1;return te(t,(function(t){t.inYRange(i.y)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a}}},oe=H.extend;function se(t,e){return H.where(t,(function(t){return t.pos===e}))}function le(t,e){return t.sort((function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i.index-a.index:i.weight-a.weight}))}function ue(t,e,n,i){return Math.max(t[n],e[n])+Math.max(t[i],e[i])}function de(t,e,n){var i,a,r=n.box,o=t.maxPadding;if(n.size&&(t[n.pos]-=n.size),n.size=n.horizontal?r.height:r.width,t[n.pos]+=n.size,r.getPadding){var s=r.getPadding();o.top=Math.max(o.top,s.top),o.left=Math.max(o.left,s.left),o.bottom=Math.max(o.bottom,s.bottom),o.right=Math.max(o.right,s.right)}if(i=e.outerWidth-ue(o,t,"left","right"),a=e.outerHeight-ue(o,t,"top","bottom"),i!==t.w||a!==t.h)return t.w=i,t.h=a,n.horizontal?i!==t.w:a!==t.h}function he(t,e){var n=e.maxPadding;function i(t){var i={left:0,top:0,right:0,bottom:0};return t.forEach((function(t){i[t]=Math.max(e[t],n[t])})),i}return i(t?["left","right"]:["top","bottom"])}function ce(t,e,n){var i,a,r,o,s,l,u=[];for(i=0,a=t.length;i<a;++i)(o=(r=t[i]).box).update(r.width||e.w,r.height||e.h,he(r.horizontal,e)),de(e,n,r)&&(l=!0,u.length&&(s=!0)),o.fullWidth||u.push(r);return s&&ce(u,e,n)||l}function fe(t,e,n){var i,a,r,o,s=n.padding,l=e.x,u=e.y;for(i=0,a=t.length;i<a;++i)o=(r=t[i]).box,r.horizontal?(o.left=o.fullWidth?s.left:e.left,o.right=o.fullWidth?n.outerWidth-s.right:e.left+e.w,o.top=u,o.bottom=u+o.height,o.width=o.right-o.left,u=o.bottom):(o.left=l,o.right=l+o.width,o.top=e.top,o.bottom=e.top+e.h,o.height=o.bottom-o.top,l=o.right);e.x=l,e.y=u}W._set("global",{layout:{padding:{top:0,right:0,bottom:0,left:0}}});var ge,me={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),e.fullWidth=e.fullWidth||!1,e.position=e.position||"top",e.weight=e.weight||0,e._layers=e._layers||function(){return[{z:0,draw:function(){e.draw.apply(e,arguments)}}]},t.boxes.push(e)},removeBox:function(t,e){var n=t.boxes?t.boxes.indexOf(e):-1;-1!==n&&t.boxes.splice(n,1)},configure:function(t,e,n){for(var i,a=["fullWidth","position","weight"],r=a.length,o=0;o<r;++o)i=a[o],n.hasOwnProperty(i)&&(e[i]=n[i])},update:function(t,e,n){if(t){var i=t.options.layout||{},a=H.options.toPadding(i.padding),r=e-a.width,o=n-a.height,s=function(t){var e=function(t){var e,n,i,a=[];for(e=0,n=(t||[]).length;e<n;++e)i=t[e],a.push({index:e,box:i,pos:i.position,horizontal:i.isHorizontal(),weight:i.weight});return a}(t),n=le(se(e,"left"),!0),i=le(se(e,"right")),a=le(se(e,"top"),!0),r=le(se(e,"bottom"));return{leftAndTop:n.concat(a),rightAndBottom:i.concat(r),chartArea:se(e,"chartArea"),vertical:n.concat(i),horizontal:a.concat(r)}}(t.boxes),l=s.vertical,u=s.horizontal,d=Object.freeze({outerWidth:e,outerHeight:n,padding:a,availableWidth:r,vBoxMaxWidth:r/2/l.length,hBoxMaxHeight:o/2}),h=oe({maxPadding:oe({},a),w:r,h:o,x:a.left,y:a.top},a);!function(t,e){var n,i,a;for(n=0,i=t.length;n<i;++n)(a=t[n]).width=a.horizontal?a.box.fullWidth&&e.availableWidth:e.vBoxMaxWidth,a.height=a.horizontal&&e.hBoxMaxHeight}(l.concat(u),d),ce(l,h,d),ce(u,h,d)&&ce(l,h,d),function(t){var e=t.maxPadding;function n(n){var i=Math.max(e[n]-t[n],0);return t[n]+=i,i}t.y+=n("top"),t.x+=n("left"),n("right"),n("bottom")}(h),fe(s.leftAndTop,h,d),h.x+=h.w,h.y+=h.h,fe(s.rightAndBottom,h,d),t.chartArea={left:h.left,top:h.top,right:h.left+h.w,bottom:h.top+h.h},H.each(s.chartArea,(function(e){var n=e.box;oe(n,t.chartArea),n.update(h.w,h.h)}))}}},pe=(ge=Object.freeze({__proto__:null,default:"@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}"}))&&ge.default||ge,ve="$chartjs",be="chartjs-size-monitor",ye="chartjs-render-monitor",xe="chartjs-render-animation",_e=["animationstart","webkitAnimationStart"],we={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function ke(t,e){var n=H.getStyle(t,e),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?Number(i[1]):void 0}var Me=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};function Se(t,e,n){t.addEventListener(e,n,Me)}function De(t,e,n){t.removeEventListener(e,n,Me)}function Ce(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function Pe(t){var e=document.createElement("div");return e.className=t||"",e}function Te(t,e,n){var i,a,r,o,s=t[ve]||(t[ve]={}),l=s.resizer=function(t){var e=Pe(be),n=Pe(be+"-expand"),i=Pe(be+"-shrink");n.appendChild(Pe()),i.appendChild(Pe()),e.appendChild(n),e.appendChild(i),e._reset=function(){n.scrollLeft=1e6,n.scrollTop=1e6,i.scrollLeft=1e6,i.scrollTop=1e6};var a=function(){e._reset(),t()};return Se(n,"scroll",a.bind(n,"expand")),Se(i,"scroll",a.bind(i,"shrink")),e}((i=function(){if(s.resizer){var i=n.options.maintainAspectRatio&&t.parentNode,a=i?i.clientWidth:0;e(Ce("resize",n)),i&&i.clientWidth<a&&n.canvas&&e(Ce("resize",n))}},r=!1,o=[],function(){o=Array.prototype.slice.call(arguments),a=a||this,r||(r=!0,H.requestAnimFrame.call(window,(function(){r=!1,i.apply(a,o)})))}));!function(t,e){var n=t[ve]||(t[ve]={}),i=n.renderProxy=function(t){t.animationName===xe&&e()};H.each(_e,(function(e){Se(t,e,i)})),n.reflow=!!t.offsetParent,t.classList.add(ye)}(t,(function(){if(s.resizer){var e=t.parentNode;e&&e!==l.parentNode&&e.insertBefore(l,e.firstChild),l._reset()}}))}function Oe(t){var e=t[ve]||{},n=e.resizer;delete e.resizer,function(t){var e=t[ve]||{},n=e.renderProxy;n&&(H.each(_e,(function(e){De(t,e,n)})),delete e.renderProxy),t.classList.remove(ye)}(t),n&&n.parentNode&&n.parentNode.removeChild(n)}var Ae={disableCSSInjection:!1,_enabled:"undefined"!=typeof window&&"undefined"!=typeof document,_ensureLoaded:function(t){if(!this.disableCSSInjection){var e=t.getRootNode?t.getRootNode():document;!function(t,e){var n=t[ve]||(t[ve]={});if(!n.containsStyles){n.containsStyles=!0,e="/* Chart.js */\n"+e;var i=document.createElement("style");i.setAttribute("type","text/css"),i.appendChild(document.createTextNode(e)),t.appendChild(i)}}(e.host?e:document.head,pe)}},acquireContext:function(t,e){"string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas);var n=t&&t.getContext&&t.getContext("2d");return n&&n.canvas===t?(this._ensureLoaded(t),function(t,e){var n=t.style,i=t.getAttribute("height"),a=t.getAttribute("width");if(t[ve]={initial:{height:i,width:a,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",null===a||""===a){var r=ke(t,"width");void 0!==r&&(t.width=r)}if(null===i||""===i)if(""===t.style.height)t.height=t.width/(e.options.aspectRatio||2);else{var o=ke(t,"height");void 0!==r&&(t.height=o)}}(t,e),n):null},releaseContext:function(t){var e=t.canvas;if(e[ve]){var n=e[ve].initial;["height","width"].forEach((function(t){var i=n[t];H.isNullOrUndef(i)?e.removeAttribute(t):e.setAttribute(t,i)})),H.each(n.style||{},(function(t,n){e.style[n]=t})),e.width=e.width,delete e[ve]}},addEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=n[ve]||(n[ve]={});Se(i,e,(a.proxies||(a.proxies={}))[t.id+"_"+e]=function(e){n(function(t,e){var n=we[t.type]||t.type,i=H.getRelativePosition(t,e);return Ce(n,e,i.x,i.y,t)}(e,t))})}else Te(i,n,t)},removeEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=((n[ve]||{}).proxies||{})[t.id+"_"+e];a&&De(i,e,a)}else Oe(i)}};H.addEvent=Se,H.removeEvent=De;var Fe=Ae._enabled?Ae:{acquireContext:function(t){return t&&t.canvas&&(t=t.canvas),t&&t.getContext("2d")||null}},Ie=H.extend({initialize:function(){},acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},Fe);W._set("global",{plugins:{}});var Le={_plugins:[],_cacheId:0,register:function(t){var e=this._plugins;[].concat(t).forEach((function(t){-1===e.indexOf(t)&&e.push(t)})),this._cacheId++},unregister:function(t){var e=this._plugins;[].concat(t).forEach((function(t){var n=e.indexOf(t);-1!==n&&e.splice(n,1)})),this._cacheId++},clear:function(){this._plugins=[],this._cacheId++},count:function(){return this._plugins.length},getAll:function(){return this._plugins},notify:function(t,e,n){var i,a,r,o,s,l=this.descriptors(t),u=l.length;for(i=0;i<u;++i)if("function"==typeof(s=(r=(a=l[i]).plugin)[e])&&((o=[t].concat(n||[])).push(a.options),!1===s.apply(r,o)))return!1;return!0},descriptors:function(t){var e=t.$plugins||(t.$plugins={});if(e.id===this._cacheId)return e.descriptors;var n=[],i=[],a=t&&t.config||{},r=a.options&&a.options.plugins||{};return this._plugins.concat(a.plugins||[]).forEach((function(t){if(-1===n.indexOf(t)){var e=t.id,a=r[e];!1!==a&&(!0===a&&(a=H.clone(W.global.plugins[e])),n.push(t),i.push({plugin:t,options:a||{}}))}})),e.descriptors=i,e.id=this._cacheId,i},_invalidate:function(t){delete t.$plugins}},Re={constructors:{},defaults:{},registerScaleType:function(t,e,n){this.constructors[t]=e,this.defaults[t]=H.clone(n)},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0},getScaleDefaults:function(t){return this.defaults.hasOwnProperty(t)?H.merge({},[W.scale,this.defaults[t]]):{}},updateScaleDefaults:function(t,e){this.defaults.hasOwnProperty(t)&&(this.defaults[t]=H.extend(this.defaults[t],e))},addScalesToLayout:function(t){H.each(t.scales,(function(e){e.fullWidth=e.options.fullWidth,e.position=e.options.position,e.weight=e.options.weight,me.addBox(t,e)}))}},Ne=H.valueOrDefault,We=H.rtl.getRtlAdapter;W._set("global",{tooltips:{enabled:!0,custom:null,mode:"nearest",position:"average",intersect:!0,backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,caretPadding:2,caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,callbacks:{beforeTitle:H.noop,title:function(t,e){var n="",i=e.labels,a=i?i.length:0;if(t.length>0){var r=t[0];r.label?n=r.label:r.xLabel?n=r.xLabel:a>0&&r.index<a&&(n=i[r.index])}return n},afterTitle:H.noop,beforeBody:H.noop,beforeLabel:H.noop,label:function(t,e){var n=e.datasets[t.datasetIndex].label||"";return n&&(n+=": "),H.isNullOrUndef(t.value)?n+=t.yLabel:n+=t.value,n},labelColor:function(t,e){var n=e.getDatasetMeta(t.datasetIndex).data[t.index]._view;return{borderColor:n.borderColor,backgroundColor:n.backgroundColor}},labelTextColor:function(){return this._options.bodyFontColor},afterLabel:H.noop,afterBody:H.noop,beforeFooter:H.noop,footer:H.noop,afterFooter:H.noop}}});var Ye={average:function(t){if(!t.length)return!1;var e,n,i=0,a=0,r=0;for(e=0,n=t.length;e<n;++e){var o=t[e];if(o&&o.hasValue()){var s=o.tooltipPosition();i+=s.x,a+=s.y,++r}}return{x:i/r,y:a/r}},nearest:function(t,e){var n,i,a,r=e.x,o=e.y,s=Number.POSITIVE_INFINITY;for(n=0,i=t.length;n<i;++n){var l=t[n];if(l&&l.hasValue()){var u=l.getCenterPoint(),d=H.distanceBetweenPoints(e,u);d<s&&(s=d,a=l)}}if(a){var h=a.tooltipPosition();r=h.x,o=h.y}return{x:r,y:o}}};function ze(t,e){return e&&(H.isArray(e)?Array.prototype.push.apply(t,e):t.push(e)),t}function Ee(t){return("string"==typeof t||t instanceof String)&&t.indexOf("\n")>-1?t.split("\n"):t}function Ve(t){var e=W.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,rtl:t.rtl,textDirection:t.textDirection,bodyFontColor:t.bodyFontColor,_bodyFontFamily:Ne(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:Ne(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:Ne(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:Ne(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:Ne(t.titleFontStyle,e.defaultFontStyle),titleFontSize:Ne(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:Ne(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:Ne(t.footerFontStyle,e.defaultFontStyle),footerFontSize:Ne(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function He(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function Be(t){return ze([],Ee(t))}var je=$.extend({initialize:function(){this._model=Ve(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options,n=e.callbacks,i=n.beforeTitle.apply(t,arguments),a=n.title.apply(t,arguments),r=n.afterTitle.apply(t,arguments),o=[];return o=ze(o,Ee(i)),o=ze(o,Ee(a)),o=ze(o,Ee(r))},getBeforeBody:function(){return Be(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var n=this,i=n._options.callbacks,a=[];return H.each(t,(function(t){var r={before:[],lines:[],after:[]};ze(r.before,Ee(i.beforeLabel.call(n,t,e))),ze(r.lines,i.label.call(n,t,e)),ze(r.after,Ee(i.afterLabel.call(n,t,e))),a.push(r)})),a},getAfterBody:function(){return Be(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this,e=t._options.callbacks,n=e.beforeFooter.apply(t,arguments),i=e.footer.apply(t,arguments),a=e.afterFooter.apply(t,arguments),r=[];return r=ze(r,Ee(n)),r=ze(r,Ee(i)),r=ze(r,Ee(a))},update:function(t){var e,n,i,a,r,o,s,l,u,d,h=this,c=h._options,f=h._model,g=h._model=Ve(c),m=h._active,p=h._data,v={xAlign:f.xAlign,yAlign:f.yAlign},b={x:f.x,y:f.y},y={width:f.width,height:f.height},x={x:f.caretX,y:f.caretY};if(m.length){g.opacity=1;var _=[],w=[];x=Ye[c.position].call(h,m,h._eventPosition);var k=[];for(e=0,n=m.length;e<n;++e)k.push((i=m[e],a=void 0,r=void 0,o=void 0,s=void 0,l=void 0,u=void 0,d=void 0,a=i._xScale,r=i._yScale||i._scale,o=i._index,s=i._datasetIndex,l=i._chart.getDatasetMeta(s).controller,u=l._getIndexScale(),d=l._getValueScale(),{xLabel:a?a.getLabelForIndex(o,s):"",yLabel:r?r.getLabelForIndex(o,s):"",label:u?""+u.getLabelForIndex(o,s):"",value:d?""+d.getLabelForIndex(o,s):"",index:o,datasetIndex:s,x:i._model.x,y:i._model.y}));c.filter&&(k=k.filter((function(t){return c.filter(t,p)}))),c.itemSort&&(k=k.sort((function(t,e){return c.itemSort(t,e,p)}))),H.each(k,(function(t){_.push(c.callbacks.labelColor.call(h,t,h._chart)),w.push(c.callbacks.labelTextColor.call(h,t,h._chart))})),g.title=h.getTitle(k,p),g.beforeBody=h.getBeforeBody(k,p),g.body=h.getBody(k,p),g.afterBody=h.getAfterBody(k,p),g.footer=h.getFooter(k,p),g.x=x.x,g.y=x.y,g.caretPadding=c.caretPadding,g.labelColors=_,g.labelTextColors=w,g.dataPoints=k,y=function(t,e){var n=t._chart.ctx,i=2*e.yPadding,a=0,r=e.body,o=r.reduce((function(t,e){return t+e.before.length+e.lines.length+e.after.length}),0);o+=e.beforeBody.length+e.afterBody.length;var s=e.title.length,l=e.footer.length,u=e.titleFontSize,d=e.bodyFontSize,h=e.footerFontSize;i+=s*u,i+=s?(s-1)*e.titleSpacing:0,i+=s?e.titleMarginBottom:0,i+=o*d,i+=o?(o-1)*e.bodySpacing:0,i+=l?e.footerMarginTop:0,i+=l*h,i+=l?(l-1)*e.footerSpacing:0;var c=0,f=function(t){a=Math.max(a,n.measureText(t).width+c)};return n.font=H.fontString(u,e._titleFontStyle,e._titleFontFamily),H.each(e.title,f),n.font=H.fontString(d,e._bodyFontStyle,e._bodyFontFamily),H.each(e.beforeBody.concat(e.afterBody),f),c=e.displayColors?d+2:0,H.each(r,(function(t){H.each(t.before,f),H.each(t.lines,f),H.each(t.after,f)})),c=0,n.font=H.fontString(h,e._footerFontStyle,e._footerFontFamily),H.each(e.footer,f),{width:a+=2*e.xPadding,height:i}}(this,g),b=function(t,e,n,i){var a=t.x,r=t.y,o=t.caretSize,s=t.caretPadding,l=t.cornerRadius,u=n.xAlign,d=n.yAlign,h=o+s,c=l+s;return"right"===u?a-=e.width:"center"===u&&((a-=e.width/2)+e.width>i.width&&(a=i.width-e.width),a<0&&(a=0)),"top"===d?r+=h:r-="bottom"===d?e.height+h:e.height/2,"center"===d?"left"===u?a+=h:"right"===u&&(a-=h):"left"===u?a-=c:"right"===u&&(a+=c),{x:a,y:r}}(g,y,v=function(t,e){var n,i,a,r,o,s=t._model,l=t._chart,u=t._chart.chartArea,d="center",h="center";s.y<e.height?h="top":s.y>l.height-e.height&&(h="bottom");var c=(u.left+u.right)/2,f=(u.top+u.bottom)/2;"center"===h?(n=function(t){return t<=c},i=function(t){return t>c}):(n=function(t){return t<=e.width/2},i=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},r=function(t){return t-e.width-s.caretSize-s.caretPadding<0},o=function(t){return t<=f?"top":"bottom"},n(s.x)?(d="left",a(s.x)&&(d="center",h=o(s.y))):i(s.x)&&(d="right",r(s.x)&&(d="center",h=o(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:h}}(this,y),h._chart)}else g.opacity=0;return g.xAlign=v.xAlign,g.yAlign=v.yAlign,g.x=b.x,g.y=b.y,g.width=y.width,g.height=y.height,g.caretX=x.x,g.caretY=x.y,h._model=g,t&&c.custom&&c.custom.call(h,g),h},drawCaret:function(t,e){var n=this._chart.ctx,i=this._view,a=this.getCaretPosition(t,e,i);n.lineTo(a.x1,a.y1),n.lineTo(a.x2,a.y2),n.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,n){var i,a,r,o,s,l,u=n.caretSize,d=n.cornerRadius,h=n.xAlign,c=n.yAlign,f=t.x,g=t.y,m=e.width,p=e.height;if("center"===c)s=g+p/2,"left"===h?(a=(i=f)-u,r=i,o=s+u,l=s-u):(a=(i=f+m)+u,r=i,o=s-u,l=s+u);else if("left"===h?(i=(a=f+d+u)-u,r=a+u):"right"===h?(i=(a=f+m-d-u)-u,r=a+u):(i=(a=n.caretX)-u,r=a+u),"top"===c)s=(o=g)-u,l=o;else{s=(o=g+p)+u,l=o;var v=r;r=i,i=v}return{x1:i,x2:a,x3:r,y1:o,y2:s,y3:l}},drawTitle:function(t,e,n){var i,a,r,o=e.title,s=o.length;if(s){var l=We(e.rtl,e.x,e.width);for(t.x=He(e,e._titleAlign),n.textAlign=l.textAlign(e._titleAlign),n.textBaseline="middle",i=e.titleFontSize,a=e.titleSpacing,n.fillStyle=e.titleFontColor,n.font=H.fontString(i,e._titleFontStyle,e._titleFontFamily),r=0;r<s;++r)n.fillText(o[r],l.x(t.x),t.y+i/2),t.y+=i+a,r+1===s&&(t.y+=e.titleMarginBottom-a)}},drawBody:function(t,e,n){var i,a,r,o,s,l,u,d,h=e.bodyFontSize,c=e.bodySpacing,f=e._bodyAlign,g=e.body,m=e.displayColors,p=0,v=m?He(e,"left"):0,b=We(e.rtl,e.x,e.width),y=function(e){n.fillText(e,b.x(t.x+p),t.y+h/2),t.y+=h+c},x=b.textAlign(f);for(n.textAlign=f,n.textBaseline="middle",n.font=H.fontString(h,e._bodyFontStyle,e._bodyFontFamily),t.x=He(e,x),n.fillStyle=e.bodyFontColor,H.each(e.beforeBody,y),p=m&&"right"!==x?"center"===f?h/2+1:h+2:0,s=0,u=g.length;s<u;++s){for(i=g[s],a=e.labelTextColors[s],r=e.labelColors[s],n.fillStyle=a,H.each(i.before,y),l=0,d=(o=i.lines).length;l<d;++l){if(m){var _=b.x(v);n.fillStyle=e.legendColorBackground,n.fillRect(b.leftForLtr(_,h),t.y,h,h),n.lineWidth=1,n.strokeStyle=r.borderColor,n.strokeRect(b.leftForLtr(_,h),t.y,h,h),n.fillStyle=r.backgroundColor,n.fillRect(b.leftForLtr(b.xPlus(_,1),h-2),t.y+1,h-2,h-2),n.fillStyle=a}y(o[l])}H.each(i.after,y)}p=0,H.each(e.afterBody,y),t.y-=c},drawFooter:function(t,e,n){var i,a,r=e.footer,o=r.length;if(o){var s=We(e.rtl,e.x,e.width);for(t.x=He(e,e._footerAlign),t.y+=e.footerMarginTop,n.textAlign=s.textAlign(e._footerAlign),n.textBaseline="middle",i=e.footerFontSize,n.fillStyle=e.footerFontColor,n.font=H.fontString(i,e._footerFontStyle,e._footerFontFamily),a=0;a<o;++a)n.fillText(r[a],s.x(t.x),t.y+i/2),t.y+=i+e.footerSpacing}},drawBackground:function(t,e,n,i){n.fillStyle=e.backgroundColor,n.strokeStyle=e.borderColor,n.lineWidth=e.borderWidth;var a=e.xAlign,r=e.yAlign,o=t.x,s=t.y,l=i.width,u=i.height,d=e.cornerRadius;n.beginPath(),n.moveTo(o+d,s),"top"===r&&this.drawCaret(t,i),n.lineTo(o+l-d,s),n.quadraticCurveTo(o+l,s,o+l,s+d),"center"===r&&"right"===a&&this.drawCaret(t,i),n.lineTo(o+l,s+u-d),n.quadraticCurveTo(o+l,s+u,o+l-d,s+u),"bottom"===r&&this.drawCaret(t,i),n.lineTo(o+d,s+u),n.quadraticCurveTo(o,s+u,o,s+u-d),"center"===r&&"left"===a&&this.drawCaret(t,i),n.lineTo(o,s+d),n.quadraticCurveTo(o,s,o+d,s),n.closePath(),n.fill(),e.borderWidth>0&&n.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&r&&(t.save(),t.globalAlpha=a,this.drawBackground(i,e,t,n),i.y+=e.yPadding,H.rtl.overrideTextDirection(t,e.textDirection),this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),H.rtl.restoreTextDirection(t,e.textDirection),t.restore())}},handleEvent:function(t){var e,n=this,i=n._options;return n._lastActive=n._lastActive||[],"mouseout"===t.type?n._active=[]:(n._active=n._chart.getElementsAtEventForMode(t,i.mode,i),i.reverse&&n._active.reverse()),(e=!H.arrayEquals(n._active,n._lastActive))&&(n._lastActive=n._active,(i.enabled||i.custom)&&(n._eventPosition={x:t.x,y:t.y},n.update(!0),n.pivot())),e}}),Ue=Ye,Ge=je;Ge.positioners=Ue;var qe=H.valueOrDefault;function Ze(){return H.merge({},[].slice.call(arguments),{merger:function(t,e,n,i){if("xAxes"===t||"yAxes"===t){var a,r,o,s=n[t].length;for(e[t]||(e[t]=[]),a=0;a<s;++a)o=n[t][a],r=qe(o.type,"xAxes"===t?"category":"linear"),a>=e[t].length&&e[t].push({}),!e[t][a].type||o.type&&o.type!==e[t][a].type?H.merge(e[t][a],[Re.getScaleDefaults(r),o]):H.merge(e[t][a],o)}else H._merger(t,e,n,i)}})}function $e(){return H.merge({},[].slice.call(arguments),{merger:function(t,e,n,i){var a=e[t]||{},r=n[t];"scales"===t?e[t]=Ze(a,r):"scale"===t?e[t]=H.merge(a,[Re.getScaleDefaults(r.type),r]):H._merger(t,e,n,i)}})}function Xe(t){var e=t.options;H.each(t.scales,(function(e){me.removeBox(t,e)})),e=$e(W.global,W[t.config.type],e),t.options=t.config.options=e,t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.tooltip._options=e.tooltips,t.tooltip.initialize()}function Ke(t,e,n){var i,a=function(t){return t.id===i};do{i=e+n++}while(H.findIndex(t,a)>=0);return i}function Je(t){return"top"===t||"bottom"===t}function Qe(t,e){return function(n,i){return n[t]===i[t]?n[e]-i[e]:n[t]-i[t]}}W._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var tn=function(t,e){return this.construct(t,e),this};H.extend(tn.prototype,{construct:function(t,e){var n=this;e=function(t){var e=(t=t||{}).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=$e(W.global,W[t.type],t.options||{}),t}(e);var i=Ie.acquireContext(t,e),a=i&&i.canvas,r=a&&a.height,o=a&&a.width;n.id=H.uid(),n.ctx=i,n.canvas=a,n.config=e,n.width=o,n.height=r,n.aspectRatio=r?o/r:null,n.options=e.options,n._bufferedRender=!1,n._layers=[],n.chart=n,n.controller=n,tn.instances[n.id]=n,Object.defineProperty(n,"data",{get:function(){return n.config.data},set:function(t){n.config.data=t}}),i&&a?(n.initialize(),n.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return Le.notify(t,"beforeInit"),H.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.initToolTip(),Le.notify(t,"afterInit"),t},clear:function(){return H.canvas.clear(this),this},stop:function(){return J.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,a=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(H.getMaximumWidth(i))),o=Math.max(0,Math.floor(a?r/a:H.getMaximumHeight(i)));if((e.width!==r||e.height!==o)&&(i.width=e.width=r,i.height=e.height=o,i.style.width=r+"px",i.style.height=o+"px",H.retinaScale(e,n.devicePixelRatio),!t)){var s={width:r,height:o};Le.notify(e,"resize",[s]),n.onResize&&n.onResize(e,s),e.stop(),e.update({duration:n.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;H.each(e.xAxes,(function(t,n){t.id||(t.id=Ke(e.xAxes,"x-axis-",n))})),H.each(e.yAxes,(function(t,n){t.id||(t.id=Ke(e.yAxes,"y-axis-",n))})),n&&(n.id=n.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,n=t.scales||{},i=[],a=Object.keys(n).reduce((function(t,e){return t[e]=!1,t}),{});e.scales&&(i=i.concat((e.scales.xAxes||[]).map((function(t){return{options:t,dtype:"category",dposition:"bottom"}})),(e.scales.yAxes||[]).map((function(t){return{options:t,dtype:"linear",dposition:"left"}})))),e.scale&&i.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),H.each(i,(function(e){var i=e.options,r=i.id,o=qe(i.type,e.dtype);Je(i.position)!==Je(e.dposition)&&(i.position=e.dposition),a[r]=!0;var s=null;if(r in n&&n[r].type===o)(s=n[r]).options=i,s.ctx=t.ctx,s.chart=t;else{var l=Re.getScaleConstructor(o);if(!l)return;s=new l({id:r,type:o,options:i,ctx:t.ctx,chart:t}),n[s.id]=s}s.mergeTicksOptions(),e.isDefault&&(t.scale=s)})),H.each(a,(function(t,e){t||delete n[e]})),t.scales=n,Re.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t,e,n=this,i=[],a=n.data.datasets;for(t=0,e=a.length;t<e;t++){var r=a[t],o=n.getDatasetMeta(t),s=r.type||n.config.type;if(o.type&&o.type!==s&&(n.destroyDatasetMeta(t),o=n.getDatasetMeta(t)),o.type=s,o.order=r.order||0,o.index=t,o.controller)o.controller.updateIndex(t),o.controller.linkScales();else{var l=Jt[o.type];if(void 0===l)throw new Error('"'+o.type+'" is not a chart type.');o.controller=new l(n,t),i.push(o.controller)}}return i},resetElements:function(){var t=this;H.each(t.data.datasets,(function(e,n){t.getDatasetMeta(n).controller.reset()}),t)},reset:function(){this.resetElements(),this.tooltip.initialize()},update:function(t){var e,n,i=this;if(t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]}),Xe(i),Le._invalidate(i),!1!==Le.notify(i,"beforeUpdate")){i.tooltip._data=i.data;var a=i.buildOrUpdateControllers();for(e=0,n=i.data.datasets.length;e<n;e++)i.getDatasetMeta(e).controller.buildOrUpdateElements();i.updateLayout(),i.options.animation&&i.options.animation.duration&&H.each(a,(function(t){t.reset()})),i.updateDatasets(),i.tooltip.initialize(),i.lastActive=[],Le.notify(i,"afterUpdate"),i._layers.sort(Qe("z","_idx")),i._bufferedRender?i._bufferedRequest={duration:t.duration,easing:t.easing,lazy:t.lazy}:i.render(t)}},updateLayout:function(){var t=this;!1!==Le.notify(t,"beforeLayout")&&(me.update(this,this.width,this.height),t._layers=[],H.each(t.boxes,(function(e){e._configure&&e._configure(),t._layers.push.apply(t._layers,e._layers())}),t),t._layers.forEach((function(t,e){t._idx=e})),Le.notify(t,"afterScaleUpdate"),Le.notify(t,"afterLayout"))},updateDatasets:function(){if(!1!==Le.notify(this,"beforeDatasetsUpdate")){for(var t=0,e=this.data.datasets.length;t<e;++t)this.updateDataset(t);Le.notify(this,"afterDatasetsUpdate")}},updateDataset:function(t){var e=this.getDatasetMeta(t),n={meta:e,index:t};!1!==Le.notify(this,"beforeDatasetUpdate",[n])&&(e.controller._update(),Le.notify(this,"afterDatasetUpdate",[n]))},render:function(t){var e=this;t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]});var n=e.options.animation,i=qe(t.duration,n&&n.duration),a=t.lazy;if(!1!==Le.notify(e,"beforeRender")){var r=function(t){Le.notify(e,"afterRender"),H.callback(n&&n.onComplete,[t],e)};if(n&&i){var o=new K({numSteps:i/16.66,easing:t.easing||n.easing,render:function(t,e){var n=H.easing.effects[e.easing],i=e.currentStep,a=i/e.numSteps;t.draw(n(a),a,i)},onAnimationProgress:n.onProgress,onAnimationComplete:r});J.addAnimation(e,o,i,a)}else e.draw(),r(new K({numSteps:0,chart:e}));return e}},draw:function(t){var e,n,i=this;if(i.clear(),H.isNullOrUndef(t)&&(t=1),i.transition(t),!(i.width<=0||i.height<=0)&&!1!==Le.notify(i,"beforeDraw",[t])){for(n=i._layers,e=0;e<n.length&&n[e].z<=0;++e)n[e].draw(i.chartArea);for(i.drawDatasets(t);e<n.length;++e)n[e].draw(i.chartArea);i._drawTooltip(t),Le.notify(i,"afterDraw",[t])}},transition:function(t){for(var e=0,n=(this.data.datasets||[]).length;e<n;++e)this.isDatasetVisible(e)&&this.getDatasetMeta(e).controller.transition(t);this.tooltip.transition(t)},_getSortedDatasetMetas:function(t){var e,n,i=[];for(e=0,n=(this.data.datasets||[]).length;e<n;++e)t&&!this.isDatasetVisible(e)||i.push(this.getDatasetMeta(e));return i.sort(Qe("order","index")),i},_getSortedVisibleDatasetMetas:function(){return this._getSortedDatasetMetas(!0)},drawDatasets:function(t){var e,n;if(!1!==Le.notify(this,"beforeDatasetsDraw",[t])){for(n=(e=this._getSortedVisibleDatasetMetas()).length-1;n>=0;--n)this.drawDataset(e[n],t);Le.notify(this,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n={meta:t,index:t.index,easingValue:e};!1!==Le.notify(this,"beforeDatasetDraw",[n])&&(t.controller.draw(e),Le.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,n={tooltip:e,easingValue:t};!1!==Le.notify(this,"beforeTooltipDraw",[n])&&(e.draw(),Le.notify(this,"afterTooltipDraw",[n]))},getElementAtEvent:function(t){return re.modes.single(this,t)},getElementsAtEvent:function(t){return re.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return re.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=re.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return re.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var n=e._meta[this.id];return n||(n=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e.order||0,index:t}),n},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e<n;++e)this.isDatasetVisible(e)&&t++;return t},isDatasetVisible:function(t){var e=this.getDatasetMeta(t);return"boolean"==typeof e.hidden?!e.hidden:!this.data.datasets[t].hidden},generateLegend:function(){return this.options.legendCallback(this)},destroyDatasetMeta:function(t){var e=this.id,n=this.data.datasets[t],i=n._meta&&n._meta[e];i&&(i.controller.destroy(),delete n._meta[e])},destroy:function(){var t,e,n=this,i=n.canvas;for(n.stop(),t=0,e=n.data.datasets.length;t<e;++t)n.destroyDatasetMeta(t);i&&(n.unbindEvents(),H.canvas.clear(n),Ie.releaseContext(n.ctx),n.canvas=null,n.ctx=null),Le.notify(n,"destroy"),delete tn.instances[n.id]},toBase64Image:function(){return this.canvas.toDataURL.apply(this.canvas,arguments)},initToolTip:function(){var t=this;t.tooltip=new Ge({_chart:t,_chartInstance:t,_data:t.data,_options:t.options.tooltips},t)},bindEvents:function(){var t=this,e=t._listeners={},n=function(){t.eventHandler.apply(t,arguments)};H.each(t.options.events,(function(i){Ie.addEventListener(t,i,n),e[i]=n})),t.options.responsive&&(n=function(){t.resize()},Ie.addEventListener(t,"resize",n),e.resize=n)},unbindEvents:function(){var t=this,e=t._listeners;e&&(delete t._listeners,H.each(e,(function(e,n){Ie.removeEventListener(t,n,e)})))},updateHoverStyle:function(t,e,n){var i,a,r,o=n?"set":"remove";for(a=0,r=t.length;a<r;++a)(i=t[a])&&this.getDatasetMeta(i._datasetIndex).controller[o+"HoverStyle"](i);"dataset"===e&&this.getDatasetMeta(t[0]._datasetIndex).controller["_"+o+"DatasetHoverStyle"]()},eventHandler:function(t){var e=this,n=e.tooltip;if(!1!==Le.notify(e,"beforeEvent",[t])){e._bufferedRender=!0,e._bufferedRequest=null;var i=e.handleEvent(t);n&&(i=n._start?n.handleEvent(t):i|n.handleEvent(t)),Le.notify(e,"afterEvent",[t]);var a=e._bufferedRequest;return a?e.render(a):i&&!e.animating&&(e.stop(),e.render({duration:e.options.hover.animationDuration,lazy:!0})),e._bufferedRender=!1,e._bufferedRequest=null,e}},handleEvent:function(t){var e,n=this,i=n.options||{},a=i.hover;return n.lastActive=n.lastActive||[],"mouseout"===t.type?n.active=[]:n.active=n.getElementsAtEventForMode(t,a.mode,a),H.callback(i.onHover||i.hover.onHover,[t.native,n.active],n),"mouseup"!==t.type&&"click"!==t.type||i.onClick&&i.onClick.call(n,t.native,n.active),n.lastActive.length&&n.updateHoverStyle(n.lastActive,a.mode,!1),n.active.length&&a.mode&&n.updateHoverStyle(n.active,a.mode,!0),e=!H.arrayEquals(n.active,n.lastActive),n.lastActive=n.active,e}}),tn.instances={};var en=tn;tn.Controller=tn,tn.types={},H.configMerge=$e,H.scaleMerge=Ze;function nn(){throw new Error("This method is not implemented: either no adapter can be found or an incomplete integration was provided.")}function an(t){this.options=t||{}}H.extend(an.prototype,{formats:nn,parse:nn,format:nn,add:nn,diff:nn,startOf:nn,endOf:nn,_create:function(t){return t}}),an.override=function(t){H.extend(an.prototype,t)};var rn={_date:an},on={formatters:{values:function(t){return H.isArray(t)?t:""+t},linear:function(t,e,n){var i=n.length>3?n[2]-n[1]:n[1]-n[0];Math.abs(i)>1&&t!==Math.floor(t)&&(i=t-Math.floor(t));var a=H.log10(Math.abs(i)),r="";if(0!==t)if(Math.max(Math.abs(n[0]),Math.abs(n[n.length-1]))<1e-4){var o=H.log10(Math.abs(t)),s=Math.floor(o)-Math.floor(a);s=Math.max(Math.min(s,20),0),r=t.toExponential(s)}else{var l=-1*Math.floor(a);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var i=t/Math.pow(10,Math.floor(H.log10(t)));return 0===t?"0":1===i||2===i||5===i||0===e||e===n.length-1?t.toExponential():""}}},sn=H.isArray,ln=H.isNullOrUndef,un=H.valueOrDefault,dn=H.valueAtIndexOrDefault;function hn(t,e,n){var i,a=t.getTicks().length,r=Math.min(e,a-1),o=t.getPixelForTick(r),s=t._startPixel,l=t._endPixel;if(!(n&&(i=1===a?Math.max(o-s,l-o):0===e?(t.getPixelForTick(1)-o)/2:(o-t.getPixelForTick(r-1))/2,(o+=r<e?i:-i)<s-1e-6||o>l+1e-6)))return o}function cn(t,e,n,i){var a,r,o,s,l,u,d,h,c,f,g,m,p,v=n.length,b=[],y=[],x=[];for(a=0;a<v;++a){if(s=n[a].label,l=n[a].major?e.major:e.minor,t.font=u=l.string,d=i[u]=i[u]||{data:{},gc:[]},h=l.lineHeight,c=f=0,ln(s)||sn(s)){if(sn(s))for(r=0,o=s.length;r<o;++r)g=s[r],ln(g)||sn(g)||(c=H.measureText(t,d.data,d.gc,c,g),f+=h)}else c=H.measureText(t,d.data,d.gc,c,s),f=h;b.push(c),y.push(f),x.push(h/2)}function _(t){return{width:b[t]||0,height:y[t]||0,offset:x[t]||0}}return function(t,e){H.each(t,(function(t){var n,i=t.gc,a=i.length/2;if(a>e){for(n=0;n<a;++n)delete t.data[i[n]];i.splice(0,a)}}))}(i,v),m=b.indexOf(Math.max.apply(null,b)),p=y.indexOf(Math.max.apply(null,y)),{first:_(0),last:_(v-1),widest:_(m),highest:_(p)}}function fn(t){return t.drawTicks?t.tickMarkLength:0}function gn(t){var e,n;return t.display?(e=H.options._parseFont(t),n=H.options.toPadding(t.padding),e.lineHeight+n.height):0}function mn(t,e){return H.extend(H.options._parseFont({fontFamily:un(e.fontFamily,t.fontFamily),fontSize:un(e.fontSize,t.fontSize),fontStyle:un(e.fontStyle,t.fontStyle),lineHeight:un(e.lineHeight,t.lineHeight)}),{color:H.options.resolve([e.fontColor,t.fontColor,W.global.defaultFontColor])})}function pn(t){var e=mn(t,t.minor);return{minor:e,major:t.major.enabled?mn(t,t.major):e}}function vn(t){var e,n,i,a=[];for(n=0,i=t.length;n<i;++n)void 0!==(e=t[n])._index&&a.push(e);return a}function bn(t,e,n,i){var a,r,o,s,l=un(n,0),u=Math.min(un(i,t.length),t.length),d=0;for(e=Math.ceil(e),i&&(e=(a=i-n)/Math.floor(a/e)),s=l;s<0;)d++,s=Math.round(l+d*e);for(r=Math.max(l,0);r<u;r++)o=t[r],r===s?(o._index=r,d++,s=Math.round(l+d*e)):delete o.label}W._set("scale",{display:!0,position:"left",offset:!1,gridLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,drawBorder:!0,drawOnChartArea:!0,drawTicks:!0,tickMarkLength:10,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",zeroLineBorderDash:[],zeroLineBorderDashOffset:0,offsetGridLines:!1,borderDash:[],borderDashOffset:0},scaleLabel:{display:!1,labelString:"",padding:{top:4,bottom:4}},ticks:{beginAtZero:!1,minRotation:0,maxRotation:50,mirror:!1,padding:0,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,labelOffset:0,callback:on.formatters.values,minor:{},major:{}}});var yn=$.extend({zeroLineIndex:0,getPadding:function(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}},getTicks:function(){return this._ticks},_getLabels:function(){var t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]},mergeTicksOptions:function(){},beforeUpdate:function(){H.callback(this.options.beforeUpdate,[this])},update:function(t,e,n){var i,a,r,o,s,l=this,u=l.options.ticks,d=u.sampleSize;if(l.beforeUpdate(),l.maxWidth=t,l.maxHeight=e,l.margins=H.extend({left:0,right:0,top:0,bottom:0},n),l._ticks=null,l.ticks=null,l._labelSizes=null,l._maxLabelLines=0,l.longestLabelWidth=0,l.longestTextCache=l.longestTextCache||{},l._gridLineItems=null,l._labelItems=null,l.beforeSetDimensions(),l.setDimensions(),l.afterSetDimensions(),l.beforeDataLimits(),l.determineDataLimits(),l.afterDataLimits(),l.beforeBuildTicks(),o=l.buildTicks()||[],(!(o=l.afterBuildTicks(o)||o)||!o.length)&&l.ticks)for(o=[],i=0,a=l.ticks.length;i<a;++i)o.push({value:l.ticks[i],major:!1});return l._ticks=o,s=d<o.length,r=l._convertTicksToLabels(s?function(t,e){for(var n=[],i=t.length/e,a=0,r=t.length;a<r;a+=i)n.push(t[Math.floor(a)]);return n}(o,d):o),l._configure(),l.beforeCalculateTickRotation(),l.calculateTickRotation(),l.afterCalculateTickRotation(),l.beforeFit(),l.fit(),l.afterFit(),l._ticksToDraw=u.display&&(u.autoSkip||"auto"===u.source)?l._autoSkip(o):o,s&&(r=l._convertTicksToLabels(l._ticksToDraw)),l.ticks=r,l.afterUpdate(),l.minSize},_configure:function(){var t,e,n=this,i=n.options.ticks.reverse;n.isHorizontal()?(t=n.left,e=n.right):(t=n.top,e=n.bottom,i=!i),n._startPixel=t,n._endPixel=e,n._reversePixels=i,n._length=e-t},afterUpdate:function(){H.callback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){H.callback(this.options.beforeSetDimensions,[this])},setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0},afterSetDimensions:function(){H.callback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){H.callback(this.options.beforeDataLimits,[this])},determineDataLimits:H.noop,afterDataLimits:function(){H.callback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){H.callback(this.options.beforeBuildTicks,[this])},buildTicks:H.noop,afterBuildTicks:function(t){var e=this;return sn(t)&&t.length?H.callback(e.options.afterBuildTicks,[e,t]):(e.ticks=H.callback(e.options.afterBuildTicks,[e,e.ticks])||e.ticks,t)},beforeTickToLabelConversion:function(){H.callback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){var t=this.options.ticks;this.ticks=this.ticks.map(t.userCallback||t.callback,this)},afterTickToLabelConversion:function(){H.callback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){H.callback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var t,e,n,i,a,r,o,s=this,l=s.options,u=l.ticks,d=s.getTicks().length,h=u.minRotation||0,c=u.maxRotation,f=h;!s._isVisible()||!u.display||h>=c||d<=1||!s.isHorizontal()?s.labelRotation=h:(e=(t=s._getLabelSizes()).widest.width,n=t.highest.height-t.highest.offset,i=Math.min(s.maxWidth,s.chart.width-e),e+6>(a=l.offset?s.maxWidth/d:i/(d-1))&&(a=i/(d-(l.offset?.5:1)),r=s.maxHeight-fn(l.gridLines)-u.padding-gn(l.scaleLabel),o=Math.sqrt(e*e+n*n),f=H.toDegrees(Math.min(Math.asin(Math.min((t.highest.height+6)/a,1)),Math.asin(Math.min(r/o,1))-Math.asin(n/o))),f=Math.max(h,Math.min(c,f))),s.labelRotation=f)},afterCalculateTickRotation:function(){H.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){H.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},n=t.chart,i=t.options,a=i.ticks,r=i.scaleLabel,o=i.gridLines,s=t._isVisible(),l="bottom"===i.position,u=t.isHorizontal();if(u?e.width=t.maxWidth:s&&(e.width=fn(o)+gn(r)),u?s&&(e.height=fn(o)+gn(r)):e.height=t.maxHeight,a.display&&s){var d=pn(a),h=t._getLabelSizes(),c=h.first,f=h.last,g=h.widest,m=h.highest,p=.4*d.minor.lineHeight,v=a.padding;if(u){var b=0!==t.labelRotation,y=H.toRadians(t.labelRotation),x=Math.cos(y),_=Math.sin(y),w=_*g.width+x*(m.height-(b?m.offset:0))+(b?0:p);e.height=Math.min(t.maxHeight,e.height+w+v);var k,M,S=t.getPixelForTick(0)-t.left,D=t.right-t.getPixelForTick(t.getTicks().length-1);b?(k=l?x*c.width+_*c.offset:_*(c.height-c.offset),M=l?_*(f.height-f.offset):x*f.width+_*f.offset):(k=c.width/2,M=f.width/2),t.paddingLeft=Math.max((k-S)*t.width/(t.width-S),0)+3,t.paddingRight=Math.max((M-D)*t.width/(t.width-D),0)+3}else{var C=a.mirror?0:g.width+v+p;e.width=Math.min(t.maxWidth,e.width+C),t.paddingTop=c.height/2,t.paddingBottom=f.height/2}}t.handleMargins(),u?(t.width=t._length=n.width-t.margins.left-t.margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=n.height-t.margins.top-t.margins.bottom)},handleMargins:function(){var t=this;t.margins&&(t.margins.left=Math.max(t.paddingLeft,t.margins.left),t.margins.top=Math.max(t.paddingTop,t.margins.top),t.margins.right=Math.max(t.paddingRight,t.margins.right),t.margins.bottom=Math.max(t.paddingBottom,t.margins.bottom))},afterFit:function(){H.callback(this.options.afterFit,[this])},isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(ln(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},_convertTicksToLabels:function(t){var e,n,i,a=this;for(a.ticks=t.map((function(t){return t.value})),a.beforeTickToLabelConversion(),e=a.convertTicksToLabels(t)||a.ticks,a.afterTickToLabelConversion(),n=0,i=t.length;n<i;++n)t[n].label=e[n];return e},_getLabelSizes:function(){var t=this,e=t._labelSizes;return e||(t._labelSizes=e=cn(t.ctx,pn(t.options.ticks),t.getTicks(),t.longestTextCache),t.longestLabelWidth=e.widest.width),e},_parseValue:function(t){var e,n,i,a;return sn(t)?(e=+this.getRightValue(t[0]),n=+this.getRightValue(t[1]),i=Math.min(e,n),a=Math.max(e,n)):(e=void 0,n=t=+this.getRightValue(t),i=t,a=t),{min:i,max:a,start:e,end:n}},_getScaleLabel:function(t){var e=this._parseValue(t);return void 0!==e.start?"["+e.start+", "+e.end+"]":+this.getRightValue(t)},getLabelForIndex:H.noop,getPixelForValue:H.noop,getValueForPixel:H.noop,getPixelForTick:function(t){var e=this.options.offset,n=this._ticks.length,i=1/Math.max(n-(e?0:1),1);return t<0||t>n-1?null:this.getPixelForDecimal(t*i+(e?i/2:0))},getPixelForDecimal:function(t){return this._reversePixels&&(t=1-t),this._startPixel+t*this._length},getDecimalForPixel:function(t){var e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,n,i,a,r=this.options.ticks,o=this._length,s=r.maxTicksLimit||o/this._tickSize()+1,l=r.major.enabled?function(t){var e,n,i=[];for(e=0,n=t.length;e<n;e++)t[e].major&&i.push(e);return i}(t):[],u=l.length,d=l[0],h=l[u-1];if(u>s)return function(t,e,n){var i,a,r=0,o=e[0];for(n=Math.ceil(n),i=0;i<t.length;i++)a=t[i],i===o?(a._index=i,o=e[++r*n]):delete a.label}(t,l,u/s),vn(t);if(i=function(t,e,n,i){var a,r,o,s,l=function(t){var e,n,i=t.length;if(i<2)return!1;for(n=t[0],e=1;e<i;++e)if(t[e]-t[e-1]!==n)return!1;return n}(t),u=(e.length-1)/i;if(!l)return Math.max(u,1);for(o=0,s=(a=H.math._factorize(l)).length-1;o<s;o++)if((r=a[o])>u)return r;return Math.max(u,1)}(l,t,0,s),u>0){for(e=0,n=u-1;e<n;e++)bn(t,i,l[e],l[e+1]);return a=u>1?(h-d)/(u-1):null,bn(t,i,H.isNullOrUndef(a)?0:d-a,d),bn(t,i,h,H.isNullOrUndef(a)?t.length:h+a),vn(t)}return bn(t,i),vn(t)},_tickSize:function(){var t=this.options.ticks,e=H.toRadians(this.labelRotation),n=Math.abs(Math.cos(e)),i=Math.abs(Math.sin(e)),a=this._getLabelSizes(),r=t.autoSkipPadding||0,o=a?a.widest.width+r:0,s=a?a.highest.height+r:0;return this.isHorizontal()?s*n>o*i?o/n:s/i:s*i<o*n?s/n:o/i},_isVisible:function(){var t,e,n,i=this.chart,a=this.options.display;if("auto"!==a)return!!a;for(t=0,e=i.data.datasets.length;t<e;++t)if(i.isDatasetVisible(t)&&((n=i.getDatasetMeta(t)).xAxisID===this.id||n.yAxisID===this.id))return!0;return!1},_computeGridLineItems:function(t){var e,n,i,a,r,o,s,l,u,d,h,c,f,g,m,p,v,b=this,y=b.chart,x=b.options,_=x.gridLines,w=x.position,k=_.offsetGridLines,M=b.isHorizontal(),S=b._ticksToDraw,D=S.length+(k?1:0),C=fn(_),P=[],T=_.drawBorder?dn(_.lineWidth,0,0):0,O=T/2,A=H._alignPixel,F=function(t){return A(y,t,T)};for("top"===w?(e=F(b.bottom),s=b.bottom-C,u=e-O,h=F(t.top)+O,f=t.bottom):"bottom"===w?(e=F(b.top),h=t.top,f=F(t.bottom)-O,s=e+O,u=b.top+C):"left"===w?(e=F(b.right),o=b.right-C,l=e-O,d=F(t.left)+O,c=t.right):(e=F(b.left),d=t.left,c=F(t.right)-O,o=e+O,l=b.left+C),n=0;n<D;++n)i=S[n]||{},ln(i.label)&&n<S.length||(n===b.zeroLineIndex&&x.offset===k?(g=_.zeroLineWidth,m=_.zeroLineColor,p=_.zeroLineBorderDash||[],v=_.zeroLineBorderDashOffset||0):(g=dn(_.lineWidth,n,1),m=dn(_.color,n,"rgba(0,0,0,0.1)"),p=_.borderDash||[],v=_.borderDashOffset||0),void 0!==(a=hn(b,i._index||n,k))&&(r=A(y,a,g),M?o=l=d=c=r:s=u=h=f=r,P.push({tx1:o,ty1:s,tx2:l,ty2:u,x1:d,y1:h,x2:c,y2:f,width:g,color:m,borderDash:p,borderDashOffset:v})));return P.ticksLength=D,P.borderValue=e,P},_computeLabelItems:function(){var t,e,n,i,a,r,o,s,l,u,d,h,c=this,f=c.options,g=f.ticks,m=f.position,p=g.mirror,v=c.isHorizontal(),b=c._ticksToDraw,y=pn(g),x=g.padding,_=fn(f.gridLines),w=-H.toRadians(c.labelRotation),k=[];for("top"===m?(r=c.bottom-_-x,o=w?"left":"center"):"bottom"===m?(r=c.top+_+x,o=w?"right":"center"):"left"===m?(a=c.right-(p?0:_)-x,o=p?"left":"right"):(a=c.left+(p?0:_)+x,o=p?"right":"left"),t=0,e=b.length;t<e;++t)i=(n=b[t]).label,ln(i)||(s=c.getPixelForTick(n._index||t)+g.labelOffset,u=(l=n.major?y.major:y.minor).lineHeight,d=sn(i)?i.length:1,v?(a=s,h="top"===m?((w?1:.5)-d)*u:(w?0:.5)*u):(r=s,h=(1-d)*u/2),k.push({x:a,y:r,rotation:w,label:i,font:l,textOffset:h,textAlign:o}));return k},_drawGrid:function(t){var e=this,n=e.options.gridLines;if(n.display){var i,a,r,o,s,l=e.ctx,u=e.chart,d=H._alignPixel,h=n.drawBorder?dn(n.lineWidth,0,0):0,c=e._gridLineItems||(e._gridLineItems=e._computeGridLineItems(t));for(r=0,o=c.length;r<o;++r)i=(s=c[r]).width,a=s.color,i&&a&&(l.save(),l.lineWidth=i,l.strokeStyle=a,l.setLineDash&&(l.setLineDash(s.borderDash),l.lineDashOffset=s.borderDashOffset),l.beginPath(),n.drawTicks&&(l.moveTo(s.tx1,s.ty1),l.lineTo(s.tx2,s.ty2)),n.drawOnChartArea&&(l.moveTo(s.x1,s.y1),l.lineTo(s.x2,s.y2)),l.stroke(),l.restore());if(h){var f,g,m,p,v=h,b=dn(n.lineWidth,c.ticksLength-1,1),y=c.borderValue;e.isHorizontal()?(f=d(u,e.left,v)-v/2,g=d(u,e.right,b)+b/2,m=p=y):(m=d(u,e.top,v)-v/2,p=d(u,e.bottom,b)+b/2,f=g=y),l.lineWidth=h,l.strokeStyle=dn(n.color,0),l.beginPath(),l.moveTo(f,m),l.lineTo(g,p),l.stroke()}}},_drawLabels:function(){var t=this;if(t.options.ticks.display){var e,n,i,a,r,o,s,l,u=t.ctx,d=t._labelItems||(t._labelItems=t._computeLabelItems());for(e=0,i=d.length;e<i;++e){if(o=(r=d[e]).font,u.save(),u.translate(r.x,r.y),u.rotate(r.rotation),u.font=o.string,u.fillStyle=o.color,u.textBaseline="middle",u.textAlign=r.textAlign,s=r.label,l=r.textOffset,sn(s))for(n=0,a=s.length;n<a;++n)u.fillText(""+s[n],0,l),l+=o.lineHeight;else u.fillText(s,0,l);u.restore()}}},_drawTitle:function(){var t=this,e=t.ctx,n=t.options,i=n.scaleLabel;if(i.display){var a,r,o=un(i.fontColor,W.global.defaultFontColor),s=H.options._parseFont(i),l=H.options.toPadding(i.padding),u=s.lineHeight/2,d=n.position,h=0;if(t.isHorizontal())a=t.left+t.width/2,r="bottom"===d?t.bottom-u-l.bottom:t.top+u+l.top;else{var c="left"===d;a=c?t.left+u+l.top:t.right-u-l.top,r=t.top+t.height/2,h=c?-.5*Math.PI:.5*Math.PI}e.save(),e.translate(a,r),e.rotate(h),e.textAlign="center",e.textBaseline="middle",e.fillStyle=o,e.font=s.string,e.fillText(i.labelString,0,0),e.restore()}},draw:function(t){this._isVisible()&&(this._drawGrid(t),this._drawTitle(),this._drawLabels())},_layers:function(){var t=this,e=t.options,n=e.ticks&&e.ticks.z||0,i=e.gridLines&&e.gridLines.z||0;return t._isVisible()&&n!==i&&t.draw===t._draw?[{z:i,draw:function(){t._drawGrid.apply(t,arguments),t._drawTitle.apply(t,arguments)}},{z:n,draw:function(){t._drawLabels.apply(t,arguments)}}]:[{z:n,draw:function(){t.draw.apply(t,arguments)}}]},_getMatchingVisibleMetas:function(t){var e=this,n=e.isHorizontal();return e.chart._getSortedVisibleDatasetMetas().filter((function(i){return(!t||i.type===t)&&(n?i.xAxisID===e.id:i.yAxisID===e.id)}))}});yn.prototype._draw=yn.prototype.draw;var xn=yn,_n=H.isNullOrUndef,wn=xn.extend({determineDataLimits:function(){var t,e=this,n=e._getLabels(),i=e.options.ticks,a=i.min,r=i.max,o=0,s=n.length-1;void 0!==a&&(t=n.indexOf(a))>=0&&(o=t),void 0!==r&&(t=n.indexOf(r))>=0&&(s=t),e.minIndex=o,e.maxIndex=s,e.min=n[o],e.max=n[s]},buildTicks:function(){var t=this._getLabels(),e=this.minIndex,n=this.maxIndex;this.ticks=0===e&&n===t.length-1?t:t.slice(e,n+1)},getLabelForIndex:function(t,e){var n=this.chart;return n.getDatasetMeta(e).controller._getValueScaleId()===this.id?this.getRightValue(n.data.datasets[e].data[t]):this._getLabels()[t]},_configure:function(){var t=this,e=t.options.offset,n=t.ticks;xn.prototype._configure.call(t),t.isHorizontal()||(t._reversePixels=!t._reversePixels),n&&(t._startValue=t.minIndex-(e?.5:0),t._valueRange=Math.max(n.length-(e?0:1),1))},getPixelForValue:function(t,e,n){var i,a,r,o=this;return _n(e)||_n(n)||(t=o.chart.data.datasets[n].data[e]),_n(t)||(i=o.isHorizontal()?t.x:t.y),(void 0!==i||void 0!==t&&isNaN(e))&&(a=o._getLabels(),t=H.valueOrDefault(i,t),e=-1!==(r=a.indexOf(t))?r:e,isNaN(e)&&(e=t)),o.getPixelForDecimal((e-o._startValue)/o._valueRange)},getPixelForTick:function(t){var e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t],t+this.minIndex)},getValueForPixel:function(t){var e=Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange);return Math.min(Math.max(e,0),this.ticks.length-1)},getBasePixel:function(){return this.bottom}}),kn={position:"bottom"};wn._defaults=kn;var Mn=H.noop,Sn=H.isNullOrUndef;var Dn=xn.extend({getRightValue:function(t){return"string"==typeof t?+t:xn.prototype.getRightValue.call(this,t)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=H.sign(t.min),i=H.sign(t.max);n<0&&i<0?t.max=0:n>0&&i>0&&(t.min=0)}var a=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),a!==r&&t.min>=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this.options.ticks,n=e.stepSize,i=e.maxTicksLimit;return n?t=Math.ceil(this.max/n)-Math.floor(this.min/n)+1:(t=this._computeTickLimit(),i=i||11),i&&(t=Math.min(i,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:Mn,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),i={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,precision:e.precision,stepSize:H.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var n,i,a,r,o=[],s=t.stepSize,l=s||1,u=t.maxTicks-1,d=t.min,h=t.max,c=t.precision,f=e.min,g=e.max,m=H.niceNum((g-f)/u/l)*l;if(m<1e-14&&Sn(d)&&Sn(h))return[f,g];(r=Math.ceil(g/m)-Math.floor(f/m))>u&&(m=H.niceNum(r*m/u/l)*l),s||Sn(c)?n=Math.pow(10,H._decimalPlaces(m)):(n=Math.pow(10,c),m=Math.ceil(m*n)/n),i=Math.floor(f/m)*m,a=Math.ceil(g/m)*m,s&&(!Sn(d)&&H.almostWhole(d/m,m/1e3)&&(i=d),!Sn(h)&&H.almostWhole(h/m,m/1e3)&&(a=h)),r=(a-i)/m,r=H.almostEquals(r,Math.round(r),m/1e3)?Math.round(r):Math.ceil(r),i=Math.round(i*n)/n,a=Math.round(a*n)/n,o.push(Sn(d)?i:d);for(var p=1;p<r;++p)o.push(Math.round((i+p*m)*n)/n);return o.push(Sn(h)?a:h),o}(i,t);t.handleDirectionalChanges(),t.max=H.max(a),t.min=H.min(a),e.reverse?(a.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max)},convertTicksToLabels:function(){var t=this;t.ticksAsNumbers=t.ticks.slice(),t.zeroLineIndex=t.ticks.indexOf(0),xn.prototype.convertTicksToLabels.call(t)},_configure:function(){var t,e=this,n=e.getTicks(),i=e.min,a=e.max;xn.prototype._configure.call(e),e.options.offset&&n.length&&(i-=t=(a-i)/Math.max(n.length-1,1)/2,a+=t),e._startValue=i,e._endValue=a,e._valueRange=a-i}}),Cn={position:"left",ticks:{callback:on.formatters.linear}};function Pn(t,e,n,i){var a,r,o=t.options,s=function(t,e,n){var i=[n.type,void 0===e&&void 0===n.stack?n.index:"",n.stack].join(".");return void 0===t[i]&&(t[i]={pos:[],neg:[]}),t[i]}(e,o.stacked,n),l=s.pos,u=s.neg,d=i.length;for(a=0;a<d;++a)r=t._parseValue(i[a]),isNaN(r.min)||isNaN(r.max)||n.data[a].hidden||(l[a]=l[a]||0,u[a]=u[a]||0,o.relativePoints?l[a]=100:r.min<0||r.max<0?u[a]+=r.min:l[a]+=r.max)}function Tn(t,e,n){var i,a,r=n.length;for(i=0;i<r;++i)a=t._parseValue(n[i]),isNaN(a.min)||isNaN(a.max)||e.data[i].hidden||(t.min=Math.min(t.min,a.min),t.max=Math.max(t.max,a.max))}var On=Dn.extend({determineDataLimits:function(){var t,e,n,i,a=this,r=a.options,o=a.chart.data.datasets,s=a._getMatchingVisibleMetas(),l=r.stacked,u={},d=s.length;if(a.min=Number.POSITIVE_INFINITY,a.max=Number.NEGATIVE_INFINITY,void 0===l)for(t=0;!l&&t<d;++t)l=void 0!==(e=s[t]).stack;for(t=0;t<d;++t)n=o[(e=s[t]).index].data,l?Pn(a,u,e,n):Tn(a,e,n);H.each(u,(function(t){i=t.pos.concat(t.neg),a.min=Math.min(a.min,H.min(i)),a.max=Math.max(a.max,H.max(i))})),a.min=H.isFinite(a.min)&&!isNaN(a.min)?a.min:0,a.max=H.isFinite(a.max)&&!isNaN(a.max)?a.max:1,a.handleTickRangeOptions()},_computeTickLimit:function(){var t;return this.isHorizontal()?Math.ceil(this.width/40):(t=H.options._parseFont(this.options.ticks),Math.ceil(this.height/t.lineHeight))},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return this._getScaleLabel(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){return this.getPixelForDecimal((+this.getRightValue(t)-this._startValue)/this._valueRange)},getValueForPixel:function(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange},getPixelForTick:function(t){var e=this.ticksAsNumbers;return t<0||t>e.length-1?null:this.getPixelForValue(e[t])}}),An=Cn;On._defaults=An;var Fn=H.valueOrDefault,In=H.math.log10;var Ln={position:"left",ticks:{callback:on.formatters.logarithmic}};function Rn(t,e){return H.isFinite(t)&&t>=0?t:e}var Nn=xn.extend({determineDataLimits:function(){var t,e,n,i,a,r,o=this,s=o.options,l=o.chart,u=l.data.datasets,d=o.isHorizontal();function h(t){return d?t.xAxisID===o.id:t.yAxisID===o.id}o.min=Number.POSITIVE_INFINITY,o.max=Number.NEGATIVE_INFINITY,o.minNotZero=Number.POSITIVE_INFINITY;var c=s.stacked;if(void 0===c)for(t=0;t<u.length;t++)if(e=l.getDatasetMeta(t),l.isDatasetVisible(t)&&h(e)&&void 0!==e.stack){c=!0;break}if(s.stacked||c){var f={};for(t=0;t<u.length;t++){var g=[(e=l.getDatasetMeta(t)).type,void 0===s.stacked&&void 0===e.stack?t:"",e.stack].join(".");if(l.isDatasetVisible(t)&&h(e))for(void 0===f[g]&&(f[g]=[]),a=0,r=(i=u[t].data).length;a<r;a++){var m=f[g];n=o._parseValue(i[a]),isNaN(n.min)||isNaN(n.max)||e.data[a].hidden||n.min<0||n.max<0||(m[a]=m[a]||0,m[a]+=n.max)}}H.each(f,(function(t){if(t.length>0){var e=H.min(t),n=H.max(t);o.min=Math.min(o.min,e),o.max=Math.max(o.max,n)}}))}else for(t=0;t<u.length;t++)if(e=l.getDatasetMeta(t),l.isDatasetVisible(t)&&h(e))for(a=0,r=(i=u[t].data).length;a<r;a++)n=o._parseValue(i[a]),isNaN(n.min)||isNaN(n.max)||e.data[a].hidden||n.min<0||n.max<0||(o.min=Math.min(n.min,o.min),o.max=Math.max(n.max,o.max),0!==n.min&&(o.minNotZero=Math.min(n.min,o.minNotZero)));o.min=H.isFinite(o.min)?o.min:null,o.max=H.isFinite(o.max)?o.max:null,o.minNotZero=H.isFinite(o.minNotZero)?o.minNotZero:null,this.handleTickRangeOptions()},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;t.min=Rn(e.min,t.min),t.max=Rn(e.max,t.max),t.min===t.max&&(0!==t.min&&null!==t.min?(t.min=Math.pow(10,Math.floor(In(t.min))-1),t.max=Math.pow(10,Math.floor(In(t.max))+1)):(t.min=1,t.max=10)),null===t.min&&(t.min=Math.pow(10,Math.floor(In(t.max))-1)),null===t.max&&(t.max=0!==t.min?Math.pow(10,Math.floor(In(t.min))+1):10),null===t.minNotZero&&(t.min>0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(In(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,n=!t.isHorizontal(),i={min:Rn(e.min),max:Rn(e.max)},a=t.ticks=function(t,e){var n,i,a=[],r=Fn(t.min,Math.pow(10,Math.floor(In(e.min)))),o=Math.floor(In(e.max)),s=Math.ceil(e.max/Math.pow(10,o));0===r?(n=Math.floor(In(e.minNotZero)),i=Math.floor(e.minNotZero/Math.pow(10,n)),a.push(r),r=i*Math.pow(10,n)):(n=Math.floor(In(r)),i=Math.floor(r/Math.pow(10,n)));var l=n<0?Math.pow(10,Math.abs(n)):1;do{a.push(r),10===++i&&(i=1,l=++n>=0?1:l),r=Math.round(i*Math.pow(10,n)*l)/l}while(n<o||n===o&&i<s);var u=Fn(t.max,r);return a.push(u),a}(i,t);t.max=H.max(a),t.min=H.min(a),e.reverse?(n=!n,t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max),n&&a.reverse()},convertTicksToLabels:function(){this.tickValues=this.ticks.slice(),xn.prototype.convertTicksToLabels.call(this)},getLabelForIndex:function(t,e){return this._getScaleLabel(this.chart.data.datasets[e].data[t])},getPixelForTick:function(t){var e=this.tickValues;return t<0||t>e.length-1?null:this.getPixelForValue(e[t])},_getFirstTickValue:function(t){var e=Math.floor(In(t));return Math.floor(t/Math.pow(10,e))*Math.pow(10,e)},_configure:function(){var t=this,e=t.min,n=0;xn.prototype._configure.call(t),0===e&&(e=t._getFirstTickValue(t.minNotZero),n=Fn(t.options.ticks.fontSize,W.global.defaultFontSize)/t._length),t._startValue=In(e),t._valueOffset=n,t._valueRange=(In(t.max)-In(e))/(1-n)},getPixelForValue:function(t){var e=this,n=0;return(t=+e.getRightValue(t))>e.min&&t>0&&(n=(In(t)-e._startValue)/e._valueRange+e._valueOffset),e.getPixelForDecimal(n)},getValueForPixel:function(t){var e=this,n=e.getDecimalForPixel(t);return 0===n&&0===e.min?0:Math.pow(10,e._startValue+(n-e._valueOffset)*e._valueRange)}}),Wn=Ln;Nn._defaults=Wn;var Yn=H.valueOrDefault,zn=H.valueAtIndexOrDefault,En=H.options.resolve,Vn={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:on.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(t){return t}}};function Hn(t){var e=t.ticks;return e.display&&t.display?Yn(e.fontSize,W.global.defaultFontSize)+2*e.backdropPaddingY:0}function Bn(t,e,n,i,a){return t===i||t===a?{start:e-n/2,end:e+n/2}:t<i||t>a?{start:e-n,end:e}:{start:e,end:e+n}}function jn(t){return 0===t||180===t?"center":t<180?"left":"right"}function Un(t,e,n,i){var a,r,o=n.y+i/2;if(H.isArray(e))for(a=0,r=e.length;a<r;++a)t.fillText(e[a],n.x,o),o+=i;else t.fillText(e,n.x,o)}function Gn(t,e,n){90===t||270===t?n.y-=e.h/2:(t>270||t<90)&&(n.y-=e.h)}function qn(t){return H.isNumber(t)?t:0}var Zn=Dn.extend({setDimensions:function(){var t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=Hn(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2},determineDataLimits:function(){var t=this,e=t.chart,n=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;H.each(e.data.datasets,(function(a,r){if(e.isDatasetVisible(r)){var o=e.getDatasetMeta(r);H.each(a.data,(function(e,a){var r=+t.getRightValue(e);isNaN(r)||o.data[a].hidden||(n=Math.min(r,n),i=Math.max(r,i))}))}})),t.min=n===Number.POSITIVE_INFINITY?0:n,t.max=i===Number.NEGATIVE_INFINITY?0:i,t.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Hn(this.options))},convertTicksToLabels:function(){var t=this;Dn.prototype.convertTicksToLabels.call(t),t.pointLabels=t.chart.data.labels.map((function(){var e=H.callback(t.options.pointLabels.callback,arguments,t);return e||0===e?e:""}))},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t=this.options;t.display&&t.pointLabels.display?function(t){var e,n,i,a=H.options._parseFont(t.options.pointLabels),r={l:0,r:t.width,t:0,b:t.height-t.paddingTop},o={};t.ctx.font=a.string,t._pointLabelSizes=[];var s,l,u,d=t.chart.data.labels.length;for(e=0;e<d;e++){i=t.getPointPosition(e,t.drawingArea+5),s=t.ctx,l=a.lineHeight,u=t.pointLabels[e],n=H.isArray(u)?{w:H.longestText(s,s.font,u),h:u.length*l}:{w:s.measureText(u).width,h:l},t._pointLabelSizes[e]=n;var h=t.getIndexAngle(e),c=H.toDegrees(h)%360,f=Bn(c,i.x,n.w,0,180),g=Bn(c,i.y,n.h,90,270);f.start<r.l&&(r.l=f.start,o.l=h),f.end>r.r&&(r.r=f.end,o.r=h),g.start<r.t&&(r.t=g.start,o.t=h),g.end>r.b&&(r.b=g.end,o.b=h)}t.setReductions(t.drawingArea,r,o)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(t,e,n){var i=this,a=e.l/Math.sin(n.l),r=Math.max(e.r-i.width,0)/Math.sin(n.r),o=-e.t/Math.cos(n.t),s=-Math.max(e.b-(i.height-i.paddingTop),0)/Math.cos(n.b);a=qn(a),r=qn(r),o=qn(o),s=qn(s),i.drawingArea=Math.min(Math.floor(t-(a+r)/2),Math.floor(t-(o+s)/2)),i.setCenterPoint(a,r,o,s)},setCenterPoint:function(t,e,n,i){var a=this,r=a.width-e-a.drawingArea,o=t+a.drawingArea,s=n+a.drawingArea,l=a.height-a.paddingTop-i-a.drawingArea;a.xCenter=Math.floor((o+r)/2+a.left),a.yCenter=Math.floor((s+l)/2+a.top+a.paddingTop)},getIndexAngle:function(t){var e=this.chart,n=(t*(360/e.data.labels.length)+((e.options||{}).startAngle||0))%360;return(n<0?n+360:n)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(H.isNullOrUndef(t))return NaN;var n=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*n:(t-e.min)*n},getPointPosition:function(t,e){var n=this.getIndexAngle(t)-Math.PI/2;return{x:Math.cos(n)*e+this.xCenter,y:Math.sin(n)*e+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(t){var e=this.min,n=this.max;return this.getPointPositionForValue(t||0,this.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0)},_drawGrid:function(){var t,e,n,i=this,a=i.ctx,r=i.options,o=r.gridLines,s=r.angleLines,l=Yn(s.lineWidth,o.lineWidth),u=Yn(s.color,o.color);if(r.pointLabels.display&&function(t){var e=t.ctx,n=t.options,i=n.pointLabels,a=Hn(n),r=t.getDistanceFromCenterForValue(n.ticks.reverse?t.min:t.max),o=H.options._parseFont(i);e.save(),e.font=o.string,e.textBaseline="middle";for(var s=t.chart.data.labels.length-1;s>=0;s--){var l=0===s?a/2:0,u=t.getPointPosition(s,r+l+5),d=zn(i.fontColor,s,W.global.defaultFontColor);e.fillStyle=d;var h=t.getIndexAngle(s),c=H.toDegrees(h);e.textAlign=jn(c),Gn(c,t._pointLabelSizes[s],u),Un(e,t.pointLabels[s],u,o.lineHeight)}e.restore()}(i),o.display&&H.each(i.ticks,(function(t,n){0!==n&&(e=i.getDistanceFromCenterForValue(i.ticksAsNumbers[n]),function(t,e,n,i){var a,r=t.ctx,o=e.circular,s=t.chart.data.labels.length,l=zn(e.color,i-1),u=zn(e.lineWidth,i-1);if((o||s)&&l&&u){if(r.save(),r.strokeStyle=l,r.lineWidth=u,r.setLineDash&&(r.setLineDash(e.borderDash||[]),r.lineDashOffset=e.borderDashOffset||0),r.beginPath(),o)r.arc(t.xCenter,t.yCenter,n,0,2*Math.PI);else{a=t.getPointPosition(0,n),r.moveTo(a.x,a.y);for(var d=1;d<s;d++)a=t.getPointPosition(d,n),r.lineTo(a.x,a.y)}r.closePath(),r.stroke(),r.restore()}}(i,o,e,n))})),s.display&&l&&u){for(a.save(),a.lineWidth=l,a.strokeStyle=u,a.setLineDash&&(a.setLineDash(En([s.borderDash,o.borderDash,[]])),a.lineDashOffset=En([s.borderDashOffset,o.borderDashOffset,0])),t=i.chart.data.labels.length-1;t>=0;t--)e=i.getDistanceFromCenterForValue(r.ticks.reverse?i.min:i.max),n=i.getPointPosition(t,e),a.beginPath(),a.moveTo(i.xCenter,i.yCenter),a.lineTo(n.x,n.y),a.stroke();a.restore()}},_drawLabels:function(){var t=this,e=t.ctx,n=t.options.ticks;if(n.display){var i,a,r=t.getIndexAngle(0),o=H.options._parseFont(n),s=Yn(n.fontColor,W.global.defaultFontColor);e.save(),e.font=o.string,e.translate(t.xCenter,t.yCenter),e.rotate(r),e.textAlign="center",e.textBaseline="middle",H.each(t.ticks,(function(r,l){(0!==l||n.reverse)&&(i=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),n.showLabelBackdrop&&(a=e.measureText(r).width,e.fillStyle=n.backdropColor,e.fillRect(-a/2-n.backdropPaddingX,-i-o.size/2-n.backdropPaddingY,a+2*n.backdropPaddingX,o.size+2*n.backdropPaddingY)),e.fillStyle=s,e.fillText(r,0,-i))})),e.restore()}},_drawTitle:H.noop}),$n=Vn;Zn._defaults=$n;var Xn=H._deprecated,Kn=H.options.resolve,Jn=H.valueOrDefault,Qn=Number.MIN_SAFE_INTEGER||-9007199254740991,ti=Number.MAX_SAFE_INTEGER||9007199254740991,ei={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ni=Object.keys(ei);function ii(t,e){return t-e}function ai(t){return H.valueOrDefault(t.time.min,t.ticks.min)}function ri(t){return H.valueOrDefault(t.time.max,t.ticks.max)}function oi(t,e,n,i){var a=function(t,e,n){for(var i,a,r,o=0,s=t.length-1;o>=0&&o<=s;){if(a=t[(i=o+s>>1)-1]||null,r=t[i],!a)return{lo:null,hi:r};if(r[e]<n)o=i+1;else{if(!(a[e]>n))return{lo:a,hi:r};s=i-1}}return{lo:r,hi:null}}(t,e,n),r=a.lo?a.hi?a.lo:t[t.length-2]:t[0],o=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=o[e]-r[e],l=s?(n-r[e])/s:0,u=(o[i]-r[i])*l;return r[i]+u}function si(t,e){var n=t._adapter,i=t.options.time,a=i.parser,r=a||i.format,o=e;return"function"==typeof a&&(o=a(o)),H.isFinite(o)||(o="string"==typeof r?n.parse(o,r):n.parse(o)),null!==o?+o:(a||"function"!=typeof r||(o=r(e),H.isFinite(o)||(o=n.parse(o))),o)}function li(t,e){if(H.isNullOrUndef(e))return null;var n=t.options.time,i=si(t,t.getRightValue(e));return null===i?i:(n.round&&(i=+t._adapter.startOf(i,n.round)),i)}function ui(t,e,n,i){var a,r,o,s=ni.length;for(a=ni.indexOf(t);a<s-1;++a)if(o=(r=ei[ni[a]]).steps?r.steps:ti,r.common&&Math.ceil((n-e)/(o*r.size))<=i)return ni[a];return ni[s-1]}function di(t,e,n){var i,a,r=[],o={},s=e.length;for(i=0;i<s;++i)o[a=e[i]]=i,r.push({value:a,major:!1});return 0!==s&&n?function(t,e,n,i){var a,r,o=t._adapter,s=+o.startOf(e[0].value,i),l=e[e.length-1].value;for(a=s;a<=l;a=+o.add(a,1,i))(r=n[a])>=0&&(e[r].major=!0);return e}(t,r,o,n):r}var hi=xn.extend({initialize:function(){this.mergeTicksOptions(),xn.prototype.initialize.call(this)},update:function(){var t=this,e=t.options,n=e.time||(e.time={}),i=t._adapter=new rn._date(e.adapters.date);return Xn("time scale",n.format,"time.format","time.parser"),Xn("time scale",n.min,"time.min","ticks.min"),Xn("time scale",n.max,"time.max","ticks.max"),H.mergeIf(n.displayFormats,i.formats()),xn.prototype.update.apply(t,arguments)},getRightValue:function(t){return t&&void 0!==t.t&&(t=t.t),xn.prototype.getRightValue.call(this,t)},determineDataLimits:function(){var t,e,n,i,a,r,o,s=this,l=s.chart,u=s._adapter,d=s.options,h=d.time.unit||"day",c=ti,f=Qn,g=[],m=[],p=[],v=s._getLabels();for(t=0,n=v.length;t<n;++t)p.push(li(s,v[t]));for(t=0,n=(l.data.datasets||[]).length;t<n;++t)if(l.isDatasetVisible(t))if(a=l.data.datasets[t].data,H.isObject(a[0]))for(m[t]=[],e=0,i=a.length;e<i;++e)r=li(s,a[e]),g.push(r),m[t][e]=r;else m[t]=p.slice(0),o||(g=g.concat(p),o=!0);else m[t]=[];p.length&&(c=Math.min(c,p[0]),f=Math.max(f,p[p.length-1])),g.length&&(g=n>1?function(t){var e,n,i,a={},r=[];for(e=0,n=t.length;e<n;++e)a[i=t[e]]||(a[i]=!0,r.push(i));return r}(g).sort(ii):g.sort(ii),c=Math.min(c,g[0]),f=Math.max(f,g[g.length-1])),c=li(s,ai(d))||c,f=li(s,ri(d))||f,c=c===ti?+u.startOf(Date.now(),h):c,f=f===Qn?+u.endOf(Date.now(),h)+1:f,s.min=Math.min(c,f),s.max=Math.max(c+1,f),s._table=[],s._timestamps={data:g,datasets:m,labels:p}},buildTicks:function(){var t,e,n,i=this,a=i.min,r=i.max,o=i.options,s=o.ticks,l=o.time,u=i._timestamps,d=[],h=i.getLabelCapacity(a),c=s.source,f=o.distribution;for(u="data"===c||"auto"===c&&"series"===f?u.data:"labels"===c?u.labels:function(t,e,n,i){var a,r=t._adapter,o=t.options,s=o.time,l=s.unit||ui(s.minUnit,e,n,i),u=Kn([s.stepSize,s.unitStepSize,1]),d="week"===l&&s.isoWeekday,h=e,c=[];if(d&&(h=+r.startOf(h,"isoWeek",d)),h=+r.startOf(h,d?"day":l),r.diff(n,e,l)>1e5*u)throw e+" and "+n+" are too far apart with stepSize of "+u+" "+l;for(a=h;a<n;a=+r.add(a,u,l))c.push(a);return a!==n&&"ticks"!==o.bounds||c.push(a),c}(i,a,r,h),"ticks"===o.bounds&&u.length&&(a=u[0],r=u[u.length-1]),a=li(i,ai(o))||a,r=li(i,ri(o))||r,t=0,e=u.length;t<e;++t)(n=u[t])>=a&&n<=r&&d.push(n);return i.min=a,i.max=r,i._unit=l.unit||(s.autoSkip?ui(l.minUnit,i.min,i.max,h):function(t,e,n,i,a){var r,o;for(r=ni.length-1;r>=ni.indexOf(n);r--)if(o=ni[r],ei[o].common&&t._adapter.diff(a,i,o)>=e-1)return o;return ni[n?ni.indexOf(n):0]}(i,d.length,l.minUnit,i.min,i.max)),i._majorUnit=s.major.enabled&&"year"!==i._unit?function(t){for(var e=ni.indexOf(t)+1,n=ni.length;e<n;++e)if(ei[ni[e]].common)return ni[e]}(i._unit):void 0,i._table=function(t,e,n,i){if("linear"===i||!t.length)return[{time:e,pos:0},{time:n,pos:1}];var a,r,o,s,l,u=[],d=[e];for(a=0,r=t.length;a<r;++a)(s=t[a])>e&&s<n&&d.push(s);for(d.push(n),a=0,r=d.length;a<r;++a)l=d[a+1],o=d[a-1],s=d[a],void 0!==o&&void 0!==l&&Math.round((l+o)/2)===s||u.push({time:s,pos:a/(r-1)});return u}(i._timestamps.data,a,r,f),i._offsets=function(t,e,n,i,a){var r,o,s=0,l=0;return a.offset&&e.length&&(r=oi(t,"time",e[0],"pos"),s=1===e.length?1-r:(oi(t,"time",e[1],"pos")-r)/2,o=oi(t,"time",e[e.length-1],"pos"),l=1===e.length?o:(o-oi(t,"time",e[e.length-2],"pos"))/2),{start:s,end:l,factor:1/(s+1+l)}}(i._table,d,0,0,o),s.reverse&&d.reverse(),di(i,d,i._majorUnit)},getLabelForIndex:function(t,e){var n=this,i=n._adapter,a=n.chart.data,r=n.options.time,o=a.labels&&t<a.labels.length?a.labels[t]:"",s=a.datasets[e].data[t];return H.isObject(s)&&(o=n.getRightValue(s)),r.tooltipFormat?i.format(si(n,o),r.tooltipFormat):"string"==typeof o?o:i.format(si(n,o),r.displayFormats.datetime)},tickFormatFunction:function(t,e,n,i){var a=this._adapter,r=this.options,o=r.time.displayFormats,s=o[this._unit],l=this._majorUnit,u=o[l],d=n[e],h=r.ticks,c=l&&u&&d&&d.major,f=a.format(t,i||(c?u:s)),g=c?h.major:h.minor,m=Kn([g.callback,g.userCallback,h.callback,h.userCallback]);return m?m(f,e,n):f},convertTicksToLabels:function(t){var e,n,i=[];for(e=0,n=t.length;e<n;++e)i.push(this.tickFormatFunction(t[e].value,e,t));return i},getPixelForOffset:function(t){var e=this._offsets,n=oi(this._table,"time",t,"pos");return this.getPixelForDecimal((e.start+n)*e.factor)},getPixelForValue:function(t,e,n){var i=null;if(void 0!==e&&void 0!==n&&(i=this._timestamps.datasets[n][e]),null===i&&(i=li(this,t)),null!==i)return this.getPixelForOffset(i)},getPixelForTick:function(t){var e=this.getTicks();return t>=0&&t<e.length?this.getPixelForOffset(e[t].value):null},getValueForPixel:function(t){var e=this._offsets,n=this.getDecimalForPixel(t)/e.factor-e.end,i=oi(this._table,"pos",n,"time");return this._adapter._create(i)},_getLabelSize:function(t){var e=this.options.ticks,n=this.ctx.measureText(t).width,i=H.toRadians(this.isHorizontal()?e.maxRotation:e.minRotation),a=Math.cos(i),r=Math.sin(i),o=Jn(e.fontSize,W.global.defaultFontSize);return{w:n*a+o*r,h:n*r+o*a}},getLabelWidth:function(t){return this._getLabelSize(t).w},getLabelCapacity:function(t){var e=this,n=e.options.time,i=n.displayFormats,a=i[n.unit]||i.millisecond,r=e.tickFormatFunction(t,0,di(e,[t],e._majorUnit),a),o=e._getLabelSize(r),s=Math.floor(e.isHorizontal()?e.width/o.w:e.height/o.h);return e.options.offset&&s--,s>0?s:1}}),ci={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};hi._defaults=ci;var fi={category:wn,linear:On,logarithmic:Nn,radialLinear:Zn,time:hi},gi=e((function(e,n){e.exports=function(){var n,i;function a(){return n.apply(null,arguments)}function r(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function o(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function l(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function u(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function d(t,e){var n,i=[];for(n=0;n<t.length;++n)i.push(e(t[n],n));return i}function h(t,e){return Object.prototype.hasOwnProperty.call(t,e)}function c(t,e){for(var n in e)h(e,n)&&(t[n]=e[n]);return h(e,"toString")&&(t.toString=e.toString),h(e,"valueOf")&&(t.valueOf=e.valueOf),t}function f(t,e,n,i){return Ie(t,e,n,i,!0).utc()}function g(t){return null==t._pf&&(t._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}),t._pf}function m(t){if(null==t._isValid){var e=g(t),n=i.call(e.parsedDateParts,(function(t){return null!=t})),a=!isNaN(t._d.getTime())&&e.overflow<0&&!e.empty&&!e.invalidMonth&&!e.invalidWeekday&&!e.weekdayMismatch&&!e.nullInput&&!e.invalidFormat&&!e.userInvalidated&&(!e.meridiem||e.meridiem&&n);if(t._strict&&(a=a&&0===e.charsLeftOver&&0===e.unusedTokens.length&&void 0===e.bigHour),null!=Object.isFrozen&&Object.isFrozen(t))return a;t._isValid=a}return t._isValid}function p(t){var e=f(NaN);return null!=t?c(g(e),t):g(e).userInvalidated=!0,e}i=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),n=e.length>>>0,i=0;i<n;i++)if(i in e&&t.call(this,e[i],i,e))return!0;return!1};var v=a.momentProperties=[];function b(t,e){var n,i,a;if(s(e._isAMomentObject)||(t._isAMomentObject=e._isAMomentObject),s(e._i)||(t._i=e._i),s(e._f)||(t._f=e._f),s(e._l)||(t._l=e._l),s(e._strict)||(t._strict=e._strict),s(e._tzm)||(t._tzm=e._tzm),s(e._isUTC)||(t._isUTC=e._isUTC),s(e._offset)||(t._offset=e._offset),s(e._pf)||(t._pf=g(e)),s(e._locale)||(t._locale=e._locale),v.length>0)for(n=0;n<v.length;n++)s(a=e[i=v[n]])||(t[i]=a);return t}var y=!1;function x(t){b(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===y&&(y=!0,a.updateOffset(this),y=!1)}function _(t){return t instanceof x||null!=t&&null!=t._isAMomentObject}function w(t){return t<0?Math.ceil(t)||0:Math.floor(t)}function k(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=w(e)),n}function M(t,e,n){var i,a=Math.min(t.length,e.length),r=Math.abs(t.length-e.length),o=0;for(i=0;i<a;i++)(n&&t[i]!==e[i]||!n&&k(t[i])!==k(e[i]))&&o++;return o+r}function S(t){!1===a.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function D(t,e){var n=!0;return c((function(){if(null!=a.deprecationHandler&&a.deprecationHandler(null,t),n){for(var i,r=[],o=0;o<arguments.length;o++){if(i="","object"==typeof arguments[o]){for(var s in i+="\n["+o+"] ",arguments[0])i+=s+": "+arguments[0][s]+", ";i=i.slice(0,-2)}else i=arguments[o];r.push(i)}S(t+"\nArguments: "+Array.prototype.slice.call(r).join("")+"\n"+(new Error).stack),n=!1}return e.apply(this,arguments)}),e)}var C,P={};function T(t,e){null!=a.deprecationHandler&&a.deprecationHandler(t,e),P[t]||(S(e),P[t]=!0)}function O(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function A(t,e){var n,i=c({},t);for(n in e)h(e,n)&&(o(t[n])&&o(e[n])?(i[n]={},c(i[n],t[n]),c(i[n],e[n])):null!=e[n]?i[n]=e[n]:delete i[n]);for(n in t)h(t,n)&&!h(e,n)&&o(t[n])&&(i[n]=c({},i[n]));return i}function F(t){null!=t&&this.set(t)}a.suppressDeprecationWarnings=!1,a.deprecationHandler=null,C=Object.keys?Object.keys:function(t){var e,n=[];for(e in t)h(t,e)&&n.push(e);return n};var I={};function L(t,e){var n=t.toLowerCase();I[n]=I[n+"s"]=I[e]=t}function R(t){return"string"==typeof t?I[t]||I[t.toLowerCase()]:void 0}function N(t){var e,n,i={};for(n in t)h(t,n)&&(e=R(n))&&(i[e]=t[n]);return i}var W={};function Y(t,e){W[t]=e}function z(t,e,n){var i=""+Math.abs(t),a=e-i.length;return(t>=0?n?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+i}var E=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,V=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,H={},B={};function j(t,e,n,i){var a=i;"string"==typeof i&&(a=function(){return this[i]()}),t&&(B[t]=a),e&&(B[e[0]]=function(){return z(a.apply(this,arguments),e[1],e[2])}),n&&(B[n]=function(){return this.localeData().ordinal(a.apply(this,arguments),t)})}function U(t,e){return t.isValid()?(e=G(e,t.localeData()),H[e]=H[e]||function(t){var e,n,i,a=t.match(E);for(e=0,n=a.length;e<n;e++)B[a[e]]?a[e]=B[a[e]]:a[e]=(i=a[e]).match(/\[[\s\S]/)?i.replace(/^\[|\]$/g,""):i.replace(/\\/g,"");return function(e){var i,r="";for(i=0;i<n;i++)r+=O(a[i])?a[i].call(e,t):a[i];return r}}(e),H[e](t)):t.localeData().invalidDate()}function G(t,e){var n=5;function i(t){return e.longDateFormat(t)||t}for(V.lastIndex=0;n>=0&&V.test(t);)t=t.replace(V,i),V.lastIndex=0,n-=1;return t}var q=/\d/,Z=/\d\d/,$=/\d{3}/,X=/\d{4}/,K=/[+-]?\d{6}/,J=/\d\d?/,Q=/\d\d\d\d?/,tt=/\d\d\d\d\d\d?/,et=/\d{1,3}/,nt=/\d{1,4}/,it=/[+-]?\d{1,6}/,at=/\d+/,rt=/[+-]?\d+/,ot=/Z|[+-]\d\d:?\d\d/gi,st=/Z|[+-]\d\d(?::?\d\d)?/gi,lt=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,ut={};function dt(t,e,n){ut[t]=O(e)?e:function(t,i){return t&&n?n:e}}function ht(t,e){return h(ut,t)?ut[t](e._strict,e._locale):new RegExp(ct(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,(function(t,e,n,i,a){return e||n||i||a}))))}function ct(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var ft={};function gt(t,e){var n,i=e;for("string"==typeof t&&(t=[t]),l(e)&&(i=function(t,n){n[e]=k(t)}),n=0;n<t.length;n++)ft[t[n]]=i}function mt(t,e){gt(t,(function(t,n,i,a){i._w=i._w||{},e(t,i._w,i,a)}))}function pt(t,e,n){null!=e&&h(ft,t)&&ft[t](e,n._a,n,t)}var vt=0,bt=1,yt=2,xt=3,_t=4,wt=5,kt=6,Mt=7,St=8;function Dt(t){return Ct(t)?366:365}function Ct(t){return t%4==0&&t%100!=0||t%400==0}j("Y",0,0,(function(){var t=this.year();return t<=9999?""+t:"+"+t})),j(0,["YY",2],0,(function(){return this.year()%100})),j(0,["YYYY",4],0,"year"),j(0,["YYYYY",5],0,"year"),j(0,["YYYYYY",6,!0],0,"year"),L("year","y"),Y("year",1),dt("Y",rt),dt("YY",J,Z),dt("YYYY",nt,X),dt("YYYYY",it,K),dt("YYYYYY",it,K),gt(["YYYYY","YYYYYY"],vt),gt("YYYY",(function(t,e){e[vt]=2===t.length?a.parseTwoDigitYear(t):k(t)})),gt("YY",(function(t,e){e[vt]=a.parseTwoDigitYear(t)})),gt("Y",(function(t,e){e[vt]=parseInt(t,10)})),a.parseTwoDigitYear=function(t){return k(t)+(k(t)>68?1900:2e3)};var Pt,Tt=Ot("FullYear",!0);function Ot(t,e){return function(n){return null!=n?(Ft(this,t,n),a.updateOffset(this,e),this):At(this,t)}}function At(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function Ft(t,e,n){t.isValid()&&!isNaN(n)&&("FullYear"===e&&Ct(t.year())&&1===t.month()&&29===t.date()?t._d["set"+(t._isUTC?"UTC":"")+e](n,t.month(),It(n,t.month())):t._d["set"+(t._isUTC?"UTC":"")+e](n))}function It(t,e){if(isNaN(t)||isNaN(e))return NaN;var n=function(t,e){return(t%e+e)%e}(e,12);return t+=(e-n)/12,1===n?Ct(t)?29:28:31-n%7%2}Pt=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;e<this.length;++e)if(this[e]===t)return e;return-1},j("M",["MM",2],"Mo",(function(){return this.month()+1})),j("MMM",0,0,(function(t){return this.localeData().monthsShort(this,t)})),j("MMMM",0,0,(function(t){return this.localeData().months(this,t)})),L("month","M"),Y("month",8),dt("M",J),dt("MM",J,Z),dt("MMM",(function(t,e){return e.monthsShortRegex(t)})),dt("MMMM",(function(t,e){return e.monthsRegex(t)})),gt(["M","MM"],(function(t,e){e[bt]=k(t)-1})),gt(["MMM","MMMM"],(function(t,e,n,i){var a=n._locale.monthsParse(t,i,n._strict);null!=a?e[bt]=a:g(n).invalidMonth=t}));var Lt=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,Rt="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Nt="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_");function Wt(t,e,n){var i,a,r,o=t.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],i=0;i<12;++i)r=f([2e3,i]),this._shortMonthsParse[i]=this.monthsShort(r,"").toLocaleLowerCase(),this._longMonthsParse[i]=this.months(r,"").toLocaleLowerCase();return n?"MMM"===e?-1!==(a=Pt.call(this._shortMonthsParse,o))?a:null:-1!==(a=Pt.call(this._longMonthsParse,o))?a:null:"MMM"===e?-1!==(a=Pt.call(this._shortMonthsParse,o))?a:-1!==(a=Pt.call(this._longMonthsParse,o))?a:null:-1!==(a=Pt.call(this._longMonthsParse,o))?a:-1!==(a=Pt.call(this._shortMonthsParse,o))?a:null}function Yt(t,e){var n;if(!t.isValid())return t;if("string"==typeof e)if(/^\d+$/.test(e))e=k(e);else if(!l(e=t.localeData().monthsParse(e)))return t;return n=Math.min(t.date(),It(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t}function zt(t){return null!=t?(Yt(this,t),a.updateOffset(this,!0),this):At(this,"Month")}var Et=lt,Vt=lt;function Ht(){function t(t,e){return e.length-t.length}var e,n,i=[],a=[],r=[];for(e=0;e<12;e++)n=f([2e3,e]),i.push(this.monthsShort(n,"")),a.push(this.months(n,"")),r.push(this.months(n,"")),r.push(this.monthsShort(n,""));for(i.sort(t),a.sort(t),r.sort(t),e=0;e<12;e++)i[e]=ct(i[e]),a[e]=ct(a[e]);for(e=0;e<24;e++)r[e]=ct(r[e]);this._monthsRegex=new RegExp("^("+r.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+i.join("|")+")","i")}function Bt(t,e,n,i,a,r,o){var s;return t<100&&t>=0?(s=new Date(t+400,e,n,i,a,r,o),isFinite(s.getFullYear())&&s.setFullYear(t)):s=new Date(t,e,n,i,a,r,o),s}function jt(t){var e;if(t<100&&t>=0){var n=Array.prototype.slice.call(arguments);n[0]=t+400,e=new Date(Date.UTC.apply(null,n)),isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t)}else e=new Date(Date.UTC.apply(null,arguments));return e}function Ut(t,e,n){var i=7+e-n;return-(7+jt(t,0,i).getUTCDay()-e)%7+i-1}function Gt(t,e,n,i,a){var r,o,s=1+7*(e-1)+(7+n-i)%7+Ut(t,i,a);return s<=0?o=Dt(r=t-1)+s:s>Dt(t)?(r=t+1,o=s-Dt(t)):(r=t,o=s),{year:r,dayOfYear:o}}function qt(t,e,n){var i,a,r=Ut(t.year(),e,n),o=Math.floor((t.dayOfYear()-r-1)/7)+1;return o<1?i=o+Zt(a=t.year()-1,e,n):o>Zt(t.year(),e,n)?(i=o-Zt(t.year(),e,n),a=t.year()+1):(a=t.year(),i=o),{week:i,year:a}}function Zt(t,e,n){var i=Ut(t,e,n),a=Ut(t+1,e,n);return(Dt(t)-i+a)/7}function $t(t,e){return t.slice(e,7).concat(t.slice(0,e))}j("w",["ww",2],"wo","week"),j("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),Y("week",5),Y("isoWeek",5),dt("w",J),dt("ww",J,Z),dt("W",J),dt("WW",J,Z),mt(["w","ww","W","WW"],(function(t,e,n,i){e[i.substr(0,1)]=k(t)})),j("d",0,"do","day"),j("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),j("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),j("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),j("e",0,0,"weekday"),j("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),Y("day",11),Y("weekday",11),Y("isoWeekday",11),dt("d",J),dt("e",J),dt("E",J),dt("dd",(function(t,e){return e.weekdaysMinRegex(t)})),dt("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),dt("dddd",(function(t,e){return e.weekdaysRegex(t)})),mt(["dd","ddd","dddd"],(function(t,e,n,i){var a=n._locale.weekdaysParse(t,i,n._strict);null!=a?e.d=a:g(n).invalidWeekday=t})),mt(["d","e","E"],(function(t,e,n,i){e[i]=k(t)}));var Xt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Kt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Jt="Su_Mo_Tu_We_Th_Fr_Sa".split("_");function Qt(t,e,n){var i,a,r,o=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],i=0;i<7;++i)r=f([2e3,1]).day(i),this._minWeekdaysParse[i]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[i]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[i]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===e?-1!==(a=Pt.call(this._weekdaysParse,o))?a:null:"ddd"===e?-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:null:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:"dddd"===e?-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:"ddd"===e?-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:null}var te=lt,ee=lt,ne=lt;function ie(){function t(t,e){return e.length-t.length}var e,n,i,a,r,o=[],s=[],l=[],u=[];for(e=0;e<7;e++)n=f([2e3,1]).day(e),i=this.weekdaysMin(n,""),a=this.weekdaysShort(n,""),r=this.weekdays(n,""),o.push(i),s.push(a),l.push(r),u.push(i),u.push(a),u.push(r);for(o.sort(t),s.sort(t),l.sort(t),u.sort(t),e=0;e<7;e++)s[e]=ct(s[e]),l[e]=ct(l[e]),u[e]=ct(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function ae(){return this.hours()%12||12}function re(t,e){j(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function oe(t,e){return e._meridiemParse}j("H",["HH",2],0,"hour"),j("h",["hh",2],0,ae),j("k",["kk",2],0,(function(){return this.hours()||24})),j("hmm",0,0,(function(){return""+ae.apply(this)+z(this.minutes(),2)})),j("hmmss",0,0,(function(){return""+ae.apply(this)+z(this.minutes(),2)+z(this.seconds(),2)})),j("Hmm",0,0,(function(){return""+this.hours()+z(this.minutes(),2)})),j("Hmmss",0,0,(function(){return""+this.hours()+z(this.minutes(),2)+z(this.seconds(),2)})),re("a",!0),re("A",!1),L("hour","h"),Y("hour",13),dt("a",oe),dt("A",oe),dt("H",J),dt("h",J),dt("k",J),dt("HH",J,Z),dt("hh",J,Z),dt("kk",J,Z),dt("hmm",Q),dt("hmmss",tt),dt("Hmm",Q),dt("Hmmss",tt),gt(["H","HH"],xt),gt(["k","kk"],(function(t,e,n){var i=k(t);e[xt]=24===i?0:i})),gt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),gt(["h","hh"],(function(t,e,n){e[xt]=k(t),g(n).bigHour=!0})),gt("hmm",(function(t,e,n){var i=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i)),g(n).bigHour=!0})),gt("hmmss",(function(t,e,n){var i=t.length-4,a=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i,2)),e[wt]=k(t.substr(a)),g(n).bigHour=!0})),gt("Hmm",(function(t,e,n){var i=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i))})),gt("Hmmss",(function(t,e,n){var i=t.length-4,a=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i,2)),e[wt]=k(t.substr(a))}));var se,le=Ot("Hours",!0),ue={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Rt,monthsShort:Nt,week:{dow:0,doy:6},weekdays:Xt,weekdaysMin:Jt,weekdaysShort:Kt,meridiemParse:/[ap]\.?m?\.?/i},de={},he={};function ce(t){return t?t.toLowerCase().replace("_","-"):t}function fe(n){var i=null;if(!de[n]&&e&&e.exports)try{i=se._abbr,t(),ge(i)}catch(t){}return de[n]}function ge(t,e){var n;return t&&((n=s(e)?pe(t):me(t,e))?se=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),se._abbr}function me(t,e){if(null!==e){var n,i=ue;if(e.abbr=t,null!=de[t])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),i=de[t]._config;else if(null!=e.parentLocale)if(null!=de[e.parentLocale])i=de[e.parentLocale]._config;else{if(null==(n=fe(e.parentLocale)))return he[e.parentLocale]||(he[e.parentLocale]=[]),he[e.parentLocale].push({name:t,config:e}),null;i=n._config}return de[t]=new F(A(i,e)),he[t]&&he[t].forEach((function(t){me(t.name,t.config)})),ge(t),de[t]}return delete de[t],null}function pe(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return se;if(!r(t)){if(e=fe(t))return e;t=[t]}return function(t){for(var e,n,i,a,r=0;r<t.length;){for(e=(a=ce(t[r]).split("-")).length,n=(n=ce(t[r+1]))?n.split("-"):null;e>0;){if(i=fe(a.slice(0,e).join("-")))return i;if(n&&n.length>=e&&M(a,n,!0)>=e-1)break;e--}r++}return se}(t)}function ve(t){var e,n=t._a;return n&&-2===g(t).overflow&&(e=n[bt]<0||n[bt]>11?bt:n[yt]<1||n[yt]>It(n[vt],n[bt])?yt:n[xt]<0||n[xt]>24||24===n[xt]&&(0!==n[_t]||0!==n[wt]||0!==n[kt])?xt:n[_t]<0||n[_t]>59?_t:n[wt]<0||n[wt]>59?wt:n[kt]<0||n[kt]>999?kt:-1,g(t)._overflowDayOfYear&&(e<vt||e>yt)&&(e=yt),g(t)._overflowWeeks&&-1===e&&(e=Mt),g(t)._overflowWeekday&&-1===e&&(e=St),g(t).overflow=e),t}function be(t,e,n){return null!=t?t:null!=e?e:n}function ye(t){var e,n,i,r,o,s=[];if(!t._d){for(i=function(t){var e=new Date(a.now());return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}(t),t._w&&null==t._a[yt]&&null==t._a[bt]&&function(t){var e,n,i,a,r,o,s,l;if(null!=(e=t._w).GG||null!=e.W||null!=e.E)r=1,o=4,n=be(e.GG,t._a[vt],qt(Le(),1,4).year),i=be(e.W,1),((a=be(e.E,1))<1||a>7)&&(l=!0);else{r=t._locale._week.dow,o=t._locale._week.doy;var u=qt(Le(),r,o);n=be(e.gg,t._a[vt],u.year),i=be(e.w,u.week),null!=e.d?((a=e.d)<0||a>6)&&(l=!0):null!=e.e?(a=e.e+r,(e.e<0||e.e>6)&&(l=!0)):a=r}i<1||i>Zt(n,r,o)?g(t)._overflowWeeks=!0:null!=l?g(t)._overflowWeekday=!0:(s=Gt(n,i,a,r,o),t._a[vt]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(o=be(t._a[vt],i[vt]),(t._dayOfYear>Dt(o)||0===t._dayOfYear)&&(g(t)._overflowDayOfYear=!0),n=jt(o,0,t._dayOfYear),t._a[bt]=n.getUTCMonth(),t._a[yt]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=i[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[xt]&&0===t._a[_t]&&0===t._a[wt]&&0===t._a[kt]&&(t._nextDay=!0,t._a[xt]=0),t._d=(t._useUTC?jt:Bt).apply(null,s),r=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[xt]=24),t._w&&void 0!==t._w.d&&t._w.d!==r&&(g(t).weekdayMismatch=!0)}}var xe=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_e=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,we=/Z|[+-]\d\d(?::?\d\d)?/,ke=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Me=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Se=/^\/?Date\((\-?\d+)/i;function De(t){var e,n,i,a,r,o,s=t._i,l=xe.exec(s)||_e.exec(s);if(l){for(g(t).iso=!0,e=0,n=ke.length;e<n;e++)if(ke[e][1].exec(l[1])){a=ke[e][0],i=!1!==ke[e][2];break}if(null==a)return void(t._isValid=!1);if(l[3]){for(e=0,n=Me.length;e<n;e++)if(Me[e][1].exec(l[3])){r=(l[2]||" ")+Me[e][0];break}if(null==r)return void(t._isValid=!1)}if(!i&&null!=r)return void(t._isValid=!1);if(l[4]){if(!we.exec(l[4]))return void(t._isValid=!1);o="Z"}t._f=a+(r||"")+(o||""),Ae(t)}else t._isValid=!1}var Ce=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;function Pe(t){var e=parseInt(t,10);return e<=49?2e3+e:e<=999?1900+e:e}var Te={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function Oe(t){var e,n,i,a,r,o,s,l=Ce.exec(t._i.replace(/\([^)]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").replace(/^\s\s*/,"").replace(/\s\s*$/,""));if(l){var u=(e=l[4],n=l[3],i=l[2],a=l[5],r=l[6],o=l[7],s=[Pe(e),Nt.indexOf(n),parseInt(i,10),parseInt(a,10),parseInt(r,10)],o&&s.push(parseInt(o,10)),s);if(!function(t,e,n){return!t||Kt.indexOf(t)===new Date(e[0],e[1],e[2]).getDay()||(g(n).weekdayMismatch=!0,n._isValid=!1,!1)}(l[1],u,t))return;t._a=u,t._tzm=function(t,e,n){if(t)return Te[t];if(e)return 0;var i=parseInt(n,10),a=i%100;return(i-a)/100*60+a}(l[8],l[9],l[10]),t._d=jt.apply(null,t._a),t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),g(t).rfc2822=!0}else t._isValid=!1}function Ae(t){if(t._f!==a.ISO_8601)if(t._f!==a.RFC_2822){t._a=[],g(t).empty=!0;var e,n,i,r,o,s=""+t._i,l=s.length,u=0;for(i=G(t._f,t._locale).match(E)||[],e=0;e<i.length;e++)r=i[e],(n=(s.match(ht(r,t))||[])[0])&&((o=s.substr(0,s.indexOf(n))).length>0&&g(t).unusedInput.push(o),s=s.slice(s.indexOf(n)+n.length),u+=n.length),B[r]?(n?g(t).empty=!1:g(t).unusedTokens.push(r),pt(r,n,t)):t._strict&&!n&&g(t).unusedTokens.push(r);g(t).charsLeftOver=l-u,s.length>0&&g(t).unusedInput.push(s),t._a[xt]<=12&&!0===g(t).bigHour&&t._a[xt]>0&&(g(t).bigHour=void 0),g(t).parsedDateParts=t._a.slice(0),g(t).meridiem=t._meridiem,t._a[xt]=function(t,e,n){var i;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?((i=t.isPM(n))&&e<12&&(e+=12),i||12!==e||(e=0),e):e}(t._locale,t._a[xt],t._meridiem),ye(t),ve(t)}else Oe(t);else De(t)}function Fe(t){var e=t._i,n=t._f;return t._locale=t._locale||pe(t._l),null===e||void 0===n&&""===e?p({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),_(e)?new x(ve(e)):(u(e)?t._d=e:r(n)?function(t){var e,n,i,a,r;if(0===t._f.length)return g(t).invalidFormat=!0,void(t._d=new Date(NaN));for(a=0;a<t._f.length;a++)r=0,e=b({},t),null!=t._useUTC&&(e._useUTC=t._useUTC),e._f=t._f[a],Ae(e),m(e)&&(r+=g(e).charsLeftOver,r+=10*g(e).unusedTokens.length,g(e).score=r,(null==i||r<i)&&(i=r,n=e));c(t,n||e)}(t):n?Ae(t):function(t){var e=t._i;s(e)?t._d=new Date(a.now()):u(e)?t._d=new Date(e.valueOf()):"string"==typeof e?function(t){var e=Se.exec(t._i);null===e?(De(t),!1===t._isValid&&(delete t._isValid,Oe(t),!1===t._isValid&&(delete t._isValid,a.createFromInputFallback(t)))):t._d=new Date(+e[1])}(t):r(e)?(t._a=d(e.slice(0),(function(t){return parseInt(t,10)})),ye(t)):o(e)?function(t){if(!t._d){var e=N(t._i);t._a=d([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],(function(t){return t&&parseInt(t,10)})),ye(t)}}(t):l(e)?t._d=new Date(e):a.createFromInputFallback(t)}(t),m(t)||(t._d=null),t))}function Ie(t,e,n,i,a){var s,l={};return!0!==n&&!1!==n||(i=n,n=void 0),(o(t)&&function(t){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(t).length;var e;for(e in t)if(t.hasOwnProperty(e))return!1;return!0}(t)||r(t)&&0===t.length)&&(t=void 0),l._isAMomentObject=!0,l._useUTC=l._isUTC=a,l._l=n,l._i=t,l._f=e,l._strict=i,(s=new x(ve(Fe(l))))._nextDay&&(s.add(1,"d"),s._nextDay=void 0),s}function Le(t,e,n,i){return Ie(t,e,n,i,!1)}a.createFromInputFallback=D("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",(function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))})),a.ISO_8601=function(){},a.RFC_2822=function(){};var Re=D("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",(function(){var t=Le.apply(null,arguments);return this.isValid()&&t.isValid()?t<this?this:t:p()})),Ne=D("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",(function(){var t=Le.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:p()}));function We(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Le();for(n=e[0],i=1;i<e.length;++i)e[i].isValid()&&!e[i][t](n)||(n=e[i]);return n}var Ye=["year","quarter","month","week","day","hour","minute","second","millisecond"];function ze(t){var e=N(t),n=e.year||0,i=e.quarter||0,a=e.month||0,r=e.week||e.isoWeek||0,o=e.day||0,s=e.hour||0,l=e.minute||0,u=e.second||0,d=e.millisecond||0;this._isValid=function(t){for(var e in t)if(-1===Pt.call(Ye,e)||null!=t[e]&&isNaN(t[e]))return!1;for(var n=!1,i=0;i<Ye.length;++i)if(t[Ye[i]]){if(n)return!1;parseFloat(t[Ye[i]])!==k(t[Ye[i]])&&(n=!0)}return!0}(e),this._milliseconds=+d+1e3*u+6e4*l+1e3*s*60*60,this._days=+o+7*r,this._months=+a+3*i+12*n,this._data={},this._locale=pe(),this._bubble()}function Ee(t){return t instanceof ze}function Ve(t){return t<0?-1*Math.round(-1*t):Math.round(t)}function He(t,e){j(t,0,0,(function(){var t=this.utcOffset(),n="+";return t<0&&(t=-t,n="-"),n+z(~~(t/60),2)+e+z(~~t%60,2)}))}He("Z",":"),He("ZZ",""),dt("Z",st),dt("ZZ",st),gt(["Z","ZZ"],(function(t,e,n){n._useUTC=!0,n._tzm=je(st,t)}));var Be=/([\+\-]|\d\d)/gi;function je(t,e){var n=(e||"").match(t);if(null===n)return null;var i=((n[n.length-1]||[])+"").match(Be)||["-",0,0],a=60*i[1]+k(i[2]);return 0===a?0:"+"===i[0]?a:-a}function Ue(t,e){var n,i;return e._isUTC?(n=e.clone(),i=(_(t)||u(t)?t.valueOf():Le(t).valueOf())-n.valueOf(),n._d.setTime(n._d.valueOf()+i),a.updateOffset(n,!1),n):Le(t).local()}function Ge(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function qe(){return!!this.isValid()&&this._isUTC&&0===this._offset}a.updateOffset=function(){};var Ze=/^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,$e=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function Xe(t,e){var n,i,a,r,o,s,u=t,d=null;return Ee(t)?u={ms:t._milliseconds,d:t._days,M:t._months}:l(t)?(u={},e?u[e]=t:u.milliseconds=t):(d=Ze.exec(t))?(n="-"===d[1]?-1:1,u={y:0,d:k(d[yt])*n,h:k(d[xt])*n,m:k(d[_t])*n,s:k(d[wt])*n,ms:k(Ve(1e3*d[kt]))*n}):(d=$e.exec(t))?(n="-"===d[1]?-1:1,u={y:Ke(d[2],n),M:Ke(d[3],n),w:Ke(d[4],n),d:Ke(d[5],n),h:Ke(d[6],n),m:Ke(d[7],n),s:Ke(d[8],n)}):null==u?u={}:"object"==typeof u&&("from"in u||"to"in u)&&(r=Le(u.from),o=Le(u.to),a=r.isValid()&&o.isValid()?(o=Ue(o,r),r.isBefore(o)?s=Je(r,o):((s=Je(o,r)).milliseconds=-s.milliseconds,s.months=-s.months),s):{milliseconds:0,months:0},(u={}).ms=a.milliseconds,u.M=a.months),i=new ze(u),Ee(t)&&h(t,"_locale")&&(i._locale=t._locale),i}function Ke(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function Je(t,e){var n={};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function Qe(t,e){return function(n,i){var a;return null===i||isNaN(+i)||(T(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),a=n,n=i,i=a),tn(this,Xe(n="string"==typeof n?+n:n,i),t),this}}function tn(t,e,n,i){var r=e._milliseconds,o=Ve(e._days),s=Ve(e._months);t.isValid()&&(i=null==i||i,s&&Yt(t,At(t,"Month")+s*n),o&&Ft(t,"Date",At(t,"Date")+o*n),r&&t._d.setTime(t._d.valueOf()+r*n),i&&a.updateOffset(t,o||s))}Xe.fn=ze.prototype,Xe.invalid=function(){return Xe(NaN)};var en=Qe(1,"add"),nn=Qe(-1,"subtract");function an(t,e){var n=12*(e.year()-t.year())+(e.month()-t.month()),i=t.clone().add(n,"months");return-(n+(e-i<0?(e-i)/(i-t.clone().add(n-1,"months")):(e-i)/(t.clone().add(n+1,"months")-i)))||0}function rn(t){var e;return void 0===t?this._locale._abbr:(null!=(e=pe(t))&&(this._locale=e),this)}a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var on=D("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",(function(t){return void 0===t?this.localeData():this.locale(t)}));function sn(){return this._locale}var ln=1e3,un=60*ln,dn=60*un,hn=3506328*dn;function cn(t,e){return(t%e+e)%e}function fn(t,e,n){return t<100&&t>=0?new Date(t+400,e,n)-hn:new Date(t,e,n).valueOf()}function gn(t,e,n){return t<100&&t>=0?Date.UTC(t+400,e,n)-hn:Date.UTC(t,e,n)}function mn(t,e){j(0,[t,t.length],0,e)}function pn(t,e,n,i,a){var r;return null==t?qt(this,i,a).year:(e>(r=Zt(t,i,a))&&(e=r),vn.call(this,t,e,n,i,a))}function vn(t,e,n,i,a){var r=Gt(t,e,n,i,a),o=jt(r.year,0,r.dayOfYear);return this.year(o.getUTCFullYear()),this.month(o.getUTCMonth()),this.date(o.getUTCDate()),this}j(0,["gg",2],0,(function(){return this.weekYear()%100})),j(0,["GG",2],0,(function(){return this.isoWeekYear()%100})),mn("gggg","weekYear"),mn("ggggg","weekYear"),mn("GGGG","isoWeekYear"),mn("GGGGG","isoWeekYear"),L("weekYear","gg"),L("isoWeekYear","GG"),Y("weekYear",1),Y("isoWeekYear",1),dt("G",rt),dt("g",rt),dt("GG",J,Z),dt("gg",J,Z),dt("GGGG",nt,X),dt("gggg",nt,X),dt("GGGGG",it,K),dt("ggggg",it,K),mt(["gggg","ggggg","GGGG","GGGGG"],(function(t,e,n,i){e[i.substr(0,2)]=k(t)})),mt(["gg","GG"],(function(t,e,n,i){e[i]=a.parseTwoDigitYear(t)})),j("Q",0,"Qo","quarter"),L("quarter","Q"),Y("quarter",7),dt("Q",q),gt("Q",(function(t,e){e[bt]=3*(k(t)-1)})),j("D",["DD",2],"Do","date"),L("date","D"),Y("date",9),dt("D",J),dt("DD",J,Z),dt("Do",(function(t,e){return t?e._dayOfMonthOrdinalParse||e._ordinalParse:e._dayOfMonthOrdinalParseLenient})),gt(["D","DD"],yt),gt("Do",(function(t,e){e[yt]=k(t.match(J)[0])}));var bn=Ot("Date",!0);j("DDD",["DDDD",3],"DDDo","dayOfYear"),L("dayOfYear","DDD"),Y("dayOfYear",4),dt("DDD",et),dt("DDDD",$),gt(["DDD","DDDD"],(function(t,e,n){n._dayOfYear=k(t)})),j("m",["mm",2],0,"minute"),L("minute","m"),Y("minute",14),dt("m",J),dt("mm",J,Z),gt(["m","mm"],_t);var yn=Ot("Minutes",!1);j("s",["ss",2],0,"second"),L("second","s"),Y("second",15),dt("s",J),dt("ss",J,Z),gt(["s","ss"],wt);var xn,_n=Ot("Seconds",!1);for(j("S",0,0,(function(){return~~(this.millisecond()/100)})),j(0,["SS",2],0,(function(){return~~(this.millisecond()/10)})),j(0,["SSS",3],0,"millisecond"),j(0,["SSSS",4],0,(function(){return 10*this.millisecond()})),j(0,["SSSSS",5],0,(function(){return 100*this.millisecond()})),j(0,["SSSSSS",6],0,(function(){return 1e3*this.millisecond()})),j(0,["SSSSSSS",7],0,(function(){return 1e4*this.millisecond()})),j(0,["SSSSSSSS",8],0,(function(){return 1e5*this.millisecond()})),j(0,["SSSSSSSSS",9],0,(function(){return 1e6*this.millisecond()})),L("millisecond","ms"),Y("millisecond",16),dt("S",et,q),dt("SS",et,Z),dt("SSS",et,$),xn="SSSS";xn.length<=9;xn+="S")dt(xn,at);function wn(t,e){e[kt]=k(1e3*("0."+t))}for(xn="S";xn.length<=9;xn+="S")gt(xn,wn);var kn=Ot("Milliseconds",!1);j("z",0,0,"zoneAbbr"),j("zz",0,0,"zoneName");var Mn=x.prototype;function Sn(t){return t}Mn.add=en,Mn.calendar=function(t,e){var n=t||Le(),i=Ue(n,this).startOf("day"),r=a.calendarFormat(this,i)||"sameElse",o=e&&(O(e[r])?e[r].call(this,n):e[r]);return this.format(o||this.localeData().calendar(r,this,Le(n)))},Mn.clone=function(){return new x(this)},Mn.diff=function(t,e,n){var i,a,r;if(!this.isValid())return NaN;if(!(i=Ue(t,this)).isValid())return NaN;switch(a=6e4*(i.utcOffset()-this.utcOffset()),e=R(e)){case"year":r=an(this,i)/12;break;case"month":r=an(this,i);break;case"quarter":r=an(this,i)/3;break;case"second":r=(this-i)/1e3;break;case"minute":r=(this-i)/6e4;break;case"hour":r=(this-i)/36e5;break;case"day":r=(this-i-a)/864e5;break;case"week":r=(this-i-a)/6048e5;break;default:r=this-i}return n?r:w(r)},Mn.endOf=function(t){var e;if(void 0===(t=R(t))||"millisecond"===t||!this.isValid())return this;var n=this._isUTC?gn:fn;switch(t){case"year":e=n(this.year()+1,0,1)-1;break;case"quarter":e=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":e=n(this.year(),this.month()+1,1)-1;break;case"week":e=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":e=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":e=n(this.year(),this.month(),this.date()+1)-1;break;case"hour":e=this._d.valueOf(),e+=dn-cn(e+(this._isUTC?0:this.utcOffset()*un),dn)-1;break;case"minute":e=this._d.valueOf(),e+=un-cn(e,un)-1;break;case"second":e=this._d.valueOf(),e+=ln-cn(e,ln)-1}return this._d.setTime(e),a.updateOffset(this,!0),this},Mn.format=function(t){t||(t=this.isUtc()?a.defaultFormatUtc:a.defaultFormat);var e=U(this,t);return this.localeData().postformat(e)},Mn.from=function(t,e){return this.isValid()&&(_(t)&&t.isValid()||Le(t).isValid())?Xe({to:this,from:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()},Mn.fromNow=function(t){return this.from(Le(),t)},Mn.to=function(t,e){return this.isValid()&&(_(t)&&t.isValid()||Le(t).isValid())?Xe({from:this,to:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()},Mn.toNow=function(t){return this.to(Le(),t)},Mn.get=function(t){return O(this[t=R(t)])?this[t]():this},Mn.invalidAt=function(){return g(this).overflow},Mn.isAfter=function(t,e){var n=_(t)?t:Le(t);return!(!this.isValid()||!n.isValid())&&("millisecond"===(e=R(e)||"millisecond")?this.valueOf()>n.valueOf():n.valueOf()<this.clone().startOf(e).valueOf())},Mn.isBefore=function(t,e){var n=_(t)?t:Le(t);return!(!this.isValid()||!n.isValid())&&("millisecond"===(e=R(e)||"millisecond")?this.valueOf()<n.valueOf():this.clone().endOf(e).valueOf()<n.valueOf())},Mn.isBetween=function(t,e,n,i){var a=_(t)?t:Le(t),r=_(e)?e:Le(e);return!!(this.isValid()&&a.isValid()&&r.isValid())&&("("===(i=i||"()")[0]?this.isAfter(a,n):!this.isBefore(a,n))&&(")"===i[1]?this.isBefore(r,n):!this.isAfter(r,n))},Mn.isSame=function(t,e){var n,i=_(t)?t:Le(t);return!(!this.isValid()||!i.isValid())&&("millisecond"===(e=R(e)||"millisecond")?this.valueOf()===i.valueOf():(n=i.valueOf(),this.clone().startOf(e).valueOf()<=n&&n<=this.clone().endOf(e).valueOf()))},Mn.isSameOrAfter=function(t,e){return this.isSame(t,e)||this.isAfter(t,e)},Mn.isSameOrBefore=function(t,e){return this.isSame(t,e)||this.isBefore(t,e)},Mn.isValid=function(){return m(this)},Mn.lang=on,Mn.locale=rn,Mn.localeData=sn,Mn.max=Ne,Mn.min=Re,Mn.parsingFlags=function(){return c({},g(this))},Mn.set=function(t,e){if("object"==typeof t)for(var n=function(t){var e=[];for(var n in t)e.push({unit:n,priority:W[n]});return e.sort((function(t,e){return t.priority-e.priority})),e}(t=N(t)),i=0;i<n.length;i++)this[n[i].unit](t[n[i].unit]);else if(O(this[t=R(t)]))return this[t](e);return this},Mn.startOf=function(t){var e;if(void 0===(t=R(t))||"millisecond"===t||!this.isValid())return this;var n=this._isUTC?gn:fn;switch(t){case"year":e=n(this.year(),0,1);break;case"quarter":e=n(this.year(),this.month()-this.month()%3,1);break;case"month":e=n(this.year(),this.month(),1);break;case"week":e=n(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":e=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":e=n(this.year(),this.month(),this.date());break;case"hour":e=this._d.valueOf(),e-=cn(e+(this._isUTC?0:this.utcOffset()*un),dn);break;case"minute":e=this._d.valueOf(),e-=cn(e,un);break;case"second":e=this._d.valueOf(),e-=cn(e,ln)}return this._d.setTime(e),a.updateOffset(this,!0),this},Mn.subtract=nn,Mn.toArray=function(){var t=this;return[t.year(),t.month(),t.date(),t.hour(),t.minute(),t.second(),t.millisecond()]},Mn.toObject=function(){var t=this;return{years:t.year(),months:t.month(),date:t.date(),hours:t.hours(),minutes:t.minutes(),seconds:t.seconds(),milliseconds:t.milliseconds()}},Mn.toDate=function(){return new Date(this.valueOf())},Mn.toISOString=function(t){if(!this.isValid())return null;var e=!0!==t,n=e?this.clone().utc():this;return n.year()<0||n.year()>9999?U(n,e?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):O(Date.prototype.toISOString)?e?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",U(n,"Z")):U(n,e?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},Mn.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var t="moment",e="";this.isLocal()||(t=0===this.utcOffset()?"moment.utc":"moment.parseZone",e="Z");var n="["+t+'("]',i=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",a=e+'[")]';return this.format(n+i+"-MM-DD[T]HH:mm:ss.SSS"+a)},Mn.toJSON=function(){return this.isValid()?this.toISOString():null},Mn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},Mn.unix=function(){return Math.floor(this.valueOf()/1e3)},Mn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},Mn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},Mn.year=Tt,Mn.isLeapYear=function(){return Ct(this.year())},Mn.weekYear=function(t){return pn.call(this,t,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},Mn.isoWeekYear=function(t){return pn.call(this,t,this.isoWeek(),this.isoWeekday(),1,4)},Mn.quarter=Mn.quarters=function(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)},Mn.month=zt,Mn.daysInMonth=function(){return It(this.year(),this.month())},Mn.week=Mn.weeks=function(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")},Mn.isoWeek=Mn.isoWeeks=function(t){var e=qt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")},Mn.weeksInYear=function(){var t=this.localeData()._week;return Zt(this.year(),t.dow,t.doy)},Mn.isoWeeksInYear=function(){return Zt(this.year(),1,4)},Mn.date=bn,Mn.day=Mn.days=function(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=function(t,e){return"string"!=typeof t?t:isNaN(t)?"number"==typeof(t=e.weekdaysParse(t))?t:null:parseInt(t,10)}(t,this.localeData()),this.add(t-e,"d")):e},Mn.weekday=function(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")},Mn.isoWeekday=function(t){if(!this.isValid())return null!=t?this:NaN;if(null!=t){var e=function(t,e){return"string"==typeof t?e.weekdaysParse(t)%7||7:isNaN(t)?null:t}(t,this.localeData());return this.day(this.day()%7?e:e-7)}return this.day()||7},Mn.dayOfYear=function(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")},Mn.hour=Mn.hours=le,Mn.minute=Mn.minutes=yn,Mn.second=Mn.seconds=_n,Mn.millisecond=Mn.milliseconds=kn,Mn.utcOffset=function(t,e,n){var i,r=this._offset||0;if(!this.isValid())return null!=t?this:NaN;if(null!=t){if("string"==typeof t){if(null===(t=je(st,t)))return this}else Math.abs(t)<16&&!n&&(t*=60);return!this._isUTC&&e&&(i=Ge(this)),this._offset=t,this._isUTC=!0,null!=i&&this.add(i,"m"),r!==t&&(!e||this._changeInProgress?tn(this,Xe(t-r,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?r:Ge(this)},Mn.utc=function(t){return this.utcOffset(0,t)},Mn.local=function(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Ge(this),"m")),this},Mn.parseZone=function(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var t=je(ot,this._i);null!=t?this.utcOffset(t):this.utcOffset(0,!0)}return this},Mn.hasAlignedHourOffset=function(t){return!!this.isValid()&&(t=t?Le(t).utcOffset():0,(this.utcOffset()-t)%60==0)},Mn.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},Mn.isLocal=function(){return!!this.isValid()&&!this._isUTC},Mn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},Mn.isUtc=qe,Mn.isUTC=qe,Mn.zoneAbbr=function(){return this._isUTC?"UTC":""},Mn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},Mn.dates=D("dates accessor is deprecated. Use date instead.",bn),Mn.months=D("months accessor is deprecated. Use month instead",zt),Mn.years=D("years accessor is deprecated. Use year instead",Tt),Mn.zone=D("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),Mn.isDSTShifted=D("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(b(t,this),(t=Fe(t))._a){var e=t._isUTC?f(t._a):Le(t._a);this._isDSTShifted=this.isValid()&&M(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}));var Dn=F.prototype;function Cn(t,e,n,i){var a=pe(),r=f().set(i,e);return a[n](r,t)}function Pn(t,e,n){if(l(t)&&(e=t,t=void 0),t=t||"",null!=e)return Cn(t,e,n,"month");var i,a=[];for(i=0;i<12;i++)a[i]=Cn(t,i,n,"month");return a}function Tn(t,e,n,i){"boolean"==typeof t?(l(e)&&(n=e,e=void 0),e=e||""):(n=e=t,t=!1,l(e)&&(n=e,e=void 0),e=e||"");var a,r=pe(),o=t?r._week.dow:0;if(null!=n)return Cn(e,(n+o)%7,i,"day");var s=[];for(a=0;a<7;a++)s[a]=Cn(e,(a+o)%7,i,"day");return s}Dn.calendar=function(t,e,n){var i=this._calendar[t]||this._calendar.sameElse;return O(i)?i.call(e,n):i},Dn.longDateFormat=function(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,(function(t){return t.slice(1)})),this._longDateFormat[t])},Dn.invalidDate=function(){return this._invalidDate},Dn.ordinal=function(t){return this._ordinal.replace("%d",t)},Dn.preparse=Sn,Dn.postformat=Sn,Dn.relativeTime=function(t,e,n,i){var a=this._relativeTime[n];return O(a)?a(t,e,n,i):a.replace(/%d/i,t)},Dn.pastFuture=function(t,e){var n=this._relativeTime[t>0?"future":"past"];return O(n)?n(e):n.replace(/%s/i,e)},Dn.set=function(t){var e,n;for(n in t)O(e=t[n])?this[n]=e:this["_"+n]=e;this._config=t,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},Dn.months=function(t,e){return t?r(this._months)?this._months[t.month()]:this._months[(this._months.isFormat||Lt).test(e)?"format":"standalone"][t.month()]:r(this._months)?this._months:this._months.standalone},Dn.monthsShort=function(t,e){return t?r(this._monthsShort)?this._monthsShort[t.month()]:this._monthsShort[Lt.test(e)?"format":"standalone"][t.month()]:r(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},Dn.monthsParse=function(t,e,n){var i,a,r;if(this._monthsParseExact)return Wt.call(this,t,e,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),i=0;i<12;i++){if(a=f([2e3,i]),n&&!this._longMonthsParse[i]&&(this._longMonthsParse[i]=new RegExp("^"+this.months(a,"").replace(".","")+"$","i"),this._shortMonthsParse[i]=new RegExp("^"+this.monthsShort(a,"").replace(".","")+"$","i")),n||this._monthsParse[i]||(r="^"+this.months(a,"")+"|^"+this.monthsShort(a,""),this._monthsParse[i]=new RegExp(r.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[i].test(t))return i;if(n&&"MMM"===e&&this._shortMonthsParse[i].test(t))return i;if(!n&&this._monthsParse[i].test(t))return i}},Dn.monthsRegex=function(t){return this._monthsParseExact?(h(this,"_monthsRegex")||Ht.call(this),t?this._monthsStrictRegex:this._monthsRegex):(h(this,"_monthsRegex")||(this._monthsRegex=Vt),this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex)},Dn.monthsShortRegex=function(t){return this._monthsParseExact?(h(this,"_monthsRegex")||Ht.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):(h(this,"_monthsShortRegex")||(this._monthsShortRegex=Et),this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex)},Dn.week=function(t){return qt(t,this._week.dow,this._week.doy).week},Dn.firstDayOfYear=function(){return this._week.doy},Dn.firstDayOfWeek=function(){return this._week.dow},Dn.weekdays=function(t,e){var n=r(this._weekdays)?this._weekdays:this._weekdays[t&&!0!==t&&this._weekdays.isFormat.test(e)?"format":"standalone"];return!0===t?$t(n,this._week.dow):t?n[t.day()]:n},Dn.weekdaysMin=function(t){return!0===t?$t(this._weekdaysMin,this._week.dow):t?this._weekdaysMin[t.day()]:this._weekdaysMin},Dn.weekdaysShort=function(t){return!0===t?$t(this._weekdaysShort,this._week.dow):t?this._weekdaysShort[t.day()]:this._weekdaysShort},Dn.weekdaysParse=function(t,e,n){var i,a,r;if(this._weekdaysParseExact)return Qt.call(this,t,e,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),i=0;i<7;i++){if(a=f([2e3,1]).day(i),n&&!this._fullWeekdaysParse[i]&&(this._fullWeekdaysParse[i]=new RegExp("^"+this.weekdays(a,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[i]=new RegExp("^"+this.weekdaysShort(a,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[i]=new RegExp("^"+this.weekdaysMin(a,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[i]||(r="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,""),this._weekdaysParse[i]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[i].test(t))return i;if(n&&"ddd"===e&&this._shortWeekdaysParse[i].test(t))return i;if(n&&"dd"===e&&this._minWeekdaysParse[i].test(t))return i;if(!n&&this._weekdaysParse[i].test(t))return i}},Dn.weekdaysRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):(h(this,"_weekdaysRegex")||(this._weekdaysRegex=te),this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex)},Dn.weekdaysShortRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(h(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ee),this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},Dn.weekdaysMinRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(h(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=ne),this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},Dn.isPM=function(t){return"p"===(t+"").toLowerCase().charAt(0)},Dn.meridiem=function(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"},ge("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10;return t+(1===k(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th")}}),a.lang=D("moment.lang is deprecated. Use moment.locale instead.",ge),a.langData=D("moment.langData is deprecated. Use moment.localeData instead.",pe);var On=Math.abs;function An(t,e,n,i){var a=Xe(e,n);return t._milliseconds+=i*a._milliseconds,t._days+=i*a._days,t._months+=i*a._months,t._bubble()}function Fn(t){return t<0?Math.floor(t):Math.ceil(t)}function In(t){return 4800*t/146097}function Ln(t){return 146097*t/4800}function Rn(t){return function(){return this.as(t)}}var Nn=Rn("ms"),Wn=Rn("s"),Yn=Rn("m"),zn=Rn("h"),En=Rn("d"),Vn=Rn("w"),Hn=Rn("M"),Bn=Rn("Q"),jn=Rn("y");function Un(t){return function(){return this.isValid()?this._data[t]:NaN}}var Gn=Un("milliseconds"),qn=Un("seconds"),Zn=Un("minutes"),$n=Un("hours"),Xn=Un("days"),Kn=Un("months"),Jn=Un("years"),Qn=Math.round,ti={ss:44,s:45,m:45,h:22,d:26,M:11};function ei(t,e,n,i,a){return a.relativeTime(e||1,!!n,t,i)}var ni=Math.abs;function ii(t){return(t>0)-(t<0)||+t}function ai(){if(!this.isValid())return this.localeData().invalidDate();var t,e,n=ni(this._milliseconds)/1e3,i=ni(this._days),a=ni(this._months);t=w(n/60),e=w(t/60),n%=60,t%=60;var r=w(a/12),o=a%=12,s=i,l=e,u=t,d=n?n.toFixed(3).replace(/\.?0+$/,""):"",h=this.asSeconds();if(!h)return"P0D";var c=h<0?"-":"",f=ii(this._months)!==ii(h)?"-":"",g=ii(this._days)!==ii(h)?"-":"",m=ii(this._milliseconds)!==ii(h)?"-":"";return c+"P"+(r?f+r+"Y":"")+(o?f+o+"M":"")+(s?g+s+"D":"")+(l||u||d?"T":"")+(l?m+l+"H":"")+(u?m+u+"M":"")+(d?m+d+"S":"")}var ri=ze.prototype;return ri.isValid=function(){return this._isValid},ri.abs=function(){var t=this._data;return this._milliseconds=On(this._milliseconds),this._days=On(this._days),this._months=On(this._months),t.milliseconds=On(t.milliseconds),t.seconds=On(t.seconds),t.minutes=On(t.minutes),t.hours=On(t.hours),t.months=On(t.months),t.years=On(t.years),this},ri.add=function(t,e){return An(this,t,e,1)},ri.subtract=function(t,e){return An(this,t,e,-1)},ri.as=function(t){if(!this.isValid())return NaN;var e,n,i=this._milliseconds;if("month"===(t=R(t))||"quarter"===t||"year"===t)switch(e=this._days+i/864e5,n=this._months+In(e),t){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(e=this._days+Math.round(Ln(this._months)),t){case"week":return e/7+i/6048e5;case"day":return e+i/864e5;case"hour":return 24*e+i/36e5;case"minute":return 1440*e+i/6e4;case"second":return 86400*e+i/1e3;case"millisecond":return Math.floor(864e5*e)+i;default:throw new Error("Unknown unit "+t)}},ri.asMilliseconds=Nn,ri.asSeconds=Wn,ri.asMinutes=Yn,ri.asHours=zn,ri.asDays=En,ri.asWeeks=Vn,ri.asMonths=Hn,ri.asQuarters=Bn,ri.asYears=jn,ri.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*k(this._months/12):NaN},ri._bubble=function(){var t,e,n,i,a,r=this._milliseconds,o=this._days,s=this._months,l=this._data;return r>=0&&o>=0&&s>=0||r<=0&&o<=0&&s<=0||(r+=864e5*Fn(Ln(s)+o),o=0,s=0),l.milliseconds=r%1e3,t=w(r/1e3),l.seconds=t%60,e=w(t/60),l.minutes=e%60,n=w(e/60),l.hours=n%24,o+=w(n/24),a=w(In(o)),s+=a,o-=Fn(Ln(a)),i=w(s/12),s%=12,l.days=o,l.months=s,l.years=i,this},ri.clone=function(){return Xe(this)},ri.get=function(t){return t=R(t),this.isValid()?this[t+"s"]():NaN},ri.milliseconds=Gn,ri.seconds=qn,ri.minutes=Zn,ri.hours=$n,ri.days=Xn,ri.weeks=function(){return w(this.days()/7)},ri.months=Kn,ri.years=Jn,ri.humanize=function(t){if(!this.isValid())return this.localeData().invalidDate();var e=this.localeData(),n=function(t,e,n){var i=Xe(t).abs(),a=Qn(i.as("s")),r=Qn(i.as("m")),o=Qn(i.as("h")),s=Qn(i.as("d")),l=Qn(i.as("M")),u=Qn(i.as("y")),d=a<=ti.ss&&["s",a]||a<ti.s&&["ss",a]||r<=1&&["m"]||r<ti.m&&["mm",r]||o<=1&&["h"]||o<ti.h&&["hh",o]||s<=1&&["d"]||s<ti.d&&["dd",s]||l<=1&&["M"]||l<ti.M&&["MM",l]||u<=1&&["y"]||["yy",u];return d[2]=e,d[3]=+t>0,d[4]=n,ei.apply(null,d)}(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)},ri.toISOString=ai,ri.toString=ai,ri.toJSON=ai,ri.locale=rn,ri.localeData=sn,ri.toIsoString=D("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ai),ri.lang=on,j("X",0,0,"unix"),j("x",0,0,"valueOf"),dt("x",rt),dt("X",/[+-]?\d+(\.\d{1,3})?/),gt("X",(function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))})),gt("x",(function(t,e,n){n._d=new Date(k(t))})),a.version="2.24.0",n=Le,a.fn=Mn,a.min=function(){return We("isBefore",[].slice.call(arguments,0))},a.max=function(){return We("isAfter",[].slice.call(arguments,0))},a.now=function(){return Date.now?Date.now():+new Date},a.utc=f,a.unix=function(t){return Le(1e3*t)},a.months=function(t,e){return Pn(t,e,"months")},a.isDate=u,a.locale=ge,a.invalid=p,a.duration=Xe,a.isMoment=_,a.weekdays=function(t,e,n){return Tn(t,e,n,"weekdays")},a.parseZone=function(){return Le.apply(null,arguments).parseZone()},a.localeData=pe,a.isDuration=Ee,a.monthsShort=function(t,e){return Pn(t,e,"monthsShort")},a.weekdaysMin=function(t,e,n){return Tn(t,e,n,"weekdaysMin")},a.defineLocale=me,a.updateLocale=function(t,e){if(null!=e){var n,i,a=ue;null!=(i=fe(t))&&(a=i._config),e=A(a,e),(n=new F(e)).parentLocale=de[t],de[t]=n,ge(t)}else null!=de[t]&&(null!=de[t].parentLocale?de[t]=de[t].parentLocale:null!=de[t]&&delete de[t]);return de[t]},a.locales=function(){return C(de)},a.weekdaysShort=function(t,e,n){return Tn(t,e,n,"weekdaysShort")},a.normalizeUnits=R,a.relativeTimeRounding=function(t){return void 0===t?Qn:"function"==typeof t&&(Qn=t,!0)},a.relativeTimeThreshold=function(t,e){return void 0!==ti[t]&&(void 0===e?ti[t]:(ti[t]=e,"s"===t&&(ti.ss=e-1),!0))},a.calendarFormat=function(t,e){var n=t.diff(e,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"},a.prototype=Mn,a.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},a}()})),mi={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};rn._date.override("function"==typeof gi?{_id:"moment",formats:function(){return mi},parse:function(t,e){return"string"==typeof t&&"string"==typeof e?t=gi(t,e):t instanceof gi||(t=gi(t)),t.isValid()?t.valueOf():null},format:function(t,e){return gi(t).format(e)},add:function(t,e,n){return gi(t).add(e,n).valueOf()},diff:function(t,e,n){return gi(t).diff(gi(e),n)},startOf:function(t,e,n){return t=gi(t),"isoWeek"===e?t.isoWeekday(n).valueOf():t.startOf(e).valueOf()},endOf:function(t,e){return gi(t).endOf(e).valueOf()},_create:function(t){return gi(t)}}:{}),W._set("global",{plugins:{filler:{propagate:!0}}});var pi={dataset:function(t){var e=t.fill,n=t.chart,i=n.getDatasetMeta(e),a=i&&n.isDatasetVisible(e)&&i.dataset._children||[],r=a.length||0;return r?function(t,e){return e<r&&a[e]._view||null}:null},boundary:function(t){var e=t.boundary,n=e?e.x:null,i=e?e.y:null;return H.isArray(e)?function(t,n){return e[n]}:function(t){return{x:null===n?t.x:n,y:null===i?t.y:i}}}};function vi(t,e,n){var i,a=t._model||{},r=a.fill;if(void 0===r&&(r=!!a.backgroundColor),!1===r||null===r)return!1;if(!0===r)return"origin";if(i=parseFloat(r,10),isFinite(i)&&Math.floor(i)===i)return"-"!==r[0]&&"+"!==r[0]||(i=e+i),!(i===e||i<0||i>=n)&&i;switch(r){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return r;default:return!1}}function bi(t){return(t.el._scale||{}).getPointPositionForValue?function(t){var e,n,i,a,r,o=t.el._scale,s=o.options,l=o.chart.data.labels.length,u=t.fill,d=[];if(!l)return null;for(e=s.ticks.reverse?o.max:o.min,n=s.ticks.reverse?o.min:o.max,i=o.getPointPositionForValue(0,e),a=0;a<l;++a)r="start"===u||"end"===u?o.getPointPositionForValue(a,"start"===u?e:n):o.getBasePosition(a),s.gridLines.circular&&(r.cx=i.x,r.cy=i.y,r.angle=o.getIndexAngle(a)-Math.PI/2),d.push(r);return d}(t):function(t){var e,n=t.el._model||{},i=t.el._scale||{},a=t.fill,r=null;if(isFinite(a))return null;if("start"===a?r=void 0===n.scaleBottom?i.bottom:n.scaleBottom:"end"===a?r=void 0===n.scaleTop?i.top:n.scaleTop:void 0!==n.scaleZero?r=n.scaleZero:i.getBasePixel&&(r=i.getBasePixel()),null!=r){if(void 0!==r.x&&void 0!==r.y)return r;if(H.isFinite(r))return{x:(e=i.isHorizontal())?r:null,y:e?null:r}}return null}(t)}function yi(t,e,n){var i,a=t[e].fill,r=[e];if(!n)return a;for(;!1!==a&&-1===r.indexOf(a);){if(!isFinite(a))return a;if(!(i=t[a]))return!1;if(i.visible)return a;r.push(a),a=i.fill}return!1}function xi(t){var e=t.fill,n="dataset";return!1===e?null:(isFinite(e)||(n="boundary"),pi[n](t))}function _i(t){return t&&!t.skip}function wi(t,e,n,i,a){var r,o,s,l;if(i&&a){for(t.moveTo(e[0].x,e[0].y),r=1;r<i;++r)H.canvas.lineTo(t,e[r-1],e[r]);if(void 0===n[0].angle)for(t.lineTo(n[a-1].x,n[a-1].y),r=a-1;r>0;--r)H.canvas.lineTo(t,n[r],n[r-1],!0);else for(o=n[0].cx,s=n[0].cy,l=Math.sqrt(Math.pow(n[0].x-o,2)+Math.pow(n[0].y-s,2)),r=a-1;r>0;--r)t.arc(o,s,l,n[r].angle,n[r-1].angle,!0)}}function ki(t,e,n,i,a,r){var o,s,l,u,d,h,c,f,g=e.length,m=i.spanGaps,p=[],v=[],b=0,y=0;for(t.beginPath(),o=0,s=g;o<s;++o)d=n(u=e[l=o%g]._view,l,i),h=_i(u),c=_i(d),r&&void 0===f&&h&&(s=g+(f=o+1)),h&&c?(b=p.push(u),y=v.push(d)):b&&y&&(m?(h&&p.push(u),c&&v.push(d)):(wi(t,p,v,b,y),b=y=0,p=[],v=[]));wi(t,p,v,b,y),t.closePath(),t.fillStyle=a,t.fill()}var Mi={id:"filler",afterDatasetsUpdate:function(t,e){var n,i,a,r,o=(t.data.datasets||[]).length,s=e.propagate,l=[];for(i=0;i<o;++i)r=null,(a=(n=t.getDatasetMeta(i)).dataset)&&a._model&&a instanceof wt.Line&&(r={visible:t.isDatasetVisible(i),fill:vi(a,i,o),chart:t,el:a}),n.$filler=r,l.push(r);for(i=0;i<o;++i)(r=l[i])&&(r.fill=yi(l,i,s),r.boundary=bi(r),r.mapper=xi(r))},beforeDatasetsDraw:function(t){var e,n,i,a,r,o,s,l=t._getSortedVisibleDatasetMetas(),u=t.ctx;for(n=l.length-1;n>=0;--n)(e=l[n].$filler)&&e.visible&&(a=(i=e.el)._view,r=i._children||[],o=e.mapper,s=a.backgroundColor||W.global.defaultColor,o&&s&&r.length&&(H.canvas.clipArea(u,t.chartArea),ki(u,r,o,a,s,i._loop),H.canvas.unclipArea(u)))}},Si=H.rtl.getRtlAdapter,Di=H.noop,Ci=H.valueOrDefault;function Pi(t,e){return t.usePointStyle&&t.boxWidth>e?e:t.boxWidth}W._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data.datasets,n=t.options.legend||{},i=n.labels&&n.labels.usePointStyle;return t._getSortedDatasetMetas().map((function(n){var a=n.controller.getStyle(i?0:void 0);return{text:e[n.index].label,fillStyle:a.backgroundColor,hidden:!t.isDatasetVisible(n.index),lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,pointStyle:a.pointStyle,rotation:a.rotation,datasetIndex:n.index}}),this)}}},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data.datasets;for(a.setAttribute("class",t.id+"-legend"),e=0,n=r.length;e<n;e++)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=r[e].backgroundColor,r[e].label&&i.appendChild(document.createTextNode(r[e].label));return a.outerHTML}});var Ti=$.extend({initialize:function(t){H.extend(this,t),this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1},beforeUpdate:Di,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Di,beforeSetDimensions:Di,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Di,beforeBuildLabels:Di,buildLabels:function(){var t=this,e=t.options.labels||{},n=H.callback(e.generateLabels,[t.chart],t)||[];e.filter&&(n=n.filter((function(n){return e.filter(n,t.chart.data)}))),t.options.reverse&&n.reverse(),t.legendItems=n},afterBuildLabels:Di,beforeFit:Di,fit:function(){var t=this,e=t.options,n=e.labels,i=e.display,a=t.ctx,r=H.options._parseFont(n),o=r.size,s=t.legendHitBoxes=[],l=t.minSize,u=t.isHorizontal();if(u?(l.width=t.maxWidth,l.height=i?10:0):(l.width=i?10:0,l.height=t.maxHeight),i){if(a.font=r.string,u){var d=t.lineWidths=[0],h=0;a.textAlign="left",a.textBaseline="middle",H.each(t.legendItems,(function(t,e){var i=Pi(n,o)+o/2+a.measureText(t.text).width;(0===e||d[d.length-1]+i+2*n.padding>l.width)&&(h+=o+n.padding,d[d.length-(e>0?0:1)]=0),s[e]={left:0,top:0,width:i,height:o},d[d.length-1]+=i+n.padding})),l.height+=h}else{var c=n.padding,f=t.columnWidths=[],g=t.columnHeights=[],m=n.padding,p=0,v=0;H.each(t.legendItems,(function(t,e){var i=Pi(n,o)+o/2+a.measureText(t.text).width;e>0&&v+o+2*c>l.height&&(m+=p+n.padding,f.push(p),g.push(v),p=0,v=0),p=Math.max(p,i),v+=o+c,s[e]={left:0,top:0,width:i,height:o}})),m+=p,f.push(p),g.push(v),l.width+=m}t.width=l.width,t.height=l.height}else t.width=l.width=t.height=l.height=0},afterFit:Di,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,n=e.labels,i=W.global,a=i.defaultColor,r=i.elements.line,o=t.height,s=t.columnHeights,l=t.width,u=t.lineWidths;if(e.display){var d,h=Si(e.rtl,t.left,t.minSize.width),c=t.ctx,f=Ci(n.fontColor,i.defaultFontColor),g=H.options._parseFont(n),m=g.size;c.textAlign=h.textAlign("left"),c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=g.string;var p=Pi(n,m),v=t.legendHitBoxes,b=function(t,i){switch(e.align){case"start":return n.padding;case"end":return t-i;default:return(t-i+n.padding)/2}},y=t.isHorizontal();d=y?{x:t.left+b(l,u[0]),y:t.top+n.padding,line:0}:{x:t.left+n.padding,y:t.top+b(o,s[0]),line:0},H.rtl.overrideTextDirection(t.ctx,e.textDirection);var x=m+n.padding;H.each(t.legendItems,(function(e,i){var f=c.measureText(e.text).width,g=p+m/2+f,_=d.x,w=d.y;h.setWidth(t.minSize.width),y?i>0&&_+g+n.padding>t.left+t.minSize.width&&(w=d.y+=x,d.line++,_=d.x=t.left+b(l,u[d.line])):i>0&&w+x>t.top+t.minSize.height&&(_=d.x=_+t.columnWidths[d.line]+n.padding,d.line++,w=d.y=t.top+b(o,s[d.line]));var k=h.x(_);!function(t,e,i){if(!(isNaN(p)||p<=0)){c.save();var o=Ci(i.lineWidth,r.borderWidth);if(c.fillStyle=Ci(i.fillStyle,a),c.lineCap=Ci(i.lineCap,r.borderCapStyle),c.lineDashOffset=Ci(i.lineDashOffset,r.borderDashOffset),c.lineJoin=Ci(i.lineJoin,r.borderJoinStyle),c.lineWidth=o,c.strokeStyle=Ci(i.strokeStyle,a),c.setLineDash&&c.setLineDash(Ci(i.lineDash,r.borderDash)),n&&n.usePointStyle){var s=p*Math.SQRT2/2,l=h.xPlus(t,p/2),u=e+m/2;H.canvas.drawPoint(c,i.pointStyle,s,l,u,i.rotation)}else c.fillRect(h.leftForLtr(t,p),e,p,m),0!==o&&c.strokeRect(h.leftForLtr(t,p),e,p,m);c.restore()}}(k,w,e),v[i].left=h.leftForLtr(k,v[i].width),v[i].top=w,function(t,e,n,i){var a=m/2,r=h.xPlus(t,p+a),o=e+a;c.fillText(n.text,r,o),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(r,o),c.lineTo(h.xPlus(r,i),o),c.stroke())}(k,w,e,f),y?d.x+=g+n.padding:d.y+=x})),H.rtl.restoreTextDirection(t.ctx,e.textDirection)}},_getLegendItemAt:function(t,e){var n,i,a,r=this;if(t>=r.left&&t<=r.right&&e>=r.top&&e<=r.bottom)for(a=r.legendHitBoxes,n=0;n<a.length;++n)if(t>=(i=a[n]).left&&t<=i.left+i.width&&e>=i.top&&e<=i.top+i.height)return r.legendItems[n];return null},handleEvent:function(t){var e,n=this,i=n.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!i.onHover&&!i.onLeave)return}else{if("click"!==a)return;if(!i.onClick)return}e=n._getLegendItemAt(t.x,t.y),"click"===a?e&&i.onClick&&i.onClick.call(n,t.native,e):(i.onLeave&&e!==n._hoveredItem&&(n._hoveredItem&&i.onLeave.call(n,t.native,n._hoveredItem),n._hoveredItem=e),i.onHover&&e&&i.onHover.call(n,t.native,e))}});function Oi(t,e){var n=new Ti({ctx:t.ctx,options:e,chart:t});me.configure(t,n,e),me.addBox(t,n),t.legend=n}var Ai={id:"legend",_element:Ti,beforeInit:function(t){var e=t.options.legend;e&&Oi(t,e)},beforeUpdate:function(t){var e=t.options.legend,n=t.legend;e?(H.mergeIf(e,W.global.legend),n?(me.configure(t,n,e),n.options=e):Oi(t,e)):n&&(me.removeBox(t,n),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}},Fi=H.noop;W._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Ii=$.extend({initialize:function(t){H.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:Fi,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Fi,beforeSetDimensions:Fi,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Fi,beforeBuildLabels:Fi,buildLabels:Fi,afterBuildLabels:Fi,beforeFit:Fi,fit:function(){var t,e=this,n=e.options,i=e.minSize={},a=e.isHorizontal();n.display?(t=(H.isArray(n.text)?n.text.length:1)*H.options._parseFont(n).lineHeight+2*n.padding,e.width=i.width=a?e.maxWidth:t,e.height=i.height=a?t:e.maxHeight):e.width=i.width=e.height=i.height=0},afterFit:Fi,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=t.options;if(n.display){var i,a,r,o=H.options._parseFont(n),s=o.lineHeight,l=s/2+n.padding,u=0,d=t.top,h=t.left,c=t.bottom,f=t.right;e.fillStyle=H.valueOrDefault(n.fontColor,W.global.defaultFontColor),e.font=o.string,t.isHorizontal()?(a=h+(f-h)/2,r=d+l,i=f-h):(a="left"===n.position?h+l:f-l,r=d+(c-d)/2,i=c-d,u=Math.PI*("left"===n.position?-.5:.5)),e.save(),e.translate(a,r),e.rotate(u),e.textAlign="center",e.textBaseline="middle";var g=n.text;if(H.isArray(g))for(var m=0,p=0;p<g.length;++p)e.fillText(g[p],0,m,i),m+=s;else e.fillText(g,0,0,i);e.restore()}}});function Li(t,e){var n=new Ii({ctx:t.ctx,options:e,chart:t});me.configure(t,n,e),me.addBox(t,n),t.titleBlock=n}var Ri={},Ni=Mi,Wi=Ai,Yi={id:"title",_element:Ii,beforeInit:function(t){var e=t.options.title;e&&Li(t,e)},beforeUpdate:function(t){var e=t.options.title,n=t.titleBlock;e?(H.mergeIf(e,W.global.title),n?(me.configure(t,n,e),n.options=e):Li(t,e)):n&&(me.removeBox(t,n),delete t.titleBlock)}};for(var zi in Ri.filler=Ni,Ri.legend=Wi,Ri.title=Yi,en.helpers=H,function(){function t(t,e,n){var i;return"string"==typeof t?(i=parseInt(t,10),-1!==t.indexOf("%")&&(i=i/100*e.parentNode[n])):i=t,i}function e(t){return null!=t&&"none"!==t}function n(n,i,a){var r=document.defaultView,o=H._getParentNode(n),s=r.getComputedStyle(n)[i],l=r.getComputedStyle(o)[i],u=e(s),d=e(l),h=Number.POSITIVE_INFINITY;return u||d?Math.min(u?t(s,n,a):h,d?t(l,o,a):h):"none"}H.where=function(t,e){if(H.isArray(t)&&Array.prototype.filter)return t.filter(e);var n=[];return H.each(t,(function(t){e(t)&&n.push(t)})),n},H.findIndex=Array.prototype.findIndex?function(t,e,n){return t.findIndex(e,n)}:function(t,e,n){n=void 0===n?t:n;for(var i=0,a=t.length;i<a;++i)if(e.call(n,t[i],i,t))return i;return-1},H.findNextWhere=function(t,e,n){H.isNullOrUndef(n)&&(n=-1);for(var i=n+1;i<t.length;i++){var a=t[i];if(e(a))return a}},H.findPreviousWhere=function(t,e,n){H.isNullOrUndef(n)&&(n=t.length);for(var i=n-1;i>=0;i--){var a=t[i];if(e(a))return a}},H.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},H.almostEquals=function(t,e,n){return Math.abs(t-e)<n},H.almostWhole=function(t,e){var n=Math.round(t);return n-e<=t&&n+e>=t},H.max=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.max(t,e)}),Number.NEGATIVE_INFINITY)},H.min=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.min(t,e)}),Number.POSITIVE_INFINITY)},H.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0===(t=+t)||isNaN(t)?t:t>0?1:-1},H.toRadians=function(t){return t*(Math.PI/180)},H.toDegrees=function(t){return t*(180/Math.PI)},H._decimalPlaces=function(t){if(H.isFinite(t)){for(var e=1,n=0;Math.round(t*e)/e!==t;)e*=10,n++;return n}},H.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},H.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},H.aliasPixel=function(t){return t%2==0?0:.5},H._alignPixel=function(t,e,n){var i=t.currentDevicePixelRatio,a=n/2;return Math.round((e-a)*i)/i+a},H.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l),h=i*(u=isNaN(u)?0:u),c=i*(d=isNaN(d)?0:d);return{previous:{x:r.x-h*(o.x-a.x),y:r.y-h*(o.y-a.y)},next:{x:r.x+c*(o.x-a.x),y:r.y+c*(o.y-a.y)}}},H.EPSILON=Number.EPSILON||1e-14,H.splineCurveMonotone=function(t){var e,n,i,a,r,o,s,l,u,d=(t||[]).map((function(t){return{model:t._model,deltaK:0,mK:0}})),h=d.length;for(e=0;e<h;++e)if(!(i=d[e]).model.skip){if(n=e>0?d[e-1]:null,(a=e<h-1?d[e+1]:null)&&!a.model.skip){var c=a.model.x-i.model.x;i.deltaK=0!==c?(a.model.y-i.model.y)/c:0}!n||n.model.skip?i.mK=i.deltaK:!a||a.model.skip?i.mK=n.deltaK:this.sign(n.deltaK)!==this.sign(i.deltaK)?i.mK=0:i.mK=(n.deltaK+i.deltaK)/2}for(e=0;e<h-1;++e)i=d[e],a=d[e+1],i.model.skip||a.model.skip||(H.almostEquals(i.deltaK,0,this.EPSILON)?i.mK=a.mK=0:(r=i.mK/i.deltaK,o=a.mK/i.deltaK,(l=Math.pow(r,2)+Math.pow(o,2))<=9||(s=3/Math.sqrt(l),i.mK=r*s*i.deltaK,a.mK=o*s*i.deltaK)));for(e=0;e<h;++e)(i=d[e]).model.skip||(n=e>0?d[e-1]:null,a=e<h-1?d[e+1]:null,n&&!n.model.skip&&(u=(i.model.x-n.model.x)/3,i.model.controlPointPreviousX=i.model.x-u,i.model.controlPointPreviousY=i.model.y-u*i.mK),a&&!a.model.skip&&(u=(a.model.x-i.model.x)/3,i.model.controlPointNextX=i.model.x+u,i.model.controlPointNextY=i.model.y+u*i.mK))},H.nextItem=function(t,e,n){return n?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},H.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},H.niceNum=function(t,e){var n=Math.floor(H.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},H.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},H.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.target||t.srcElement,o=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var l=parseFloat(H.getStyle(r,"padding-left")),u=parseFloat(H.getStyle(r,"padding-top")),d=parseFloat(H.getStyle(r,"padding-right")),h=parseFloat(H.getStyle(r,"padding-bottom")),c=o.right-o.left-l-d,f=o.bottom-o.top-u-h;return{x:n=Math.round((n-o.left-l)/c*r.width/e.currentDevicePixelRatio),y:i=Math.round((i-o.top-u)/f*r.height/e.currentDevicePixelRatio)}},H.getConstraintWidth=function(t){return n(t,"max-width","clientWidth")},H.getConstraintHeight=function(t){return n(t,"max-height","clientHeight")},H._calculatePadding=function(t,e,n){return(e=H.getStyle(t,e)).indexOf("%")>-1?n*parseInt(e,10)/100:parseInt(e,10)},H._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},H.getMaximumWidth=function(t){var e=H._getParentNode(t);if(!e)return t.clientWidth;var n=e.clientWidth,i=n-H._calculatePadding(e,"padding-left",n)-H._calculatePadding(e,"padding-right",n),a=H.getConstraintWidth(t);return isNaN(a)?i:Math.min(i,a)},H.getMaximumHeight=function(t){var e=H._getParentNode(t);if(!e)return t.clientHeight;var n=e.clientHeight,i=n-H._calculatePadding(e,"padding-top",n)-H._calculatePadding(e,"padding-bottom",n),a=H.getConstraintHeight(t);return isNaN(a)?i:Math.min(i,a)},H.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},H.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,r=t.width;i.height=a*n,i.width=r*n,t.ctx.scale(n,n),i.style.height||i.style.width||(i.style.height=a+"px",i.style.width=r+"px")}},H.fontString=function(t,e,n){return e+" "+t+"px "+n},H.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var o,s,l,u,d,h=0,c=n.length;for(o=0;o<c;o++)if(null!=(u=n[o])&&!0!==H.isArray(u))h=H.measureText(t,a,r,h,u);else if(H.isArray(u))for(s=0,l=u.length;s<l;s++)null==(d=u[s])||H.isArray(d)||(h=H.measureText(t,a,r,h,d));var f=r.length/2;if(f>n.length){for(o=0;o<f;o++)delete a[r[o]];r.splice(0,f)}return h},H.measureText=function(t,e,n,i,a){var r=e[a];return r||(r=e[a]=t.measureText(a).width,n.push(a)),r>i&&(i=r),i},H.numberOfLabelLines=function(t){var e=1;return H.each(t,(function(t){H.isArray(t)&&t.length>e&&(e=t.length)})),e},H.color=k?function(t){return t instanceof CanvasGradient&&(t=W.global.defaultColor),k(t)}:function(t){return console.error("Color.js not found!"),t},H.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:H.color(t).saturate(.5).darken(.1).rgbString()}}(),en._adapters=rn,en.Animation=K,en.animationService=J,en.controllers=Jt,en.DatasetController=it,en.defaults=W,en.Element=$,en.elements=wt,en.Interaction=re,en.layouts=me,en.platform=Ie,en.plugins=Le,en.Scale=xn,en.scaleService=Re,en.Ticks=on,en.Tooltip=Ge,en.helpers.each(fi,(function(t,e){en.scaleService.registerScaleType(e,t,t._defaults)})),Ri)Ri.hasOwnProperty(zi)&&en.plugins.register(Ri[zi]);en.platform.initialize();var Ei=en;return"undefined"!=typeof window&&(window.Chart=en),en.Chart=en,en.Legend=Ri.legend._element,en.Title=Ri.title._element,en.pluginService=en.plugins,en.PluginBase=en.Element.extend({}),en.canvasHelpers=en.helpers.canvas,en.layoutService=en.layouts,en.LinearScaleBase=Dn,en.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],(function(t){en[t]=function(e,n){return new en(e,en.helpers.merge(n||{},{type:t.charAt(0).toLowerCase()+t.slice(1)}))}})),Ei})); diff --git a/lib/web/chartjs/Chart.css b/lib/web/chartjs/Chart.css new file mode 100644 index 0000000000000..5e749593eeb65 --- /dev/null +++ b/lib/web/chartjs/Chart.css @@ -0,0 +1,47 @@ +/* + * DOM element rendering detection + * https://davidwalsh.name/detect-node-insertion + */ +@keyframes chartjs-render-animation { + from { opacity: 0.99; } + to { opacity: 1; } +} + +.chartjs-render-monitor { + animation: chartjs-render-animation 0.001s; +} + +/* + * DOM element resizing detection + * https://github.com/marcj/css-element-queries + */ +.chartjs-size-monitor, +.chartjs-size-monitor-expand, +.chartjs-size-monitor-shrink { + position: absolute; + direction: ltr; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + pointer-events: none; + visibility: hidden; + z-index: -1; +} + +.chartjs-size-monitor-expand > div { + position: absolute; + width: 1000000px; + height: 1000000px; + left: 0; + top: 0; +} + +.chartjs-size-monitor-shrink > div { + position: absolute; + width: 200%; + height: 200%; + left: 0; + top: 0; +} diff --git a/lib/web/chartjs/Chart.js b/lib/web/chartjs/Chart.js new file mode 100644 index 0000000000000..e8d937cf82c54 --- /dev/null +++ b/lib/web/chartjs/Chart.js @@ -0,0 +1,16151 @@ +/*! + * Chart.js v2.9.3 + * https://www.chartjs.org + * (c) 2019 Chart.js Contributors + * Released under the MIT License + */ +(function (global, factory) { +typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(function() { try { return require('moment'); } catch(e) { } }()) : +typeof define === 'function' && define.amd ? define(['require'], function(require) { return factory(function() { try { return require('moment'); } catch(e) { } }()); }) : +(global = global || self, global.Chart = factory(global.moment)); +}(this, (function (moment) { 'use strict'; + +moment = moment && moment.hasOwnProperty('default') ? moment['default'] : moment; + +function createCommonjsModule(fn, module) { + return module = { exports: {} }, fn(module, module.exports), module.exports; +} + +function getCjsExportFromNamespace (n) { + return n && n['default'] || n; +} + +var colorName = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +var conversions = createCommonjsModule(function (module) { +/* MIT license */ + + +// NOTE: conversions should only return primitive values (i.e. arrays, or +// values that give correct `typeof` results). +// do not use box values types (i.e. Number(), String(), etc.) + +var reverseKeywords = {}; +for (var key in colorName) { + if (colorName.hasOwnProperty(key)) { + reverseKeywords[colorName[key]] = key; + } +} + +var convert = module.exports = { + rgb: {channels: 3, labels: 'rgb'}, + hsl: {channels: 3, labels: 'hsl'}, + hsv: {channels: 3, labels: 'hsv'}, + hwb: {channels: 3, labels: 'hwb'}, + cmyk: {channels: 4, labels: 'cmyk'}, + xyz: {channels: 3, labels: 'xyz'}, + lab: {channels: 3, labels: 'lab'}, + lch: {channels: 3, labels: 'lch'}, + hex: {channels: 1, labels: ['hex']}, + keyword: {channels: 1, labels: ['keyword']}, + ansi16: {channels: 1, labels: ['ansi16']}, + ansi256: {channels: 1, labels: ['ansi256']}, + hcg: {channels: 3, labels: ['h', 'c', 'g']}, + apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, + gray: {channels: 1, labels: ['gray']} +}; + +// hide .channels and .labels properties +for (var model in convert) { + if (convert.hasOwnProperty(model)) { + if (!('channels' in convert[model])) { + throw new Error('missing channels property: ' + model); + } + + if (!('labels' in convert[model])) { + throw new Error('missing channel labels property: ' + model); + } + + if (convert[model].labels.length !== convert[model].channels) { + throw new Error('channel and label counts mismatch: ' + model); + } + + var channels = convert[model].channels; + var labels = convert[model].labels; + delete convert[model].channels; + delete convert[model].labels; + Object.defineProperty(convert[model], 'channels', {value: channels}); + Object.defineProperty(convert[model], 'labels', {value: labels}); + } +} + +convert.rgb.hsl = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var min = Math.min(r, g, b); + var max = Math.max(r, g, b); + var delta = max - min; + var h; + var s; + var l; + + if (max === min) { + h = 0; + } else if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else if (b === max) { + h = 4 + (r - g) / delta; + } + + h = Math.min(h * 60, 360); + + if (h < 0) { + h += 360; + } + + l = (min + max) / 2; + + if (max === min) { + s = 0; + } else if (l <= 0.5) { + s = delta / (max + min); + } else { + s = delta / (2 - max - min); + } + + return [h, s * 100, l * 100]; +}; + +convert.rgb.hsv = function (rgb) { + var rdif; + var gdif; + var bdif; + var h; + var s; + + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var v = Math.max(r, g, b); + var diff = v - Math.min(r, g, b); + var diffc = function (c) { + return (v - c) / 6 / diff + 1 / 2; + }; + + if (diff === 0) { + h = s = 0; + } else { + s = diff / v; + rdif = diffc(r); + gdif = diffc(g); + bdif = diffc(b); + + if (r === v) { + h = bdif - gdif; + } else if (g === v) { + h = (1 / 3) + rdif - bdif; + } else if (b === v) { + h = (2 / 3) + gdif - rdif; + } + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + } + + return [ + h * 360, + s * 100, + v * 100 + ]; +}; + +convert.rgb.hwb = function (rgb) { + var r = rgb[0]; + var g = rgb[1]; + var b = rgb[2]; + var h = convert.rgb.hsl(rgb)[0]; + var w = 1 / 255 * Math.min(r, Math.min(g, b)); + + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); + + return [h, w * 100, b * 100]; +}; + +convert.rgb.cmyk = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var c; + var m; + var y; + var k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + + return [c * 100, m * 100, y * 100, k * 100]; +}; + +/** + * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance + * */ +function comparativeDistance(x, y) { + return ( + Math.pow(x[0] - y[0], 2) + + Math.pow(x[1] - y[1], 2) + + Math.pow(x[2] - y[2], 2) + ); +} + +convert.rgb.keyword = function (rgb) { + var reversed = reverseKeywords[rgb]; + if (reversed) { + return reversed; + } + + var currentClosestDistance = Infinity; + var currentClosestKeyword; + + for (var keyword in colorName) { + if (colorName.hasOwnProperty(keyword)) { + var value = colorName[keyword]; + + // Compute comparative distance + var distance = comparativeDistance(rgb, value); + + // Check if its less, if so set as closest + if (distance < currentClosestDistance) { + currentClosestDistance = distance; + currentClosestKeyword = keyword; + } + } + } + + return currentClosestKeyword; +}; + +convert.keyword.rgb = function (keyword) { + return colorName[keyword]; +}; + +convert.rgb.xyz = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); + + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + + return [x * 100, y * 100, z * 100]; +}; + +convert.rgb.lab = function (rgb) { + var xyz = convert.rgb.xyz(rgb); + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.hsl.rgb = function (hsl) { + var h = hsl[0] / 360; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var t1; + var t2; + var t3; + var rgb; + var val; + + if (s === 0) { + val = l * 255; + return [val, val, val]; + } + + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * -(i - 1); + if (t3 < 0) { + t3++; + } + if (t3 > 1) { + t3--; + } + + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + + rgb[i] = val * 255; + } + + return rgb; +}; + +convert.hsl.hsv = function (hsl) { + var h = hsl[0]; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var smin = s; + var lmin = Math.max(l, 0.01); + var sv; + var v; + + l *= 2; + s *= (l <= 1) ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + v = (l + s) / 2; + sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); + + return [h, sv * 100, v * 100]; +}; + +convert.hsv.rgb = function (hsv) { + var h = hsv[0] / 60; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var hi = Math.floor(h) % 6; + + var f = h - Math.floor(h); + var p = 255 * v * (1 - s); + var q = 255 * v * (1 - (s * f)); + var t = 255 * v * (1 - (s * (1 - f))); + v *= 255; + + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +}; + +convert.hsv.hsl = function (hsv) { + var h = hsv[0]; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var vmin = Math.max(v, 0.01); + var lmin; + var sl; + var l; + + l = (2 - s) * v; + lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= (lmin <= 1) ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; + + return [h, sl * 100, l * 100]; +}; + +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +convert.hwb.rgb = function (hwb) { + var h = hwb[0] / 360; + var wh = hwb[1] / 100; + var bl = hwb[2] / 100; + var ratio = wh + bl; + var i; + var v; + var f; + var n; + + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + + if ((i & 0x01) !== 0) { + f = 1 - f; + } + + n = wh + f * (v - wh); // linear interpolation + + var r; + var g; + var b; + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +}; + +convert.cmyk.rgb = function (cmyk) { + var c = cmyk[0] / 100; + var m = cmyk[1] / 100; + var y = cmyk[2] / 100; + var k = cmyk[3] / 100; + var r; + var g; + var b; + + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.rgb = function (xyz) { + var x = xyz[0] / 100; + var y = xyz[1] / 100; + var z = xyz[2] / 100; + var r; + var g; + var b; + + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + + // assume sRGB + r = r > 0.0031308 + ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r * 12.92; + + g = g > 0.0031308 + ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g * 12.92; + + b = b > 0.0031308 + ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b * 12.92; + + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + + return [r * 255, g * 255, b * 255]; +}; + +convert.xyz.lab = function (xyz) { + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; + + x /= 95.047; + y /= 100; + z /= 108.883; + + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + + return [l, a, b]; +}; + +convert.lab.xyz = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var x; + var y; + var z; + + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + + var y2 = Math.pow(y, 3); + var x2 = Math.pow(x, 3); + var z2 = Math.pow(z, 3); + y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; + x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; + z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; + + x *= 95.047; + y *= 100; + z *= 108.883; + + return [x, y, z]; +}; + +convert.lab.lch = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var hr; + var h; + var c; + + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + + if (h < 0) { + h += 360; + } + + c = Math.sqrt(a * a + b * b); + + return [l, c, h]; +}; + +convert.lch.lab = function (lch) { + var l = lch[0]; + var c = lch[1]; + var h = lch[2]; + var a; + var b; + var hr; + + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + + return [l, a, b]; +}; + +convert.rgb.ansi16 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization + + value = Math.round(value / 50); + + if (value === 0) { + return 30; + } + + var ansi = 30 + + ((Math.round(b / 255) << 2) + | (Math.round(g / 255) << 1) + | Math.round(r / 255)); + + if (value === 2) { + ansi += 60; + } + + return ansi; +}; + +convert.hsv.ansi16 = function (args) { + // optimization here; we already know the value and don't need to get + // it converted for us. + return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); +}; + +convert.rgb.ansi256 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + + // we use the extended greyscale palette here, with the exception of + // black and white. normal palette only has 4 greyscale shades. + if (r === g && g === b) { + if (r < 8) { + return 16; + } + + if (r > 248) { + return 231; + } + + return Math.round(((r - 8) / 247) * 24) + 232; + } + + var ansi = 16 + + (36 * Math.round(r / 255 * 5)) + + (6 * Math.round(g / 255 * 5)) + + Math.round(b / 255 * 5); + + return ansi; +}; + +convert.ansi16.rgb = function (args) { + var color = args % 10; + + // handle greyscale + if (color === 0 || color === 7) { + if (args > 50) { + color += 3.5; + } + + color = color / 10.5 * 255; + + return [color, color, color]; + } + + var mult = (~~(args > 50) + 1) * 0.5; + var r = ((color & 1) * mult) * 255; + var g = (((color >> 1) & 1) * mult) * 255; + var b = (((color >> 2) & 1) * mult) * 255; + + return [r, g, b]; +}; + +convert.ansi256.rgb = function (args) { + // handle greyscale + if (args >= 232) { + var c = (args - 232) * 10 + 8; + return [c, c, c]; + } + + args -= 16; + + var rem; + var r = Math.floor(args / 36) / 5 * 255; + var g = Math.floor((rem = args % 36) / 6) / 5 * 255; + var b = (rem % 6) / 5 * 255; + + return [r, g, b]; +}; + +convert.rgb.hex = function (args) { + var integer = ((Math.round(args[0]) & 0xFF) << 16) + + ((Math.round(args[1]) & 0xFF) << 8) + + (Math.round(args[2]) & 0xFF); + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.hex.rgb = function (args) { + var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + if (!match) { + return [0, 0, 0]; + } + + var colorString = match[0]; + + if (match[0].length === 3) { + colorString = colorString.split('').map(function (char) { + return char + char; + }).join(''); + } + + var integer = parseInt(colorString, 16); + var r = (integer >> 16) & 0xFF; + var g = (integer >> 8) & 0xFF; + var b = integer & 0xFF; + + return [r, g, b]; +}; + +convert.rgb.hcg = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var max = Math.max(Math.max(r, g), b); + var min = Math.min(Math.min(r, g), b); + var chroma = (max - min); + var grayscale; + var hue; + + if (chroma < 1) { + grayscale = min / (1 - chroma); + } else { + grayscale = 0; + } + + if (chroma <= 0) { + hue = 0; + } else + if (max === r) { + hue = ((g - b) / chroma) % 6; + } else + if (max === g) { + hue = 2 + (b - r) / chroma; + } else { + hue = 4 + (r - g) / chroma + 4; + } + + hue /= 6; + hue %= 1; + + return [hue * 360, chroma * 100, grayscale * 100]; +}; + +convert.hsl.hcg = function (hsl) { + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var c = 1; + var f = 0; + + if (l < 0.5) { + c = 2.0 * s * l; + } else { + c = 2.0 * s * (1.0 - l); + } + + if (c < 1.0) { + f = (l - 0.5 * c) / (1.0 - c); + } + + return [hsl[0], c * 100, f * 100]; +}; + +convert.hsv.hcg = function (hsv) { + var s = hsv[1] / 100; + var v = hsv[2] / 100; + + var c = s * v; + var f = 0; + + if (c < 1.0) { + f = (v - c) / (1 - c); + } + + return [hsv[0], c * 100, f * 100]; +}; + +convert.hcg.rgb = function (hcg) { + var h = hcg[0] / 360; + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + if (c === 0.0) { + return [g * 255, g * 255, g * 255]; + } + + var pure = [0, 0, 0]; + var hi = (h % 1) * 6; + var v = hi % 1; + var w = 1 - v; + var mg = 0; + + switch (Math.floor(hi)) { + case 0: + pure[0] = 1; pure[1] = v; pure[2] = 0; break; + case 1: + pure[0] = w; pure[1] = 1; pure[2] = 0; break; + case 2: + pure[0] = 0; pure[1] = 1; pure[2] = v; break; + case 3: + pure[0] = 0; pure[1] = w; pure[2] = 1; break; + case 4: + pure[0] = v; pure[1] = 0; pure[2] = 1; break; + default: + pure[0] = 1; pure[1] = 0; pure[2] = w; + } + + mg = (1.0 - c) * g; + + return [ + (c * pure[0] + mg) * 255, + (c * pure[1] + mg) * 255, + (c * pure[2] + mg) * 255 + ]; +}; + +convert.hcg.hsv = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var v = c + g * (1.0 - c); + var f = 0; + + if (v > 0.0) { + f = c / v; + } + + return [hcg[0], f * 100, v * 100]; +}; + +convert.hcg.hsl = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + + var l = g * (1.0 - c) + 0.5 * c; + var s = 0; + + if (l > 0.0 && l < 0.5) { + s = c / (2 * l); + } else + if (l >= 0.5 && l < 1.0) { + s = c / (2 * (1 - l)); + } + + return [hcg[0], s * 100, l * 100]; +}; + +convert.hcg.hwb = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + var v = c + g * (1.0 - c); + return [hcg[0], (v - c) * 100, (1 - v) * 100]; +}; + +convert.hwb.hcg = function (hwb) { + var w = hwb[1] / 100; + var b = hwb[2] / 100; + var v = 1 - b; + var c = v - w; + var g = 0; + + if (c < 1) { + g = (v - c) / (1 - c); + } + + return [hwb[0], c * 100, g * 100]; +}; + +convert.apple.rgb = function (apple) { + return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; +}; + +convert.rgb.apple = function (rgb) { + return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; +}; + +convert.gray.rgb = function (args) { + return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; +}; + +convert.gray.hsl = convert.gray.hsv = function (args) { + return [0, 0, args[0]]; +}; + +convert.gray.hwb = function (gray) { + return [0, 100, gray[0]]; +}; + +convert.gray.cmyk = function (gray) { + return [0, 0, 0, gray[0]]; +}; + +convert.gray.lab = function (gray) { + return [gray[0], 0, 0]; +}; + +convert.gray.hex = function (gray) { + var val = Math.round(gray[0] / 100 * 255) & 0xFF; + var integer = (val << 16) + (val << 8) + val; + + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.rgb.gray = function (rgb) { + var val = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [val / 255 * 100]; +}; +}); +var conversions_1 = conversions.rgb; +var conversions_2 = conversions.hsl; +var conversions_3 = conversions.hsv; +var conversions_4 = conversions.hwb; +var conversions_5 = conversions.cmyk; +var conversions_6 = conversions.xyz; +var conversions_7 = conversions.lab; +var conversions_8 = conversions.lch; +var conversions_9 = conversions.hex; +var conversions_10 = conversions.keyword; +var conversions_11 = conversions.ansi16; +var conversions_12 = conversions.ansi256; +var conversions_13 = conversions.hcg; +var conversions_14 = conversions.apple; +var conversions_15 = conversions.gray; + +/* + this function routes a model to all other models. + + all functions that are routed have a property `.conversion` attached + to the returned synthetic function. This property is an array + of strings, each with the steps in between the 'from' and 'to' + color models (inclusive). + + conversions that are not possible simply are not included. +*/ + +function buildGraph() { + var graph = {}; + // https://jsperf.com/object-keys-vs-for-in-with-closure/3 + var models = Object.keys(conversions); + + for (var len = models.length, i = 0; i < len; i++) { + graph[models[i]] = { + // http://jsperf.com/1-vs-infinity + // micro-opt, but this is simple. + distance: -1, + parent: null + }; + } + + return graph; +} + +// https://en.wikipedia.org/wiki/Breadth-first_search +function deriveBFS(fromModel) { + var graph = buildGraph(); + var queue = [fromModel]; // unshift -> queue -> pop + + graph[fromModel].distance = 0; + + while (queue.length) { + var current = queue.pop(); + var adjacents = Object.keys(conversions[current]); + + for (var len = adjacents.length, i = 0; i < len; i++) { + var adjacent = adjacents[i]; + var node = graph[adjacent]; + + if (node.distance === -1) { + node.distance = graph[current].distance + 1; + node.parent = current; + queue.unshift(adjacent); + } + } + } + + return graph; +} + +function link(from, to) { + return function (args) { + return to(from(args)); + }; +} + +function wrapConversion(toModel, graph) { + var path = [graph[toModel].parent, toModel]; + var fn = conversions[graph[toModel].parent][toModel]; + + var cur = graph[toModel].parent; + while (graph[cur].parent) { + path.unshift(graph[cur].parent); + fn = link(conversions[graph[cur].parent][cur], fn); + cur = graph[cur].parent; + } + + fn.conversion = path; + return fn; +} + +var route = function (fromModel) { + var graph = deriveBFS(fromModel); + var conversion = {}; + + var models = Object.keys(graph); + for (var len = models.length, i = 0; i < len; i++) { + var toModel = models[i]; + var node = graph[toModel]; + + if (node.parent === null) { + // no possible conversion, or this node is the source model. + continue; + } + + conversion[toModel] = wrapConversion(toModel, graph); + } + + return conversion; +}; + +var convert = {}; + +var models = Object.keys(conversions); + +function wrapRaw(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + return fn(args); + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +function wrapRounded(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + + var result = fn(args); + + // we're assuming the result is an array here. + // see notice in conversions.js; don't use box types + // in conversion functions. + if (typeof result === 'object') { + for (var len = result.length, i = 0; i < len; i++) { + result[i] = Math.round(result[i]); + } + } + + return result; + }; + + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; +} + +models.forEach(function (fromModel) { + convert[fromModel] = {}; + + Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); + Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); + + var routes = route(fromModel); + var routeModels = Object.keys(routes); + + routeModels.forEach(function (toModel) { + var fn = routes[toModel]; + + convert[fromModel][toModel] = wrapRounded(fn); + convert[fromModel][toModel].raw = wrapRaw(fn); + }); +}); + +var colorConvert = convert; + +var colorName$1 = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +/* MIT license */ + + +var colorString = { + getRgba: getRgba, + getHsla: getHsla, + getRgb: getRgb, + getHsl: getHsl, + getHwb: getHwb, + getAlpha: getAlpha, + + hexString: hexString, + rgbString: rgbString, + rgbaString: rgbaString, + percentString: percentString, + percentaString: percentaString, + hslString: hslString, + hslaString: hslaString, + hwbString: hwbString, + keyword: keyword +}; + +function getRgba(string) { + if (!string) { + return; + } + var abbr = /^#([a-fA-F0-9]{3,4})$/i, + hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, + rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, + keyword = /(\w+)/; + + var rgb = [0, 0, 0], + a = 1, + match = string.match(abbr), + hexAlpha = ""; + if (match) { + match = match[1]; + hexAlpha = match[3]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i] + match[i], 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(hex)) { + hexAlpha = match[2]; + match = match[1]; + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); + } + if (hexAlpha) { + a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; + } + } + else if (match = string.match(rgba)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = parseInt(match[i + 1]); + } + a = parseFloat(match[4]); + } + else if (match = string.match(per)) { + for (var i = 0; i < rgb.length; i++) { + rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); + } + a = parseFloat(match[4]); + } + else if (match = string.match(keyword)) { + if (match[1] == "transparent") { + return [0, 0, 0, 0]; + } + rgb = colorName$1[match[1]]; + if (!rgb) { + return; + } + } + + for (var i = 0; i < rgb.length; i++) { + rgb[i] = scale(rgb[i], 0, 255); + } + if (!a && a != 0) { + a = 1; + } + else { + a = scale(a, 0, 1); + } + rgb[3] = a; + return rgb; +} + +function getHsla(string) { + if (!string) { + return; + } + var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hsl); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + s = scale(parseFloat(match[2]), 0, 100), + l = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, s, l, a]; + } +} + +function getHwb(string) { + if (!string) { + return; + } + var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; + var match = string.match(hwb); + if (match) { + var alpha = parseFloat(match[4]); + var h = scale(parseInt(match[1]), 0, 360), + w = scale(parseFloat(match[2]), 0, 100), + b = scale(parseFloat(match[3]), 0, 100), + a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); + return [h, w, b, a]; + } +} + +function getRgb(string) { + var rgba = getRgba(string); + return rgba && rgba.slice(0, 3); +} + +function getHsl(string) { + var hsla = getHsla(string); + return hsla && hsla.slice(0, 3); +} + +function getAlpha(string) { + var vals = getRgba(string); + if (vals) { + return vals[3]; + } + else if (vals = getHsla(string)) { + return vals[3]; + } + else if (vals = getHwb(string)) { + return vals[3]; + } +} + +// generators +function hexString(rgba, a) { + var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; + return "#" + hexDouble(rgba[0]) + + hexDouble(rgba[1]) + + hexDouble(rgba[2]) + + ( + (a >= 0 && a < 1) + ? hexDouble(Math.round(a * 255)) + : "" + ); +} + +function rgbString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return rgbaString(rgba, alpha); + } + return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; +} + +function rgbaString(rgba, alpha) { + if (alpha === undefined) { + alpha = (rgba[3] !== undefined ? rgba[3] : 1); + } + return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + + ", " + alpha + ")"; +} + +function percentString(rgba, alpha) { + if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { + return percentaString(rgba, alpha); + } + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + + return "rgb(" + r + "%, " + g + "%, " + b + "%)"; +} + +function percentaString(rgba, alpha) { + var r = Math.round(rgba[0]/255 * 100), + g = Math.round(rgba[1]/255 * 100), + b = Math.round(rgba[2]/255 * 100); + return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; +} + +function hslString(hsla, alpha) { + if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { + return hslaString(hsla, alpha); + } + return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; +} + +function hslaString(hsla, alpha) { + if (alpha === undefined) { + alpha = (hsla[3] !== undefined ? hsla[3] : 1); + } + return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " + + alpha + ")"; +} + +// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax +// (hwb have alpha optional & 1 is default value) +function hwbString(hwb, alpha) { + if (alpha === undefined) { + alpha = (hwb[3] !== undefined ? hwb[3] : 1); + } + return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" + + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; +} + +function keyword(rgb) { + return reverseNames[rgb.slice(0, 3)]; +} + +// helpers +function scale(num, min, max) { + return Math.min(Math.max(min, num), max); +} + +function hexDouble(num) { + var str = num.toString(16).toUpperCase(); + return (str.length < 2) ? "0" + str : str; +} + + +//create a list of reverse color names +var reverseNames = {}; +for (var name in colorName$1) { + reverseNames[colorName$1[name]] = name; +} + +/* MIT license */ + + + +var Color = function (obj) { + if (obj instanceof Color) { + return obj; + } + if (!(this instanceof Color)) { + return new Color(obj); + } + + this.valid = false; + this.values = { + rgb: [0, 0, 0], + hsl: [0, 0, 0], + hsv: [0, 0, 0], + hwb: [0, 0, 0], + cmyk: [0, 0, 0, 0], + alpha: 1 + }; + + // parse Color() argument + var vals; + if (typeof obj === 'string') { + vals = colorString.getRgba(obj); + if (vals) { + this.setValues('rgb', vals); + } else if (vals = colorString.getHsla(obj)) { + this.setValues('hsl', vals); + } else if (vals = colorString.getHwb(obj)) { + this.setValues('hwb', vals); + } + } else if (typeof obj === 'object') { + vals = obj; + if (vals.r !== undefined || vals.red !== undefined) { + this.setValues('rgb', vals); + } else if (vals.l !== undefined || vals.lightness !== undefined) { + this.setValues('hsl', vals); + } else if (vals.v !== undefined || vals.value !== undefined) { + this.setValues('hsv', vals); + } else if (vals.w !== undefined || vals.whiteness !== undefined) { + this.setValues('hwb', vals); + } else if (vals.c !== undefined || vals.cyan !== undefined) { + this.setValues('cmyk', vals); + } + } +}; + +Color.prototype = { + isValid: function () { + return this.valid; + }, + rgb: function () { + return this.setSpace('rgb', arguments); + }, + hsl: function () { + return this.setSpace('hsl', arguments); + }, + hsv: function () { + return this.setSpace('hsv', arguments); + }, + hwb: function () { + return this.setSpace('hwb', arguments); + }, + cmyk: function () { + return this.setSpace('cmyk', arguments); + }, + + rgbArray: function () { + return this.values.rgb; + }, + hslArray: function () { + return this.values.hsl; + }, + hsvArray: function () { + return this.values.hsv; + }, + hwbArray: function () { + var values = this.values; + if (values.alpha !== 1) { + return values.hwb.concat([values.alpha]); + } + return values.hwb; + }, + cmykArray: function () { + return this.values.cmyk; + }, + rgbaArray: function () { + var values = this.values; + return values.rgb.concat([values.alpha]); + }, + hslaArray: function () { + var values = this.values; + return values.hsl.concat([values.alpha]); + }, + alpha: function (val) { + if (val === undefined) { + return this.values.alpha; + } + this.setValues('alpha', val); + return this; + }, + + red: function (val) { + return this.setChannel('rgb', 0, val); + }, + green: function (val) { + return this.setChannel('rgb', 1, val); + }, + blue: function (val) { + return this.setChannel('rgb', 2, val); + }, + hue: function (val) { + if (val) { + val %= 360; + val = val < 0 ? 360 + val : val; + } + return this.setChannel('hsl', 0, val); + }, + saturation: function (val) { + return this.setChannel('hsl', 1, val); + }, + lightness: function (val) { + return this.setChannel('hsl', 2, val); + }, + saturationv: function (val) { + return this.setChannel('hsv', 1, val); + }, + whiteness: function (val) { + return this.setChannel('hwb', 1, val); + }, + blackness: function (val) { + return this.setChannel('hwb', 2, val); + }, + value: function (val) { + return this.setChannel('hsv', 2, val); + }, + cyan: function (val) { + return this.setChannel('cmyk', 0, val); + }, + magenta: function (val) { + return this.setChannel('cmyk', 1, val); + }, + yellow: function (val) { + return this.setChannel('cmyk', 2, val); + }, + black: function (val) { + return this.setChannel('cmyk', 3, val); + }, + + hexString: function () { + return colorString.hexString(this.values.rgb); + }, + rgbString: function () { + return colorString.rgbString(this.values.rgb, this.values.alpha); + }, + rgbaString: function () { + return colorString.rgbaString(this.values.rgb, this.values.alpha); + }, + percentString: function () { + return colorString.percentString(this.values.rgb, this.values.alpha); + }, + hslString: function () { + return colorString.hslString(this.values.hsl, this.values.alpha); + }, + hslaString: function () { + return colorString.hslaString(this.values.hsl, this.values.alpha); + }, + hwbString: function () { + return colorString.hwbString(this.values.hwb, this.values.alpha); + }, + keyword: function () { + return colorString.keyword(this.values.rgb, this.values.alpha); + }, + + rgbNumber: function () { + var rgb = this.values.rgb; + return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; + }, + + luminosity: function () { + // http://www.w3.org/TR/WCAG20/#relativeluminancedef + var rgb = this.values.rgb; + var lum = []; + for (var i = 0; i < rgb.length; i++) { + var chan = rgb[i] / 255; + lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); + } + return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; + }, + + contrast: function (color2) { + // http://www.w3.org/TR/WCAG20/#contrast-ratiodef + var lum1 = this.luminosity(); + var lum2 = color2.luminosity(); + if (lum1 > lum2) { + return (lum1 + 0.05) / (lum2 + 0.05); + } + return (lum2 + 0.05) / (lum1 + 0.05); + }, + + level: function (color2) { + var contrastRatio = this.contrast(color2); + if (contrastRatio >= 7.1) { + return 'AAA'; + } + + return (contrastRatio >= 4.5) ? 'AA' : ''; + }, + + dark: function () { + // YIQ equation from http://24ways.org/2010/calculating-color-contrast + var rgb = this.values.rgb; + var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; + return yiq < 128; + }, + + light: function () { + return !this.dark(); + }, + + negate: function () { + var rgb = []; + for (var i = 0; i < 3; i++) { + rgb[i] = 255 - this.values.rgb[i]; + } + this.setValues('rgb', rgb); + return this; + }, + + lighten: function (ratio) { + var hsl = this.values.hsl; + hsl[2] += hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + darken: function (ratio) { + var hsl = this.values.hsl; + hsl[2] -= hsl[2] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + saturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] += hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + desaturate: function (ratio) { + var hsl = this.values.hsl; + hsl[1] -= hsl[1] * ratio; + this.setValues('hsl', hsl); + return this; + }, + + whiten: function (ratio) { + var hwb = this.values.hwb; + hwb[1] += hwb[1] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + blacken: function (ratio) { + var hwb = this.values.hwb; + hwb[2] += hwb[2] * ratio; + this.setValues('hwb', hwb); + return this; + }, + + greyscale: function () { + var rgb = this.values.rgb; + // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale + var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; + this.setValues('rgb', [val, val, val]); + return this; + }, + + clearer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha - (alpha * ratio)); + return this; + }, + + opaquer: function (ratio) { + var alpha = this.values.alpha; + this.setValues('alpha', alpha + (alpha * ratio)); + return this; + }, + + rotate: function (degrees) { + var hsl = this.values.hsl; + var hue = (hsl[0] + degrees) % 360; + hsl[0] = hue < 0 ? 360 + hue : hue; + this.setValues('hsl', hsl); + return this; + }, + + /** + * Ported from sass implementation in C + * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 + */ + mix: function (mixinColor, weight) { + var color1 = this; + var color2 = mixinColor; + var p = weight === undefined ? 0.5 : weight; + + var w = 2 * p - 1; + var a = color1.alpha() - color2.alpha(); + + var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; + var w2 = 1 - w1; + + return this + .rgb( + w1 * color1.red() + w2 * color2.red(), + w1 * color1.green() + w2 * color2.green(), + w1 * color1.blue() + w2 * color2.blue() + ) + .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); + }, + + toJSON: function () { + return this.rgb(); + }, + + clone: function () { + // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, + // making the final build way to big to embed in Chart.js. So let's do it manually, + // assuming that values to clone are 1 dimension arrays containing only numbers, + // except 'alpha' which is a number. + var result = new Color(); + var source = this.values; + var target = result.values; + var value, type; + + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + value = source[prop]; + type = ({}).toString.call(value); + if (type === '[object Array]') { + target[prop] = value.slice(0); + } else if (type === '[object Number]') { + target[prop] = value; + } else { + console.error('unexpected color value:', value); + } + } + } + + return result; + } +}; + +Color.prototype.spaces = { + rgb: ['red', 'green', 'blue'], + hsl: ['hue', 'saturation', 'lightness'], + hsv: ['hue', 'saturation', 'value'], + hwb: ['hue', 'whiteness', 'blackness'], + cmyk: ['cyan', 'magenta', 'yellow', 'black'] +}; + +Color.prototype.maxes = { + rgb: [255, 255, 255], + hsl: [360, 100, 100], + hsv: [360, 100, 100], + hwb: [360, 100, 100], + cmyk: [100, 100, 100, 100] +}; + +Color.prototype.getValues = function (space) { + var values = this.values; + var vals = {}; + + for (var i = 0; i < space.length; i++) { + vals[space.charAt(i)] = values[space][i]; + } + + if (values.alpha !== 1) { + vals.a = values.alpha; + } + + // {r: 255, g: 255, b: 255, a: 0.4} + return vals; +}; + +Color.prototype.setValues = function (space, vals) { + var values = this.values; + var spaces = this.spaces; + var maxes = this.maxes; + var alpha = 1; + var i; + + this.valid = true; + + if (space === 'alpha') { + alpha = vals; + } else if (vals.length) { + // [10, 10, 10] + values[space] = vals.slice(0, space.length); + alpha = vals[space.length]; + } else if (vals[space.charAt(0)] !== undefined) { + // {r: 10, g: 10, b: 10} + for (i = 0; i < space.length; i++) { + values[space][i] = vals[space.charAt(i)]; + } + + alpha = vals.a; + } else if (vals[spaces[space][0]] !== undefined) { + // {red: 10, green: 10, blue: 10} + var chans = spaces[space]; + + for (i = 0; i < space.length; i++) { + values[space][i] = vals[chans[i]]; + } + + alpha = vals.alpha; + } + + values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); + + if (space === 'alpha') { + return false; + } + + var capped; + + // cap values of the space prior converting all values + for (i = 0; i < space.length; i++) { + capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); + values[space][i] = Math.round(capped); + } + + // convert to all the other color spaces + for (var sname in spaces) { + if (sname !== space) { + values[sname] = colorConvert[space][sname](values[space]); + } + } + + return true; +}; + +Color.prototype.setSpace = function (space, args) { + var vals = args[0]; + + if (vals === undefined) { + // color.rgb() + return this.getValues(space); + } + + // color.rgb(10, 10, 10) + if (typeof vals === 'number') { + vals = Array.prototype.slice.call(args); + } + + this.setValues(space, vals); + return this; +}; + +Color.prototype.setChannel = function (space, index, val) { + var svalues = this.values[space]; + if (val === undefined) { + // color.red() + return svalues[index]; + } else if (val === svalues[index]) { + // color.red(color.red()) + return this; + } + + // color.red(100) + svalues[index] = val; + this.setValues(space, svalues); + + return this; +}; + +if (typeof window !== 'undefined') { + window.Color = Color; +} + +var chartjsColor = Color; + +/** + * @namespace Chart.helpers + */ +var helpers = { + /** + * An empty function that can be used, for example, for optional callback. + */ + noop: function() {}, + + /** + * Returns a unique id, sequentially generated from a global variable. + * @returns {number} + * @function + */ + uid: (function() { + var id = 0; + return function() { + return id++; + }; + }()), + + /** + * Returns true if `value` is neither null nor undefined, else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isNullOrUndef: function(value) { + return value === null || typeof value === 'undefined'; + }, + + /** + * Returns true if `value` is an array (including typed arrays), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @function + */ + isArray: function(value) { + if (Array.isArray && Array.isArray(value)) { + return true; + } + var type = Object.prototype.toString.call(value); + if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { + return true; + } + return false; + }, + + /** + * Returns true if `value` is an object (excluding null), else returns false. + * @param {*} value - The value to test. + * @returns {boolean} + * @since 2.7.0 + */ + isObject: function(value) { + return value !== null && Object.prototype.toString.call(value) === '[object Object]'; + }, + + /** + * Returns true if `value` is a finite number, else returns false + * @param {*} value - The value to test. + * @returns {boolean} + */ + isFinite: function(value) { + return (typeof value === 'number' || value instanceof Number) && isFinite(value); + }, + + /** + * Returns `value` if defined, else returns `defaultValue`. + * @param {*} value - The value to return if defined. + * @param {*} defaultValue - The value to return if `value` is undefined. + * @returns {*} + */ + valueOrDefault: function(value, defaultValue) { + return typeof value === 'undefined' ? defaultValue : value; + }, + + /** + * Returns value at the given `index` in array if defined, else returns `defaultValue`. + * @param {Array} value - The array to lookup for value at `index`. + * @param {number} index - The index in `value` to lookup for value. + * @param {*} defaultValue - The value to return if `value[index]` is undefined. + * @returns {*} + */ + valueAtIndexOrDefault: function(value, index, defaultValue) { + return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); + }, + + /** + * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the + * value returned by `fn`. If `fn` is not a function, this method returns undefined. + * @param {function} fn - The function to call. + * @param {Array|undefined|null} args - The arguments with which `fn` should be called. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @returns {*} + */ + callback: function(fn, args, thisArg) { + if (fn && typeof fn.call === 'function') { + return fn.apply(thisArg, args); + } + }, + + /** + * Note(SB) for performance sake, this method should only be used when loopable type + * is unknown or in none intensive code (not called often and small loopable). Else + * it's preferable to use a regular for() loop and save extra function calls. + * @param {object|Array} loopable - The object or array to be iterated. + * @param {function} fn - The function to call for each item. + * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. + * @param {boolean} [reverse] - If true, iterates backward on the loopable. + */ + each: function(loopable, fn, thisArg, reverse) { + var i, len, keys; + if (helpers.isArray(loopable)) { + len = loopable.length; + if (reverse) { + for (i = len - 1; i >= 0; i--) { + fn.call(thisArg, loopable[i], i); + } + } else { + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[i], i); + } + } + } else if (helpers.isObject(loopable)) { + keys = Object.keys(loopable); + len = keys.length; + for (i = 0; i < len; i++) { + fn.call(thisArg, loopable[keys[i]], keys[i]); + } + } + }, + + /** + * Returns true if the `a0` and `a1` arrays have the same content, else returns false. + * @see https://stackoverflow.com/a/14853974 + * @param {Array} a0 - The array to compare + * @param {Array} a1 - The array to compare + * @returns {boolean} + */ + arrayEquals: function(a0, a1) { + var i, ilen, v0, v1; + + if (!a0 || !a1 || a0.length !== a1.length) { + return false; + } + + for (i = 0, ilen = a0.length; i < ilen; ++i) { + v0 = a0[i]; + v1 = a1[i]; + + if (v0 instanceof Array && v1 instanceof Array) { + if (!helpers.arrayEquals(v0, v1)) { + return false; + } + } else if (v0 !== v1) { + // NOTE: two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + + return true; + }, + + /** + * Returns a deep copy of `source` without keeping references on objects and arrays. + * @param {*} source - The value to clone. + * @returns {*} + */ + clone: function(source) { + if (helpers.isArray(source)) { + return source.map(helpers.clone); + } + + if (helpers.isObject(source)) { + var target = {}; + var keys = Object.keys(source); + var klen = keys.length; + var k = 0; + + for (; k < klen; ++k) { + target[keys[k]] = helpers.clone(source[keys[k]]); + } + + return target; + } + + return source; + }, + + /** + * The default merger when Chart.helpers.merge is called without merger option. + * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. + * @private + */ + _merger: function(key, target, source, options) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.merge(tval, sval, options); + } else { + target[key] = helpers.clone(sval); + } + }, + + /** + * Merges source[key] in target[key] only if target[key] is undefined. + * @private + */ + _mergerIf: function(key, target, source) { + var tval = target[key]; + var sval = source[key]; + + if (helpers.isObject(tval) && helpers.isObject(sval)) { + helpers.mergeIf(tval, sval); + } else if (!target.hasOwnProperty(key)) { + target[key] = helpers.clone(sval); + } + }, + + /** + * Recursively deep copies `source` properties into `target` with the given `options`. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @param {object} [options] - Merging options: + * @param {function} [options.merger] - The merge method (key, target, source, options) + * @returns {object} The `target` object. + */ + merge: function(target, source, options) { + var sources = helpers.isArray(source) ? source : [source]; + var ilen = sources.length; + var merge, i, keys, klen, k; + + if (!helpers.isObject(target)) { + return target; + } + + options = options || {}; + merge = options.merger || helpers._merger; + + for (i = 0; i < ilen; ++i) { + source = sources[i]; + if (!helpers.isObject(source)) { + continue; + } + + keys = Object.keys(source); + for (k = 0, klen = keys.length; k < klen; ++k) { + merge(keys[k], target, source, options); + } + } + + return target; + }, + + /** + * Recursively deep copies `source` properties into `target` *only* if not defined in target. + * IMPORTANT: `target` is not cloned and will be updated with `source` properties. + * @param {object} target - The target object in which all sources are merged into. + * @param {object|object[]} source - Object(s) to merge into `target`. + * @returns {object} The `target` object. + */ + mergeIf: function(target, source) { + return helpers.merge(target, source, {merger: helpers._mergerIf}); + }, + + /** + * Applies the contents of two or more objects together into the first object. + * @param {object} target - The target object in which all objects are merged into. + * @param {object} arg1 - Object containing additional properties to merge in target. + * @param {object} argN - Additional objects containing properties to merge in target. + * @returns {object} The `target` object. + */ + extend: Object.assign || function(target) { + return helpers.merge(target, [].slice.call(arguments, 1), { + merger: function(key, dst, src) { + dst[key] = src[key]; + } + }); + }, + + /** + * Basic javascript inheritance based on the model created in Backbone.js + */ + inherits: function(extensions) { + var me = this; + var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { + return me.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + + Surrogate.prototype = me.prototype; + ChartElement.prototype = new Surrogate(); + ChartElement.extend = helpers.inherits; + + if (extensions) { + helpers.extend(ChartElement.prototype, extensions); + } + + ChartElement.__super__ = me.prototype; + return ChartElement; + }, + + _deprecated: function(scope, value, previous, current) { + if (value !== undefined) { + console.warn(scope + ': "' + previous + + '" is deprecated. Please use "' + current + '" instead'); + } + } +}; + +var helpers_core = helpers; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.callback instead. + * @function Chart.helpers.callCallback + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +helpers.callCallback = helpers.callback; + +/** + * Provided for backward compatibility, use Array.prototype.indexOf instead. + * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ + * @function Chart.helpers.indexOf + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.indexOf = function(array, item, fromIndex) { + return Array.prototype.indexOf.call(array, item, fromIndex); +}; + +/** + * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. + * @function Chart.helpers.getValueOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueOrDefault = helpers.valueOrDefault; + +/** + * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. + * @function Chart.helpers.getValueAtIndexOrDefault + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; + +/** + * Easing functions adapted from Robert Penner's easing equations. + * @namespace Chart.helpers.easingEffects + * @see http://www.robertpenner.com/easing/ + */ +var effects = { + linear: function(t) { + return t; + }, + + easeInQuad: function(t) { + return t * t; + }, + + easeOutQuad: function(t) { + return -t * (t - 2); + }, + + easeInOutQuad: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t; + } + return -0.5 * ((--t) * (t - 2) - 1); + }, + + easeInCubic: function(t) { + return t * t * t; + }, + + easeOutCubic: function(t) { + return (t = t - 1) * t * t + 1; + }, + + easeInOutCubic: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t; + } + return 0.5 * ((t -= 2) * t * t + 2); + }, + + easeInQuart: function(t) { + return t * t * t * t; + }, + + easeOutQuart: function(t) { + return -((t = t - 1) * t * t * t - 1); + }, + + easeInOutQuart: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t; + } + return -0.5 * ((t -= 2) * t * t * t - 2); + }, + + easeInQuint: function(t) { + return t * t * t * t * t; + }, + + easeOutQuint: function(t) { + return (t = t - 1) * t * t * t * t + 1; + }, + + easeInOutQuint: function(t) { + if ((t /= 0.5) < 1) { + return 0.5 * t * t * t * t * t; + } + return 0.5 * ((t -= 2) * t * t * t * t + 2); + }, + + easeInSine: function(t) { + return -Math.cos(t * (Math.PI / 2)) + 1; + }, + + easeOutSine: function(t) { + return Math.sin(t * (Math.PI / 2)); + }, + + easeInOutSine: function(t) { + return -0.5 * (Math.cos(Math.PI * t) - 1); + }, + + easeInExpo: function(t) { + return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); + }, + + easeOutExpo: function(t) { + return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; + }, + + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 0.5) < 1) { + return 0.5 * Math.pow(2, 10 * (t - 1)); + } + return 0.5 * (-Math.pow(2, -10 * --t) + 2); + }, + + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -(Math.sqrt(1 - t * t) - 1); + }, + + easeOutCirc: function(t) { + return Math.sqrt(1 - (t = t - 1) * t); + }, + + easeInOutCirc: function(t) { + if ((t /= 0.5) < 1) { + return -0.5 * (Math.sqrt(1 - t * t) - 1); + } + return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + }, + + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if (!p) { + p = 0.3; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; + }, + + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 0.5) === 2) { + return 1; + } + if (!p) { + p = 0.45; + } + if (a < 1) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return t * t * ((s + 1) * t - s); + }, + + easeOutBack: function(t) { + var s = 1.70158; + return (t = t - 1) * t * ((s + 1) * t + s) + 1; + }, + + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 0.5) < 1) { + return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + + easeInBounce: function(t) { + return 1 - effects.easeOutBounce(1 - t); + }, + + easeOutBounce: function(t) { + if (t < (1 / 2.75)) { + return 7.5625 * t * t; + } + if (t < (2 / 2.75)) { + return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; + } + if (t < (2.5 / 2.75)) { + return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; + } + return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; + }, + + easeInOutBounce: function(t) { + if (t < 0.5) { + return effects.easeInBounce(t * 2) * 0.5; + } + return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; + } +}; + +var helpers_easing = { + effects: effects +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.easing.effects instead. + * @function Chart.helpers.easingEffects + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.easingEffects = effects; + +var PI = Math.PI; +var RAD_PER_DEG = PI / 180; +var DOUBLE_PI = PI * 2; +var HALF_PI = PI / 2; +var QUARTER_PI = PI / 4; +var TWO_THIRDS_PI = PI * 2 / 3; + +/** + * @namespace Chart.helpers.canvas + */ +var exports$1 = { + /** + * Clears the entire canvas associated to the given `chart`. + * @param {Chart} chart - The chart for which to clear the canvas. + */ + clear: function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }, + + /** + * Creates a "path" for a rectangle with rounded corners at position (x, y) with a + * given size (width, height) and the same `radius` for all corners. + * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. + * @param {number} x - The x axis of the coordinate for the rectangle starting point. + * @param {number} y - The y axis of the coordinate for the rectangle starting point. + * @param {number} width - The rectangle's width. + * @param {number} height - The rectangle's height. + * @param {number} radius - The rounded amount (in pixels) for the four corners. + * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? + */ + roundedRect: function(ctx, x, y, width, height, radius) { + if (radius) { + var r = Math.min(radius, height / 2, width / 2); + var left = x + r; + var top = y + r; + var right = x + width - r; + var bottom = y + height - r; + + ctx.moveTo(x, top); + if (left < right && top < bottom) { + ctx.arc(left, top, r, -PI, -HALF_PI); + ctx.arc(right, top, r, -HALF_PI, 0); + ctx.arc(right, bottom, r, 0, HALF_PI); + ctx.arc(left, bottom, r, HALF_PI, PI); + } else if (left < right) { + ctx.moveTo(left, y); + ctx.arc(right, top, r, -HALF_PI, HALF_PI); + ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); + } else if (top < bottom) { + ctx.arc(left, top, r, -PI, 0); + ctx.arc(left, bottom, r, 0, PI); + } else { + ctx.arc(left, top, r, -PI, PI); + } + ctx.closePath(); + ctx.moveTo(x, y); + } else { + ctx.rect(x, y, width, height); + } + }, + + drawPoint: function(ctx, style, radius, x, y, rotation) { + var type, xOffset, yOffset, size, cornerRadius; + var rad = (rotation || 0) * RAD_PER_DEG; + + if (style && typeof style === 'object') { + type = style.toString(); + if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { + ctx.save(); + ctx.translate(x, y); + ctx.rotate(rad); + ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); + ctx.restore(); + return; + } + } + + if (isNaN(radius) || radius <= 0) { + return; + } + + ctx.beginPath(); + + switch (style) { + // Default includes circle + default: + ctx.arc(x, y, radius, 0, DOUBLE_PI); + ctx.closePath(); + break; + case 'triangle': + ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + rad += TWO_THIRDS_PI; + ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); + ctx.closePath(); + break; + case 'rectRounded': + // NOTE: the rounded rect implementation changed to use `arc` instead of + // `quadraticCurveTo` since it generates better results when rect is + // almost a circle. 0.516 (instead of 0.5) produces results with visually + // closer proportion to the previous impl and it is inscribed in the + // circle with `radius`. For more details, see the following PRs: + // https://github.com/chartjs/Chart.js/issues/5597 + // https://github.com/chartjs/Chart.js/issues/5858 + cornerRadius = radius * 0.516; + size = radius - cornerRadius; + xOffset = Math.cos(rad + QUARTER_PI) * size; + yOffset = Math.sin(rad + QUARTER_PI) * size; + ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); + ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); + ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); + ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); + ctx.closePath(); + break; + case 'rect': + if (!rotation) { + size = Math.SQRT1_2 * radius; + ctx.rect(x - size, y - size, 2 * size, 2 * size); + break; + } + rad += QUARTER_PI; + /* falls through */ + case 'rectRot': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + yOffset, y - xOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.lineTo(x - yOffset, y + xOffset); + ctx.closePath(); + break; + case 'crossRot': + rad += QUARTER_PI; + /* falls through */ + case 'cross': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'star': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + rad += QUARTER_PI; + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + ctx.moveTo(x + yOffset, y - xOffset); + ctx.lineTo(x - yOffset, y + xOffset); + break; + case 'line': + xOffset = Math.cos(rad) * radius; + yOffset = Math.sin(rad) * radius; + ctx.moveTo(x - xOffset, y - yOffset); + ctx.lineTo(x + xOffset, y + yOffset); + break; + case 'dash': + ctx.moveTo(x, y); + ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); + break; + } + + ctx.fill(); + ctx.stroke(); + }, + + /** + * Returns true if the point is inside the rectangle + * @param {object} point - The point to test + * @param {object} area - The rectangle + * @returns {boolean} + * @private + */ + _isPointInArea: function(point, area) { + var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. + + return point.x > area.left - epsilon && point.x < area.right + epsilon && + point.y > area.top - epsilon && point.y < area.bottom + epsilon; + }, + + clipArea: function(ctx, area) { + ctx.save(); + ctx.beginPath(); + ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); + ctx.clip(); + }, + + unclipArea: function(ctx) { + ctx.restore(); + }, + + lineTo: function(ctx, previous, target, flip) { + var stepped = target.steppedLine; + if (stepped) { + if (stepped === 'middle') { + var midpoint = (previous.x + target.x) / 2.0; + ctx.lineTo(midpoint, flip ? target.y : previous.y); + ctx.lineTo(midpoint, flip ? previous.y : target.y); + } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { + ctx.lineTo(previous.x, target.y); + } else { + ctx.lineTo(target.x, previous.y); + } + ctx.lineTo(target.x, target.y); + return; + } + + if (!target.tension) { + ctx.lineTo(target.x, target.y); + return; + } + + ctx.bezierCurveTo( + flip ? previous.controlPointPreviousX : previous.controlPointNextX, + flip ? previous.controlPointPreviousY : previous.controlPointNextY, + flip ? target.controlPointNextX : target.controlPointPreviousX, + flip ? target.controlPointNextY : target.controlPointPreviousY, + target.x, + target.y); + } +}; + +var helpers_canvas = exports$1; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. + * @namespace Chart.helpers.clear + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.clear = exports$1.clear; + +/** + * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. + * @namespace Chart.helpers.drawRoundedRectangle + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers_core.drawRoundedRectangle = function(ctx) { + ctx.beginPath(); + exports$1.roundedRect.apply(exports$1, arguments); +}; + +var defaults = { + /** + * @private + */ + _set: function(scope, values) { + return helpers_core.merge(this[scope] || (this[scope] = {}), values); + } +}; + +// TODO(v3): remove 'global' from namespace. all default are global and +// there's inconsistency around which options are under 'global' +defaults._set('global', { + defaultColor: 'rgba(0,0,0,0.1)', + defaultFontColor: '#666', + defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + defaultFontSize: 12, + defaultFontStyle: 'normal', + defaultLineHeight: 1.2, + showLines: true +}); + +var core_defaults = defaults; + +var valueOrDefault = helpers_core.valueOrDefault; + +/** + * Converts the given font object into a CSS font string. + * @param {object} font - A font object. + * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font + * @private + */ +function toFontString(font) { + if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { + return null; + } + + return (font.style ? font.style + ' ' : '') + + (font.weight ? font.weight + ' ' : '') + + font.size + 'px ' + + font.family; +} + +/** + * @alias Chart.helpers.options + * @namespace + */ +var helpers_options = { + /** + * Converts the given line height `value` in pixels for a specific font `size`. + * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). + * @param {number} size - The font size (in pixels) used to resolve relative `value`. + * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height + * @since 2.7.0 + */ + toLineHeight: function(value, size) { + var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); + if (!matches || matches[1] === 'normal') { + return size * 1.2; + } + + value = +matches[2]; + + switch (matches[3]) { + case 'px': + return value; + case '%': + value /= 100; + break; + } + + return size * value; + }, + + /** + * Converts the given value into a padding object with pre-computed width/height. + * @param {number|object} value - If a number, set the value to all TRBL component, + * else, if and object, use defined properties and sets undefined ones to 0. + * @returns {object} The padding values (top, right, bottom, left, width, height) + * @since 2.7.0 + */ + toPadding: function(value) { + var t, r, b, l; + + if (helpers_core.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: t, + right: r, + bottom: b, + left: l, + height: t + b, + width: l + r + }; + }, + + /** + * Parses font options and returns the font object. + * @param {object} options - A object that contains font options to be parsed. + * @return {object} The font object. + * @todo Support font.* options and renamed to toFont(). + * @private + */ + _parseFont: function(options) { + var globalDefaults = core_defaults.global; + var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); + var font = { + family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), + lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), + size: size, + style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), + weight: null, + string: '' + }; + + font.string = toFontString(font); + return font; + }, + + /** + * Evaluates the given `inputs` sequentially and returns the first defined value. + * @param {Array} inputs - An array of values, falling back to the last value. + * @param {object} [context] - If defined and the current value is a function, the value + * is called with `context` as first argument and the result becomes the new input. + * @param {number} [index] - If defined and the current value is an array, the value + * at `index` become the new input. + * @param {object} [info] - object to return information about resolution in + * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. + * @since 2.7.0 + */ + resolve: function(inputs, context, index, info) { + var cacheable = true; + var i, ilen, value; + + for (i = 0, ilen = inputs.length; i < ilen; ++i) { + value = inputs[i]; + if (value === undefined) { + continue; + } + if (context !== undefined && typeof value === 'function') { + value = value(context); + cacheable = false; + } + if (index !== undefined && helpers_core.isArray(value)) { + value = value[index]; + cacheable = false; + } + if (value !== undefined) { + if (info && !cacheable) { + info.cacheable = false; + } + return value; + } + } + } +}; + +/** + * @alias Chart.helpers.math + * @namespace + */ +var exports$2 = { + /** + * Returns an array of factors sorted from 1 to sqrt(value) + * @private + */ + _factorize: function(value) { + var result = []; + var sqrt = Math.sqrt(value); + var i; + + for (i = 1; i < sqrt; i++) { + if (value % i === 0) { + result.push(i); + result.push(value / i); + } + } + if (sqrt === (sqrt | 0)) { // if value is a square number + result.push(sqrt); + } + + result.sort(function(a, b) { + return a - b; + }).pop(); + return result; + }, + + log10: Math.log10 || function(x) { + var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. + // Check for whole powers of 10, + // which due to floating point rounding error should be corrected. + var powerOf10 = Math.round(exponent); + var isPowerOf10 = x === Math.pow(10, powerOf10); + + return isPowerOf10 ? powerOf10 : exponent; + } +}; + +var helpers_math = exports$2; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.helpers.math.log10 instead. + * @namespace Chart.helpers.log10 + * @deprecated since version 2.9.0 + * @todo remove at version 3 + * @private + */ +helpers_core.log10 = exports$2.log10; + +var getRtlAdapter = function(rectX, width) { + return { + x: function(x) { + return rectX + rectX + width - x; + }, + setWidth: function(w) { + width = w; + }, + textAlign: function(align) { + if (align === 'center') { + return align; + } + return align === 'right' ? 'left' : 'right'; + }, + xPlus: function(x, value) { + return x - value; + }, + leftForLtr: function(x, itemWidth) { + return x - itemWidth; + }, + }; +}; + +var getLtrAdapter = function() { + return { + x: function(x) { + return x; + }, + setWidth: function(w) { // eslint-disable-line no-unused-vars + }, + textAlign: function(align) { + return align; + }, + xPlus: function(x, value) { + return x + value; + }, + leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars + return x; + }, + }; +}; + +var getAdapter = function(rtl, rectX, width) { + return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); +}; + +var overrideTextDirection = function(ctx, direction) { + var style, original; + if (direction === 'ltr' || direction === 'rtl') { + style = ctx.canvas.style; + original = [ + style.getPropertyValue('direction'), + style.getPropertyPriority('direction'), + ]; + + style.setProperty('direction', direction, 'important'); + ctx.prevTextDirection = original; + } +}; + +var restoreTextDirection = function(ctx) { + var original = ctx.prevTextDirection; + if (original !== undefined) { + delete ctx.prevTextDirection; + ctx.canvas.style.setProperty('direction', original[0], original[1]); + } +}; + +var helpers_rtl = { + getRtlAdapter: getAdapter, + overrideTextDirection: overrideTextDirection, + restoreTextDirection: restoreTextDirection, +}; + +var helpers$1 = helpers_core; +var easing = helpers_easing; +var canvas = helpers_canvas; +var options = helpers_options; +var math = helpers_math; +var rtl = helpers_rtl; +helpers$1.easing = easing; +helpers$1.canvas = canvas; +helpers$1.options = options; +helpers$1.math = math; +helpers$1.rtl = rtl; + +function interpolate(start, view, model, ease) { + var keys = Object.keys(model); + var i, ilen, key, actual, origin, target, type, c0, c1; + + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + + target = model[key]; + + // if a value is added to the model after pivot() has been called, the view + // doesn't contain it, so let's initialize the view to the target value. + if (!view.hasOwnProperty(key)) { + view[key] = target; + } + + actual = view[key]; + + if (actual === target || key[0] === '_') { + continue; + } + + if (!start.hasOwnProperty(key)) { + start[key] = actual; + } + + origin = start[key]; + + type = typeof target; + + if (type === typeof origin) { + if (type === 'string') { + c0 = chartjsColor(origin); + if (c0.valid) { + c1 = chartjsColor(target); + if (c1.valid) { + view[key] = c1.mix(c0, ease).rgbString(); + continue; + } + } + } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { + view[key] = origin + (target - origin) * ease; + continue; + } + } + + view[key] = target; + } +} + +var Element = function(configuration) { + helpers$1.extend(this, configuration); + this.initialize.apply(this, arguments); +}; + +helpers$1.extend(Element.prototype, { + _type: undefined, + + initialize: function() { + this.hidden = false; + }, + + pivot: function() { + var me = this; + if (!me._view) { + me._view = helpers$1.extend({}, me._model); + } + me._start = {}; + return me; + }, + + transition: function(ease) { + var me = this; + var model = me._model; + var start = me._start; + var view = me._view; + + // No animation -> No Transition + if (!model || ease === 1) { + me._view = helpers$1.extend({}, model); + me._start = null; + return me; + } + + if (!view) { + view = me._view = {}; + } + + if (!start) { + start = me._start = {}; + } + + interpolate(start, view, model, ease); + + return me; + }, + + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + + hasValue: function() { + return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); + } +}); + +Element.extend = helpers$1.inherits; + +var core_element = Element; + +var exports$3 = core_element.extend({ + chart: null, // the animation associated chart instance + currentStep: 0, // the current animation step + numSteps: 60, // default number of steps + easing: '', // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes +}); + +var core_animation = exports$3; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart.Animation instead + * @prop Chart.Animation#animationObject + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'animationObject', { + get: function() { + return this; + } +}); + +/** + * Provided for backward compatibility, use Chart.Animation#chart instead + * @prop Chart.Animation#chartInstance + * @deprecated since version 2.6.0 + * @todo remove at version 3 + */ +Object.defineProperty(exports$3.prototype, 'chartInstance', { + get: function() { + return this.chart; + }, + set: function(value) { + this.chart = value; + } +}); + +core_defaults._set('global', { + animation: { + duration: 1000, + easing: 'easeOutQuart', + onProgress: helpers$1.noop, + onComplete: helpers$1.noop + } +}); + +var core_animations = { + animations: [], + request: null, + + /** + * @param {Chart} chart - The chart to animate. + * @param {Chart.Animation} animation - The animation that we will animate. + * @param {number} duration - The animation duration in ms. + * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions + */ + addAnimation: function(chart, animation, duration, lazy) { + var animations = this.animations; + var i, ilen; + + animation.chart = chart; + animation.startTime = Date.now(); + animation.duration = duration; + + if (!lazy) { + chart.animating = true; + } + + for (i = 0, ilen = animations.length; i < ilen; ++i) { + if (animations[i].chart === chart) { + animations[i] = animation; + return; + } + } + + animations.push(animation); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (animations.length === 1) { + this.requestAnimationFrame(); + } + }, + + cancelAnimation: function(chart) { + var index = helpers$1.findIndex(this.animations, function(animation) { + return animation.chart === chart; + }); + + if (index !== -1) { + this.animations.splice(index, 1); + chart.animating = false; + } + }, + + requestAnimationFrame: function() { + var me = this; + if (me.request === null) { + // Skip animation frame requests until the active one is executed. + // This can happen when processing mouse events, e.g. 'mousemove' + // and 'mouseout' events will trigger multiple renders. + me.request = helpers$1.requestAnimFrame.call(window, function() { + me.request = null; + me.startDigest(); + }); + } + }, + + /** + * @private + */ + startDigest: function() { + var me = this; + + me.advance(); + + // Do we have more stuff to animate? + if (me.animations.length > 0) { + me.requestAnimationFrame(); + } + }, + + /** + * @private + */ + advance: function() { + var animations = this.animations; + var animation, chart, numSteps, nextStep; + var i = 0; + + // 1 animation per chart, so we are looping charts here + while (i < animations.length) { + animation = animations[i]; + chart = animation.chart; + numSteps = animation.numSteps; + + // Make sure that currentStep starts at 1 + // https://github.com/chartjs/Chart.js/issues/6104 + nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; + animation.currentStep = Math.min(nextStep, numSteps); + + helpers$1.callback(animation.render, [chart, animation], chart); + helpers$1.callback(animation.onAnimationProgress, [animation], chart); + + if (animation.currentStep >= numSteps) { + helpers$1.callback(animation.onAnimationComplete, [animation], chart); + chart.animating = false; + animations.splice(i, 1); + } else { + ++i; + } + } + } +}; + +var resolve = helpers$1.options.resolve; + +var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; + +/** + * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', + * 'unshift') and notify the listener AFTER the array has been altered. Listeners are + * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. + */ +function listenArrayEvents(array, listener) { + if (array._chartjs) { + array._chartjs.listeners.push(listener); + return; + } + + Object.defineProperty(array, '_chartjs', { + configurable: true, + enumerable: false, + value: { + listeners: [listener] + } + }); + + arrayEvents.forEach(function(key) { + var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); + var base = array[key]; + + Object.defineProperty(array, key, { + configurable: true, + enumerable: false, + value: function() { + var args = Array.prototype.slice.call(arguments); + var res = base.apply(this, args); + + helpers$1.each(array._chartjs.listeners, function(object) { + if (typeof object[method] === 'function') { + object[method].apply(object, args); + } + }); + + return res; + } + }); + }); +} + +/** + * Removes the given array event listener and cleanup extra attached properties (such as + * the _chartjs stub and overridden methods) if array doesn't have any more listeners. + */ +function unlistenArrayEvents(array, listener) { + var stub = array._chartjs; + if (!stub) { + return; + } + + var listeners = stub.listeners; + var index = listeners.indexOf(listener); + if (index !== -1) { + listeners.splice(index, 1); + } + + if (listeners.length > 0) { + return; + } + + arrayEvents.forEach(function(key) { + delete array[key]; + }); + + delete array._chartjs; +} + +// Base class for all dataset controllers (line, bar, etc) +var DatasetController = function(chart, datasetIndex) { + this.initialize(chart, datasetIndex); +}; + +helpers$1.extend(DatasetController.prototype, { + + /** + * Element type used to generate a meta dataset (e.g. Chart.element.Line). + * @type {Chart.core.element} + */ + datasetElementType: null, + + /** + * Element type used to generate a meta data (e.g. Chart.element.Point). + * @type {Chart.core.element} + */ + dataElementType: null, + + /** + * Dataset element option keys to be resolved in _resolveDatasetElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth' + ], + + /** + * Data element option keys to be resolved in _resolveDataElementOptions. + * A derived controller may override this to resolve controller-specific options. + * The keys defined here are for backward compatibility for legend styles. + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'pointStyle' + ], + + initialize: function(chart, datasetIndex) { + var me = this; + me.chart = chart; + me.index = datasetIndex; + me.linkScales(); + me.addElements(); + me._type = me.getMeta().type; + }, + + updateIndex: function(datasetIndex) { + this.index = datasetIndex; + }, + + linkScales: function() { + var me = this; + var meta = me.getMeta(); + var chart = me.chart; + var scales = chart.scales; + var dataset = me.getDataset(); + var scalesOpts = chart.options.scales; + + if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) { + meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id; + } + if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) { + meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getMeta: function() { + return this.chart.getDatasetMeta(this.index); + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().yAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getValueScale: function() { + return this.getScaleForId(this._getValueScaleId()); + }, + + /** + * @private + */ + _getIndexScale: function() { + return this.getScaleForId(this._getIndexScaleId()); + }, + + reset: function() { + this._update(true); + }, + + /** + * @private + */ + destroy: function() { + if (this._data) { + unlistenArrayEvents(this._data, this); + } + }, + + createMetaDataset: function() { + var me = this; + var type = me.datasetElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index + }); + }, + + createMetaData: function(index) { + var me = this; + var type = me.dataElementType; + return type && new type({ + _chart: me.chart, + _datasetIndex: me.index, + _index: index + }); + }, + + addElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data || []; + var metaData = meta.data; + var i, ilen; + + for (i = 0, ilen = data.length; i < ilen; ++i) { + metaData[i] = metaData[i] || me.createMetaData(i); + } + + meta.dataset = meta.dataset || me.createMetaDataset(); + }, + + addElementAndReset: function(index) { + var element = this.createMetaData(index); + this.getMeta().data.splice(index, 0, element); + this.updateElement(element, index, true); + }, + + buildOrUpdateElements: function() { + var me = this; + var dataset = me.getDataset(); + var data = dataset.data || (dataset.data = []); + + // In order to correctly handle data addition/deletion animation (an thus simulate + // real-time charts), we need to monitor these data modifications and synchronize + // the internal meta data accordingly. + if (me._data !== data) { + if (me._data) { + // This case happens when the user replaced the data array instance. + unlistenArrayEvents(me._data, me); + } + + if (data && Object.isExtensible(data)) { + listenArrayEvents(data, me); + } + me._data = data; + } + + // Re-sync meta data in case the user replaced the data array or if we missed + // any updates and so make sure that we handle number of datapoints changing. + me.resyncElements(); + }, + + /** + * Returns the merged user-supplied and default dataset-level options + * @private + */ + _configure: function() { + var me = this; + me._config = helpers$1.merge({}, [ + me.chart.options.datasets[me._type], + me.getDataset(), + ], { + merger: function(key, target, source) { + if (key !== '_meta' && key !== 'data') { + helpers$1._merger(key, target, source); + } + } + }); + }, + + _update: function(reset) { + var me = this; + me._configure(); + me._cachedDataOpts = null; + me.update(reset); + }, + + update: helpers$1.noop, + + transition: function(easingValue) { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + for (; i < ilen; ++i) { + elements[i].transition(easingValue); + } + + if (meta.dataset) { + meta.dataset.transition(easingValue); + } + }, + + draw: function() { + var meta = this.getMeta(); + var elements = meta.data || []; + var ilen = elements.length; + var i = 0; + + if (meta.dataset) { + meta.dataset.draw(); + } + + for (; i < ilen; ++i) { + elements[i].draw(); + } + }, + + /** + * Returns a set of predefined style properties that should be used to represent the dataset + * or the data if the index is specified + * @param {number} index - data index + * @return {IStyleInterface} style object + */ + getStyle: function(index) { + var me = this; + var meta = me.getMeta(); + var dataset = meta.dataset; + var style; + + me._configure(); + if (dataset && index === undefined) { + style = me._resolveDatasetElementOptions(dataset || {}); + } else { + index = index || 0; + style = me._resolveDataElementOptions(meta.data[index] || {}, index); + } + + if (style.fill === false || style.fill === null) { + style.backgroundColor = style.borderColor; + } + + return style; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element, hover) { + var me = this; + var chart = me.chart; + var datasetOpts = me._config; + var custom = element.custom || {}; + var options = chart.options.elements[me.datasetElementType.prototype._type] || {}; + var elementOptions = me._datasetElementOptions; + var values = {}; + var i, ilen, key, readKey; + + // Scriptable options + var context = { + chart: chart, + dataset: me.getDataset(), + datasetIndex: me.index, + hover: hover + }; + + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key; + values[key] = resolve([ + custom[readKey], + datasetOpts[readKey], + options[readKey] + ], context); + } + + return values; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(element, index) { + var me = this; + var custom = element && element.custom; + var cached = me._cachedDataOpts; + if (cached && !custom) { + return cached; + } + var chart = me.chart; + var datasetOpts = me._config; + var options = chart.options.elements[me.dataElementType.prototype._type] || {}; + var elementOptions = me._dataElementOptions; + var values = {}; + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: me.getDataset(), + datasetIndex: me.index + }; + + // `resolve` sets cacheable to `false` if any option is indexed or scripted + var info = {cacheable: !custom}; + + var keys, i, ilen, key; + + custom = custom || {}; + + if (helpers$1.isArray(elementOptions)) { + for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { + key = elementOptions[i]; + values[key] = resolve([ + custom[key], + datasetOpts[key], + options[key] + ], context, index, info); + } + } else { + keys = Object.keys(elementOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + values[key] = resolve([ + custom[key], + datasetOpts[elementOptions[key]], + datasetOpts[key], + options[key] + ], context, index, info); + } + } + + if (info.cacheable) { + me._cachedDataOpts = Object.freeze(values); + } + + return values; + }, + + removeHoverStyle: function(element) { + helpers$1.merge(element._model, element.$previousStyle || {}); + delete element.$previousStyle; + }, + + setHoverStyle: function(element) { + var dataset = this.chart.data.datasets[element._datasetIndex]; + var index = element._index; + var custom = element.custom || {}; + var model = element._model; + var getHoverColor = helpers$1.getHoverColor; + + element.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth + }; + + model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); + model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); + model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); + }, + + /** + * @private + */ + _removeDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + + if (element) { + this.removeHoverStyle(element); + } + }, + + /** + * @private + */ + _setDatasetHoverStyle: function() { + var element = this.getMeta().dataset; + var prev = {}; + var i, ilen, key, keys, hoverOptions, model; + + if (!element) { + return; + } + + model = element._model; + hoverOptions = this._resolveDatasetElementOptions(element, true); + + keys = Object.keys(hoverOptions); + for (i = 0, ilen = keys.length; i < ilen; ++i) { + key = keys[i]; + prev[key] = model[key]; + model[key] = hoverOptions[key]; + } + + element.$previousStyle = prev; + }, + + /** + * @private + */ + resyncElements: function() { + var me = this; + var meta = me.getMeta(); + var data = me.getDataset().data; + var numMeta = meta.data.length; + var numData = data.length; + + if (numData < numMeta) { + meta.data.splice(numData, numMeta - numData); + } else if (numData > numMeta) { + me.insertElements(numMeta, numData - numMeta); + } + }, + + /** + * @private + */ + insertElements: function(start, count) { + for (var i = 0; i < count; ++i) { + this.addElementAndReset(start + i); + } + }, + + /** + * @private + */ + onDataPush: function() { + var count = arguments.length; + this.insertElements(this.getDataset().data.length - count, count); + }, + + /** + * @private + */ + onDataPop: function() { + this.getMeta().data.pop(); + }, + + /** + * @private + */ + onDataShift: function() { + this.getMeta().data.shift(); + }, + + /** + * @private + */ + onDataSplice: function(start, count) { + this.getMeta().data.splice(start, count); + this.insertElements(start, arguments.length - 2); + }, + + /** + * @private + */ + onDataUnshift: function() { + this.insertElements(0, arguments.length); + } +}); + +DatasetController.extend = helpers$1.inherits; + +var core_datasetController = DatasetController; + +var TAU = Math.PI * 2; + +core_defaults._set('global', { + elements: { + arc: { + backgroundColor: core_defaults.global.defaultColor, + borderColor: '#fff', + borderWidth: 2, + borderAlign: 'center' + } + } +}); + +function clipArc(ctx, arc) { + var startAngle = arc.startAngle; + var endAngle = arc.endAngle; + var pixelMargin = arc.pixelMargin; + var angleMargin = pixelMargin / arc.outerRadius; + var x = arc.x; + var y = arc.y; + + // Draw an inner border by cliping the arc and drawing a double-width border + // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders + ctx.beginPath(); + ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin); + if (arc.innerRadius > pixelMargin) { + angleMargin = pixelMargin / arc.innerRadius; + ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true); + } else { + ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2); + } + ctx.closePath(); + ctx.clip(); +} + +function drawFullCircleBorders(ctx, vm, arc, inner) { + var endAngle = arc.endAngle; + var i; + + if (inner) { + arc.endAngle = arc.startAngle + TAU; + clipArc(ctx, arc); + arc.endAngle = endAngle; + if (arc.endAngle === arc.startAngle && arc.fullCircles) { + arc.endAngle += TAU; + arc.fullCircles--; + } + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.stroke(); + } +} + +function drawBorder(ctx, vm, arc) { + var inner = vm.borderAlign === 'inner'; + + if (inner) { + ctx.lineWidth = vm.borderWidth * 2; + ctx.lineJoin = 'round'; + } else { + ctx.lineWidth = vm.borderWidth; + ctx.lineJoin = 'bevel'; + } + + if (arc.fullCircles) { + drawFullCircleBorders(ctx, vm, arc, inner); + } + + if (inner) { + clipArc(ctx, arc); + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.stroke(); +} + +var element_arc = core_element.extend({ + _type: 'arc', + + inLabelRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } + return false; + }, + + inRange: function(chartX, chartY) { + var vm = this._view; + + if (vm) { + var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); + var angle = pointRelativePosition.angle; + var distance = pointRelativePosition.distance; + + // Sanitise angle range + var startAngle = vm.startAngle; + var endAngle = vm.endAngle; + while (endAngle < startAngle) { + endAngle += TAU; + } + while (angle > endAngle) { + angle -= TAU; + } + while (angle < startAngle) { + angle += TAU; + } + + // Check if within the range of the open/close angle + var betweenAngles = (angle >= startAngle && angle <= endAngle); + var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); + + return (betweenAngles && withinRadius); + } + return false; + }, + + getCenterPoint: function() { + var vm = this._view; + var halfAngle = (vm.startAngle + vm.endAngle) / 2; + var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; + return { + x: vm.x + Math.cos(halfAngle) * halfRadius, + y: vm.y + Math.sin(halfAngle) * halfRadius + }; + }, + + getArea: function() { + var vm = this._view; + return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); + }, + + tooltipPosition: function() { + var vm = this._view; + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); + var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; + var arc = { + x: vm.x, + y: vm.y, + innerRadius: vm.innerRadius, + outerRadius: Math.max(vm.outerRadius - pixelMargin, 0), + pixelMargin: pixelMargin, + startAngle: vm.startAngle, + endAngle: vm.endAngle, + fullCircles: Math.floor(vm.circumference / TAU) + }; + var i; + + ctx.save(); + + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + + if (arc.fullCircles) { + arc.endAngle = arc.startAngle + TAU; + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + for (i = 0; i < arc.fullCircles; ++i) { + ctx.fill(); + } + arc.endAngle = arc.startAngle + vm.circumference % TAU; + } + + ctx.beginPath(); + ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); + ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); + ctx.closePath(); + ctx.fill(); + + if (vm.borderWidth) { + drawBorder(ctx, vm, arc); + } + + ctx.restore(); + } +}); + +var valueOrDefault$1 = helpers$1.valueOrDefault; + +var defaultColor = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + line: { + tension: 0.4, + backgroundColor: defaultColor, + borderWidth: 3, + borderColor: defaultColor, + borderCapStyle: 'butt', + borderDash: [], + borderDashOffset: 0.0, + borderJoinStyle: 'miter', + capBezierPoints: true, + fill: true, // do we fill in the area between the line and its base axis + } + } +}); + +var element_line = core_element.extend({ + _type: 'line', + + draw: function() { + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; + var spanGaps = vm.spanGaps; + var points = me._children.slice(); // clone array + var globalDefaults = core_defaults.global; + var globalOptionLineElements = globalDefaults.elements.line; + var lastDrawnIndex = -1; + var closePath = me._loop; + var index, previous, currentVM; + + if (!points.length) { + return; + } + + if (me._loop) { + for (index = 0; index < points.length; ++index) { + previous = helpers$1.previousItem(points, index); + // If the line has an open path, shift the point array + if (!points[index]._view.skip && previous._view.skip) { + points = points.slice(index).concat(points.slice(0, index)); + closePath = spanGaps; + break; + } + } + // If the line has a close path, add the first point again + if (closePath) { + points.push(points[0]); + } + } + + ctx.save(); + + // Stroke Line Options + ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; + + // IE 9 and 10 do not support line dash + if (ctx.setLineDash) { + ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); + } + + ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); + ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; + ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); + ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; + + // Stroke Line + ctx.beginPath(); + + // First point moves to it's starting position no matter what + currentVM = points[0]._view; + if (!currentVM.skip) { + ctx.moveTo(currentVM.x, currentVM.y); + lastDrawnIndex = 0; + } + + for (index = 1; index < points.length; ++index) { + currentVM = points[index]._view; + previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex]; + + if (!currentVM.skip) { + if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { + // There was a gap and this is the first point after the gap + ctx.moveTo(currentVM.x, currentVM.y); + } else { + // Line to next point + helpers$1.canvas.lineTo(ctx, previous._view, currentVM); + } + lastDrawnIndex = index; + } + } + + if (closePath) { + ctx.closePath(); + } + + ctx.stroke(); + ctx.restore(); + } +}); + +var valueOrDefault$2 = helpers$1.valueOrDefault; + +var defaultColor$1 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + point: { + radius: 3, + pointStyle: 'circle', + backgroundColor: defaultColor$1, + borderColor: defaultColor$1, + borderWidth: 1, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1 + } + } +}); + +function xRange(mouseX) { + var vm = this._view; + return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; +} + +function yRange(mouseY) { + var vm = this._view; + return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; +} + +var element_point = core_element.extend({ + _type: 'point', + + inRange: function(mouseX, mouseY) { + var vm = this._view; + return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; + }, + + inLabelRange: xRange, + inXRange: xRange, + inYRange: yRange, + + getCenterPoint: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + }, + + getArea: function() { + return Math.PI * Math.pow(this._view.radius, 2); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + + draw: function(chartArea) { + var vm = this._view; + var ctx = this._chart.ctx; + var pointStyle = vm.pointStyle; + var rotation = vm.rotation; + var radius = vm.radius; + var x = vm.x; + var y = vm.y; + var globalDefaults = core_defaults.global; + var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow + + if (vm.skip) { + return; + } + + // Clipping for Points. + if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { + ctx.strokeStyle = vm.borderColor || defaultColor; + ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); + ctx.fillStyle = vm.backgroundColor || defaultColor; + helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); + } + } +}); + +var defaultColor$2 = core_defaults.global.defaultColor; + +core_defaults._set('global', { + elements: { + rectangle: { + backgroundColor: defaultColor$2, + borderColor: defaultColor$2, + borderSkipped: 'bottom', + borderWidth: 0 + } + } +}); + +function isVertical(vm) { + return vm && vm.width !== undefined; +} + +/** + * Helper function to get the bounds of the bar regardless of the orientation + * @param bar {Chart.Element.Rectangle} the bar + * @return {Bounds} bounds of the bar + * @private + */ +function getBarBounds(vm) { + var x1, x2, y1, y2, half; + + if (isVertical(vm)) { + half = vm.width / 2; + x1 = vm.x - half; + x2 = vm.x + half; + y1 = Math.min(vm.y, vm.base); + y2 = Math.max(vm.y, vm.base); + } else { + half = vm.height / 2; + x1 = Math.min(vm.x, vm.base); + x2 = Math.max(vm.x, vm.base); + y1 = vm.y - half; + y2 = vm.y + half; + } + + return { + left: x1, + top: y1, + right: x2, + bottom: y2 + }; +} + +function swap(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} + +function parseBorderSkipped(vm) { + var edge = vm.borderSkipped; + var res = {}; + + if (!edge) { + return res; + } + + if (vm.horizontal) { + if (vm.base > vm.x) { + edge = swap(edge, 'left', 'right'); + } + } else if (vm.base < vm.y) { + edge = swap(edge, 'bottom', 'top'); + } + + res[edge] = true; + return res; +} + +function parseBorderWidth(vm, maxW, maxH) { + var value = vm.borderWidth; + var skip = parseBorderSkipped(vm); + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, + r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, + b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, + l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l + }; +} + +function boundingRects(vm) { + var bounds = getBarBounds(vm); + var width = bounds.right - bounds.left; + var height = bounds.bottom - bounds.top; + var border = parseBorderWidth(vm, width / 2, height / 2); + + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b + } + }; +} + +function inRange(vm, x, y) { + var skipX = x === null; + var skipY = y === null; + var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); + + return bounds + && (skipX || x >= bounds.left && x <= bounds.right) + && (skipY || y >= bounds.top && y <= bounds.bottom); +} + +var element_rectangle = core_element.extend({ + _type: 'rectangle', + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + var rects = boundingRects(vm); + var outer = rects.outer; + var inner = rects.inner; + + ctx.fillStyle = vm.backgroundColor; + ctx.fillRect(outer.x, outer.y, outer.w, outer.h); + + if (outer.w === inner.w && outer.h === inner.h) { + return; + } + + ctx.save(); + ctx.beginPath(); + ctx.rect(outer.x, outer.y, outer.w, outer.h); + ctx.clip(); + ctx.fillStyle = vm.borderColor; + ctx.rect(inner.x, inner.y, inner.w, inner.h); + ctx.fill('evenodd'); + ctx.restore(); + }, + + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + + inRange: function(mouseX, mouseY) { + return inRange(this._view, mouseX, mouseY); + }, + + inLabelRange: function(mouseX, mouseY) { + var vm = this._view; + return isVertical(vm) + ? inRange(vm, mouseX, null) + : inRange(vm, null, mouseY); + }, + + inXRange: function(mouseX) { + return inRange(this._view, mouseX, null); + }, + + inYRange: function(mouseY) { + return inRange(this._view, null, mouseY); + }, + + getCenterPoint: function() { + var vm = this._view; + var x, y; + if (isVertical(vm)) { + x = vm.x; + y = (vm.y + vm.base) / 2; + } else { + x = (vm.x + vm.base) / 2; + y = vm.y; + } + + return {x: x, y: y}; + }, + + getArea: function() { + var vm = this._view; + + return isVertical(vm) + ? vm.width * Math.abs(vm.y - vm.base) + : vm.height * Math.abs(vm.x - vm.base); + }, + + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y + }; + } +}); + +var elements = {}; +var Arc = element_arc; +var Line = element_line; +var Point = element_point; +var Rectangle = element_rectangle; +elements.Arc = Arc; +elements.Line = Line; +elements.Point = Point; +elements.Rectangle = Rectangle; + +var deprecated = helpers$1._deprecated; +var valueOrDefault$3 = helpers$1.valueOrDefault; + +core_defaults._set('bar', { + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + offset: true, + gridLines: { + offsetGridLines: true + } + }], + + yAxes: [{ + type: 'linear' + }] + } +}); + +core_defaults._set('global', { + datasets: { + bar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +/** + * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. + * @private + */ +function computeMinSampleSize(scale, pixels) { + var min = scale._length; + var prev, curr, i, ilen; + + for (i = 1, ilen = pixels.length; i < ilen; ++i) { + min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); + } + + for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { + curr = scale.getPixelForTick(i); + min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; + prev = curr; + } + + return min; +} + +/** + * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, + * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This + * mode currently always generates bars equally sized (until we introduce scriptable options?). + * @private + */ +function computeFitCategoryTraits(index, ruler, options) { + var thickness = options.barThickness; + var count = ruler.stackCount; + var curr = ruler.pixels[index]; + var min = helpers$1.isNullOrUndef(thickness) + ? computeMinSampleSize(ruler.scale, ruler.pixels) + : -1; + var size, ratio; + + if (helpers$1.isNullOrUndef(thickness)) { + size = min * options.categoryPercentage; + ratio = options.barPercentage; + } else { + // When bar thickness is enforced, category and bar percentages are ignored. + // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') + // and deprecate barPercentage since this value is ignored when thickness is absolute. + size = thickness * count; + ratio = 1; + } + + return { + chunk: size / count, + ratio: ratio, + start: curr - (size / 2) + }; +} + +/** + * Computes an "optimal" category that globally arranges bars side by side (no gap when + * percentage options are 1), based on the previous and following categories. This mode + * generates bars with different widths when data are not evenly spaced. + * @private + */ +function computeFlexCategoryTraits(index, ruler, options) { + var pixels = ruler.pixels; + var curr = pixels[index]; + var prev = index > 0 ? pixels[index - 1] : null; + var next = index < pixels.length - 1 ? pixels[index + 1] : null; + var percent = options.categoryPercentage; + var start, size; + + if (prev === null) { + // first data: its size is double based on the next point or, + // if it's also the last data, we use the scale size. + prev = curr - (next === null ? ruler.end - ruler.start : next - curr); + } + + if (next === null) { + // last data: its size is also double based on the previous point. + next = curr + curr - prev; + } + + start = curr - (curr - Math.min(prev, next)) / 2 * percent; + size = Math.abs(next - prev) / 2 * percent; + + return { + chunk: size / ruler.stackCount, + ratio: options.barPercentage, + start: start + }; +} + +var controller_bar = core_datasetController.extend({ + + dataElementType: elements.Rectangle, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderSkipped', + 'borderWidth', + 'barPercentage', + 'barThickness', + 'categoryPercentage', + 'maxBarThickness', + 'minBarLength' + ], + + initialize: function() { + var me = this; + var meta, scaleOpts; + + core_datasetController.prototype.initialize.apply(me, arguments); + + meta = me.getMeta(); + meta.stack = me.getDataset().stack; + meta.bar = true; + + scaleOpts = me._getIndexScale().options; + deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage'); + deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness'); + deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage'); + deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength'); + deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness'); + }, + + update: function(reset) { + var me = this; + var rects = me.getMeta().data; + var i, ilen; + + me._ruler = me.getRuler(); + + for (i = 0, ilen = rects.length; i < ilen; ++i) { + me.updateElement(rects[i], i, reset); + } + }, + + updateElement: function(rectangle, index, reset) { + var me = this; + var meta = me.getMeta(); + var dataset = me.getDataset(); + var options = me._resolveDataElementOptions(rectangle, index); + + rectangle._xScale = me.getScaleForId(meta.xAxisID); + rectangle._yScale = me.getScaleForId(meta.yAxisID); + rectangle._datasetIndex = me.index; + rectangle._index = index; + rectangle._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderSkipped: options.borderSkipped, + borderWidth: options.borderWidth, + datasetLabel: dataset.label, + label: me.chart.data.labels[index] + }; + + if (helpers$1.isArray(dataset.data[index])) { + rectangle._model.borderSkipped = null; + } + + me._updateElementGeometry(rectangle, index, reset, options); + + rectangle.pivot(); + }, + + /** + * @private + */ + _updateElementGeometry: function(rectangle, index, reset, options) { + var me = this; + var model = rectangle._model; + var vscale = me._getValueScale(); + var base = vscale.getBasePixel(); + var horizontal = vscale.isHorizontal(); + var ruler = me._ruler || me.getRuler(); + var vpixels = me.calculateBarValuePixels(me.index, index, options); + var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options); + + model.horizontal = horizontal; + model.base = reset ? base : vpixels.base; + model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; + model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; + model.height = horizontal ? ipixels.size : undefined; + model.width = horizontal ? undefined : ipixels.size; + }, + + /** + * Returns the stacks based on groups and bar visibility. + * @param {number} [last] - The dataset index + * @returns {string[]} The list of stack IDs + * @private + */ + _getStacks: function(last) { + var me = this; + var scale = me._getIndexScale(); + var metasets = scale._getMatchingVisibleMetas(me._type); + var stacked = scale.options.stacked; + var ilen = metasets.length; + var stacks = []; + var i, meta; + + for (i = 0; i < ilen; ++i) { + meta = metasets[i]; + // stacked | meta.stack + // | found | not found | undefined + // false | x | x | x + // true | | x | + // undefined | | x | x + if (stacked === false || stacks.indexOf(meta.stack) === -1 || + (stacked === undefined && meta.stack === undefined)) { + stacks.push(meta.stack); + } + if (meta.index === last) { + break; + } + } + + return stacks; + }, + + /** + * Returns the effective number of stacks based on groups and bar visibility. + * @private + */ + getStackCount: function() { + return this._getStacks().length; + }, + + /** + * Returns the stack index for the given dataset based on groups and bar visibility. + * @param {number} [datasetIndex] - The dataset index + * @param {string} [name] - The stack name to find + * @returns {number} The stack index + * @private + */ + getStackIndex: function(datasetIndex, name) { + var stacks = this._getStacks(datasetIndex); + var index = (name !== undefined) + ? stacks.indexOf(name) + : -1; // indexOf returns -1 if element is not present + + return (index === -1) + ? stacks.length - 1 + : index; + }, + + /** + * @private + */ + getRuler: function() { + var me = this; + var scale = me._getIndexScale(); + var pixels = []; + var i, ilen; + + for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { + pixels.push(scale.getPixelForValue(null, i, me.index)); + } + + return { + pixels: pixels, + start: scale._startPixel, + end: scale._endPixel, + stackCount: me.getStackCount(), + scale: scale + }; + }, + + /** + * Note: pixel values are not clamped to the scale area. + * @private + */ + calculateBarValuePixels: function(datasetIndex, index, options) { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var isHorizontal = scale.isHorizontal(); + var datasets = chart.data.datasets; + var metasets = scale._getMatchingVisibleMetas(me._type); + var value = scale._parseValue(datasets[datasetIndex].data[index]); + var minBarLength = options.minBarLength; + var stacked = scale.options.stacked; + var stack = me.getMeta().stack; + var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; + var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; + var ilen = metasets.length; + var i, imeta, ivalue, base, head, size, stackLength; + + if (stacked || (stacked === undefined && stack !== undefined)) { + for (i = 0; i < ilen; ++i) { + imeta = metasets[i]; + + if (imeta.index === datasetIndex) { + break; + } + + if (imeta.stack === stack) { + stackLength = scale._parseValue(datasets[imeta.index].data[index]); + ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; + + if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { + start += ivalue; + } + } + } + } + + base = scale.getPixelForValue(start); + head = scale.getPixelForValue(start + length); + size = head - base; + + if (minBarLength !== undefined && Math.abs(size) < minBarLength) { + size = minBarLength; + if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) { + head = base - minBarLength; + } else { + head = base + minBarLength; + } + } + + return { + size: size, + base: base, + head: head, + center: head + size / 2 + }; + }, + + /** + * @private + */ + calculateBarIndexPixels: function(datasetIndex, index, ruler, options) { + var me = this; + var range = options.barThickness === 'flex' + ? computeFlexCategoryTraits(index, ruler, options) + : computeFitCategoryTraits(index, ruler, options); + + var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); + var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); + var size = Math.min( + valueOrDefault$3(options.maxBarThickness, Infinity), + range.chunk * range.ratio); + + return { + base: center - size / 2, + head: center + size / 2, + center: center, + size: size + }; + }, + + draw: function() { + var me = this; + var chart = me.chart; + var scale = me._getValueScale(); + var rects = me.getMeta().data; + var dataset = me.getDataset(); + var ilen = rects.length; + var i = 0; + + helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); + + for (; i < ilen; ++i) { + var val = scale._parseValue(dataset.data[i]); + if (!isNaN(val.min) && !isNaN(val.max)) { + rects[i].draw(); + } + } + + helpers$1.canvas.unclipArea(chart.ctx); + }, + + /** + * @private + */ + _resolveDataElementOptions: function() { + var me = this; + var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments)); + var indexOpts = me._getIndexScale().options; + var valueOpts = me._getValueScale().options; + + values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage); + values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness); + values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage); + values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness); + values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength); + + return values; + } + +}); + +var valueOrDefault$4 = helpers$1.valueOrDefault; +var resolve$1 = helpers$1.options.resolve; + +core_defaults._set('bubble', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + type: 'linear', // bubble should probably use a linear scale by default + position: 'bottom', + id: 'x-axis-0' // need an ID so datasets can reference the scale + }], + yAxes: [{ + type: 'linear', + position: 'left', + id: 'y-axis-0' + }] + }, + + tooltips: { + callbacks: { + title: function() { + // Title doesn't make sense for scatter since we format the data as a point + return ''; + }, + label: function(item, data) { + var datasetLabel = data.datasets[item.datasetIndex].label || ''; + var dataPoint = data.datasets[item.datasetIndex].data[item.index]; + return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; + } + } + } +}); + +var controller_bubble = core_datasetController.extend({ + /** + * @protected + */ + dataElementType: elements.Point, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + 'hoverRadius', + 'hitRadius', + 'pointStyle', + 'rotation' + ], + + /** + * @protected + */ + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var points = meta.data; + + // Update Points + helpers$1.each(points, function(point, index) { + me.updateElement(point, index, reset); + }); + }, + + /** + * @protected + */ + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var xScale = me.getScaleForId(meta.xAxisID); + var yScale = me.getScaleForId(meta.yAxisID); + var options = me._resolveDataElementOptions(point, index); + var data = me.getDataset().data[index]; + var dsIndex = me.index; + + var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); + var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); + + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = dsIndex; + point._index = index; + point._model = { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + hitRadius: options.hitRadius, + pointStyle: options.pointStyle, + rotation: options.rotation, + radius: reset ? 0 : options.radius, + skip: custom.skip || isNaN(x) || isNaN(y), + x: x, + y: y, + }; + + point.pivot(); + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); + model.radius = options.radius + options.hoverRadius; + }, + + /** + * @private + */ + _resolveDataElementOptions: function(point, index) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var custom = point.custom || {}; + var data = dataset.data[index] || {}; + var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); + + // Scriptable options + var context = { + chart: chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + // In case values were cached (and thus frozen), we need to clone the values + if (me._cachedDataOpts === values) { + values = helpers$1.extend({}, values); + } + + // Custom radius resolution + values.radius = resolve$1([ + custom.radius, + data.r, + me._config.radius, + chart.options.elements.point.radius + ], context, index); + + return values; + } +}); + +var valueOrDefault$5 = helpers$1.valueOrDefault; + +var PI$1 = Math.PI; +var DOUBLE_PI$1 = PI$1 * 2; +var HALF_PI$1 = PI$1 / 2; + +core_defaults._set('doughnut', { + animation: { + // Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, + // Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false + }, + hover: { + mode: 'single' + }, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + // toggle visibility of index if exists + if (meta.data[index]) { + meta.data[index].hidden = !meta.data[index].hidden; + } + } + + chart.update(); + } + }, + + // The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, + + // The rotation of the chart, where the first data arc begins. + rotation: -HALF_PI$1, + + // The total circumference of the chart. + circumference: DOUBLE_PI$1, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(tooltipItem, data) { + var dataLabel = data.labels[tooltipItem.index]; + var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; + + if (helpers$1.isArray(dataLabel)) { + // show value on first line of multiline label + // need to clone because we are changing the value + dataLabel = dataLabel.slice(); + dataLabel[0] += value; + } else { + dataLabel += value; + } + + return dataLabel; + } + } + } +}); + +var controller_doughnut = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly + getRingIndex: function(datasetIndex) { + var ringIndex = 0; + + for (var j = 0; j < datasetIndex; ++j) { + if (this.chart.isDatasetVisible(j)) { + ++ringIndex; + } + } + + return ringIndex; + }, + + update: function(reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var ratioX = 1; + var ratioY = 1; + var offsetX = 0; + var offsetY = 0; + var meta = me.getMeta(); + var arcs = meta.data; + var cutout = opts.cutoutPercentage / 100 || 0; + var circumference = opts.circumference; + var chartWeight = me._getRingWeight(me.index); + var maxWidth, maxHeight, i, ilen; + + // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc + if (circumference < DOUBLE_PI$1) { + var startAngle = opts.rotation % DOUBLE_PI$1; + startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0; + var endAngle = startAngle + circumference; + var startX = Math.cos(startAngle); + var startY = Math.sin(startAngle); + var endX = Math.cos(endAngle); + var endY = Math.sin(endAngle); + var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1; + var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1; + var contains180 = startAngle === -PI$1 || endAngle >= PI$1; + var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1; + var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); + var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); + var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); + var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); + ratioX = (maxX - minX) / 2; + ratioY = (maxY - minY) / 2; + offsetX = -(maxX + minX) / 2; + offsetY = -(maxY + minY) / 2; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + } + + chart.borderWidth = me.getMaxBorderWidth(); + maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; + maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; + chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); + chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); + chart.offsetX = offsetX * chart.outerRadius; + chart.offsetY = offsetY * chart.outerRadius; + + meta.total = me.calculateTotal(); + + me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); + me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + me.updateElement(arcs[i], i, reset); + } + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var animationOpts = opts.animation; + var centerX = (chartArea.left + chartArea.right) / 2; + var centerY = (chartArea.top + chartArea.bottom) / 2; + var startAngle = opts.rotation; // non reset case handled later + var endAngle = opts.rotation; // non reset case handled later + var dataset = me.getDataset(); + var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1); + var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; + var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX + chart.offsetX, + y: centerY + chart.offsetY, + startAngle: startAngle, + endAngle: endAngle, + circumference: circumference, + outerRadius: outerRadius, + innerRadius: innerRadius, + label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) + } + }); + + var model = arc._model; + + // Set correct angles if not resetting + if (!reset || !animationOpts.animateRotate) { + if (index === 0) { + model.startAngle = opts.rotation; + } else { + model.startAngle = me.getMeta().data[index - 1]._model.endAngle; + } + + model.endAngle = model.startAngle + model.circumference; + } + + arc.pivot(); + }, + + calculateTotal: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var total = 0; + var value; + + helpers$1.each(meta.data, function(element, index) { + value = dataset.data[index]; + if (!isNaN(value) && !element.hidden) { + total += Math.abs(value); + } + }); + + /* if (total === 0) { + total = NaN; + }*/ + + return total; + }, + + calculateCircumference: function(value) { + var total = this.getMeta().total; + if (total > 0 && !isNaN(value)) { + return DOUBLE_PI$1 * (Math.abs(value) / total); + } + return 0; + }, + + // gets the max border or hover width to properly scale pie charts + getMaxBorderWidth: function(arcs) { + var me = this; + var max = 0; + var chart = me.chart; + var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; + + if (!arcs) { + // Find the outmost visible dataset + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + arcs = meta.data; + if (i !== me.index) { + controller = meta.controller; + } + break; + } + } + } + + if (!arcs) { + return 0; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arc = arcs[i]; + if (controller) { + controller._configure(); + options = controller._resolveDataElementOptions(arc, i); + } else { + options = arc._options; + } + if (options.borderAlign !== 'inner') { + borderWidth = options.borderWidth; + hoverWidth = options.hoverBorderWidth; + + max = borderWidth > max ? borderWidth : max; + max = hoverWidth > max ? hoverWidth : max; + } + } + return max; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly + * @private + */ + _getRingWeightOffset: function(datasetIndex) { + var ringWeightOffset = 0; + + for (var i = 0; i < datasetIndex; ++i) { + if (this.chart.isDatasetVisible(i)) { + ringWeightOffset += this._getRingWeight(i); + } + } + + return ringWeightOffset; + }, + + /** + * @private + */ + _getRingWeight: function(dataSetIndex) { + return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0); + }, + + /** + * Returns the sum of all visibile data set weights. This value can be 0. + * @private + */ + _getVisibleDatasetWeightTotal: function() { + return this._getRingWeightOffset(this.chart.data.datasets.length); + } +}); + +core_defaults._set('horizontalBar', { + hover: { + mode: 'index', + axis: 'y' + }, + + scales: { + xAxes: [{ + type: 'linear', + position: 'bottom' + }], + + yAxes: [{ + type: 'category', + position: 'left', + offset: true, + gridLines: { + offsetGridLines: true + } + }] + }, + + elements: { + rectangle: { + borderSkipped: 'left' + } + }, + + tooltips: { + mode: 'index', + axis: 'y' + } +}); + +core_defaults._set('global', { + datasets: { + horizontalBar: { + categoryPercentage: 0.8, + barPercentage: 0.9 + } + } +}); + +var controller_horizontalBar = controller_bar.extend({ + /** + * @private + */ + _getValueScaleId: function() { + return this.getMeta().xAxisID; + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.getMeta().yAxisID; + } +}); + +var valueOrDefault$6 = helpers$1.valueOrDefault; +var resolve$2 = helpers$1.options.resolve; +var isPointInArea = helpers$1.canvas._isPointInArea; + +core_defaults._set('line', { + showLines: true, + spanGaps: false, + + hover: { + mode: 'label' + }, + + scales: { + xAxes: [{ + type: 'category', + id: 'x-axis-0' + }], + yAxes: [{ + type: 'linear', + id: 'y-axis-0' + }] + } +}); + +function scaleClip(scale, halfBorderWidth) { + var tickOpts = scale && scale.options.ticks || {}; + var reverse = tickOpts.reverse; + var min = tickOpts.min === undefined ? halfBorderWidth : 0; + var max = tickOpts.max === undefined ? halfBorderWidth : 0; + return { + start: reverse ? max : min, + end: reverse ? min : max + }; +} + +function defaultClip(xScale, yScale, borderWidth) { + var halfBorderWidth = borderWidth / 2; + var x = scaleClip(xScale, halfBorderWidth); + var y = scaleClip(yScale, halfBorderWidth); + + return { + top: y.end, + right: x.end, + bottom: y.start, + left: x.start + }; +} + +function toClip(value) { + var t, r, b, l; + + if (helpers$1.isObject(value)) { + t = value.top; + r = value.right; + b = value.bottom; + l = value.left; + } else { + t = r = b = l = value; + } + + return { + top: t, + right: r, + bottom: b, + left: l + }; +} + + +var controller_line = core_datasetController.extend({ + + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderCapStyle', + 'borderColor', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'borderWidth', + 'cubicInterpolationMode', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var options = me.chart.options; + var config = me._config; + var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines); + var i, ilen; + + me._xScale = me.getScaleForId(meta.xAxisID); + me._yScale = me.getScaleForId(meta.yAxisID); + + // Update Line + if (showLine) { + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = me._yScale; + line._datasetIndex = me.index; + // Data + line._children = points; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + } + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + if (showLine && line._model.tension !== 0) { + me.updateBezierControlPoints(); + } + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var meta = me.getMeta(); + var custom = point.custom || {}; + var dataset = me.getDataset(); + var datasetIndex = me.index; + var value = dataset.data[index]; + var xScale = me._xScale; + var yScale = me._yScale; + var lineModel = meta.dataset._model; + var x, y; + + var options = me._resolveDataElementOptions(point, index); + + x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); + y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); + + // Utility + point._xScale = xScale; + point._yScale = yScale; + point._options = options; + point._datasetIndex = datasetIndex; + point._index = index; + + // Desired view properties + point._model = { + x: x, + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), + steppedLine: lineModel ? lineModel.steppedLine : false, + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function(element) { + var me = this; + var config = me._config; + var custom = element.custom || {}; + var options = me.chart.options; + var lineOptions = options.elements.line; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + // The default behavior of lines is to break at null values, according + // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 + // This option gives lines the ability to span gaps + values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension); + values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]); + values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth))); + + return values; + }, + + calculatePointY: function(value, index, datasetIndex) { + var me = this; + var chart = me.chart; + var yScale = me._yScale; + var sumPos = 0; + var sumNeg = 0; + var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen; + + if (yScale.options.stacked) { + rightValue = +yScale.getRightValue(value); + metasets = chart._getSortedVisibleDatasetMetas(); + ilen = metasets.length; + + for (i = 0; i < ilen; ++i) { + dsMeta = metasets[i]; + if (dsMeta.index === datasetIndex) { + break; + } + + ds = chart.data.datasets[dsMeta.index]; + if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { + stackedRightValue = +yScale.getRightValue(ds.data[index]); + if (stackedRightValue < 0) { + sumNeg += stackedRightValue || 0; + } else { + sumPos += stackedRightValue || 0; + } + } + } + + if (rightValue < 0) { + return yScale.getPixelForValue(sumNeg + rightValue); + } + return yScale.getPixelForValue(sumPos + rightValue); + } + return yScale.getPixelForValue(value); + }, + + updateBezierControlPoints: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var lineModel = meta.dataset._model; + var area = chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (lineModel.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + if (lineModel.cubicInterpolationMode === 'monotone') { + helpers$1.splineCurveMonotone(points); + } else { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i)._model, + model, + helpers$1.nextItem(points, i)._model, + lineModel.tension + ); + model.controlPointPreviousX = controlPoints.previous.x; + model.controlPointPreviousY = controlPoints.previous.y; + model.controlPointNextX = controlPoints.next.x; + model.controlPointNextY = controlPoints.next.y; + } + } + + if (chart.options.elements.line.capBezierPoints) { + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + if (isPointInArea(model, area)) { + if (i > 0 && isPointInArea(points[i - 1]._model, area)) { + model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); + model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); + } + if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { + model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); + model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); + } + } + } + } + }, + + draw: function() { + var me = this; + var chart = me.chart; + var meta = me.getMeta(); + var points = meta.data || []; + var area = chart.chartArea; + var canvas = chart.canvas; + var i = 0; + var ilen = points.length; + var clip; + + if (me._showLine) { + clip = meta.dataset._model.clip; + + helpers$1.canvas.clipArea(chart.ctx, { + left: clip.left === false ? 0 : area.left - clip.left, + right: clip.right === false ? canvas.width : area.right + clip.right, + top: clip.top === false ? 0 : area.top - clip.top, + bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom + }); + + meta.dataset.draw(); + + helpers$1.canvas.unclipArea(chart.ctx); + } + + // Draw the points + for (; i < ilen; ++i) { + points[i].draw(area); + } + }, + + /** + * @protected + */ + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); + model.radius = valueOrDefault$6(options.hoverRadius, options.radius); + }, +}); + +var resolve$3 = helpers$1.options.resolve; + +core_defaults._set('polarArea', { + scale: { + type: 'radialLinear', + angleLines: { + display: false + }, + gridLines: { + circular: true + }, + pointLabels: { + display: false + }, + ticks: { + beginAtZero: true + } + }, + + // Boolean - Whether to animate the rotation of the chart + animation: { + animateRotate: true, + animateScale: true + }, + + startAngle: -0.5 * Math.PI, + legendCallback: function(chart) { + var list = document.createElement('ul'); + var data = chart.data; + var datasets = data.datasets; + var labels = data.labels; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + if (datasets.length) { + for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; + if (labels[i]) { + listItem.appendChild(document.createTextNode(labels[i])); + } + } + } + + return list.outerHTML; + }, + legend: { + labels: { + generateLabels: function(chart) { + var data = chart.data; + if (data.labels.length && data.datasets.length) { + return data.labels.map(function(label, i) { + var meta = chart.getDatasetMeta(0); + var style = meta.controller.getStyle(i); + + return { + text: label, + fillStyle: style.backgroundColor, + strokeStyle: style.borderColor, + lineWidth: style.borderWidth, + hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, + + // Extra data used for toggling the correct item + index: i + }; + }); + } + return []; + } + }, + + onClick: function(e, legendItem) { + var index = legendItem.index; + var chart = this.chart; + var i, ilen, meta; + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + meta = chart.getDatasetMeta(i); + meta.data[index].hidden = !meta.data[index].hidden; + } + + chart.update(); + } + }, + + // Need to override these to give a nice default + tooltips: { + callbacks: { + title: function() { + return ''; + }, + label: function(item, data) { + return data.labels[item.index] + ': ' + item.yLabel; + } + } + } +}); + +var controller_polarArea = core_datasetController.extend({ + + dataElementType: elements.Arc, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _dataElementOptions: [ + 'backgroundColor', + 'borderColor', + 'borderWidth', + 'borderAlign', + 'hoverBackgroundColor', + 'hoverBorderColor', + 'hoverBorderWidth', + ], + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var dataset = me.getDataset(); + var meta = me.getMeta(); + var start = me.chart.options.startAngle || 0; + var starts = me._starts = []; + var angles = me._angles = []; + var arcs = meta.data; + var i, ilen, angle; + + me._updateRadius(); + + meta.count = me.countVisibleElements(); + + for (i = 0, ilen = dataset.data.length; i < ilen; i++) { + starts[i] = start; + angle = me._computeAngle(i); + angles[i] = angle; + start += angle; + } + + for (i = 0, ilen = arcs.length; i < ilen; ++i) { + arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); + me.updateElement(arcs[i], i, reset); + } + }, + + /** + * @private + */ + _updateRadius: function() { + var me = this; + var chart = me.chart; + var chartArea = chart.chartArea; + var opts = chart.options; + var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); + + chart.outerRadius = Math.max(minSize / 2, 0); + chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); + chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); + + me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); + me.innerRadius = me.outerRadius - chart.radiusLength; + }, + + updateElement: function(arc, index, reset) { + var me = this; + var chart = me.chart; + var dataset = me.getDataset(); + var opts = chart.options; + var animationOpts = opts.animation; + var scale = chart.scale; + var labels = chart.data.labels; + + var centerX = scale.xCenter; + var centerY = scale.yCenter; + + // var negHalfPI = -0.5 * Math.PI; + var datasetStartAngle = opts.startAngle; + var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var startAngle = me._starts[index]; + var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); + + var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); + var options = arc._options || {}; + + helpers$1.extend(arc, { + // Utility + _datasetIndex: me.index, + _index: index, + _scale: scale, + + // Desired view properties + _model: { + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + borderAlign: options.borderAlign, + x: centerX, + y: centerY, + innerRadius: 0, + outerRadius: reset ? resetRadius : distance, + startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, + endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, + label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) + } + }); + + arc.pivot(); + }, + + countVisibleElements: function() { + var dataset = this.getDataset(); + var meta = this.getMeta(); + var count = 0; + + helpers$1.each(meta.data, function(element, index) { + if (!isNaN(dataset.data[index]) && !element.hidden) { + count++; + } + }); + + return count; + }, + + /** + * @protected + */ + setHoverStyle: function(arc) { + var model = arc._model; + var options = arc._options; + var getHoverColor = helpers$1.getHoverColor; + var valueOrDefault = helpers$1.valueOrDefault; + + arc.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + }; + + model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); + }, + + /** + * @private + */ + _computeAngle: function(index) { + var me = this; + var count = this.getMeta().count; + var dataset = me.getDataset(); + var meta = me.getMeta(); + + if (isNaN(dataset.data[index]) || meta.data[index].hidden) { + return 0; + } + + // Scriptable options + var context = { + chart: me.chart, + dataIndex: index, + dataset: dataset, + datasetIndex: me.index + }; + + return resolve$3([ + me.chart.options.elements.arc.angle, + (2 * Math.PI) / count + ], context, index); + } +}); + +core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); +core_defaults._set('pie', { + cutoutPercentage: 0 +}); + +// Pie charts are Doughnut chart with different defaults +var controller_pie = controller_doughnut; + +var valueOrDefault$7 = helpers$1.valueOrDefault; + +core_defaults._set('radar', { + spanGaps: false, + scale: { + type: 'radialLinear' + }, + elements: { + line: { + fill: 'start', + tension: 0 // no bezier in radar + } + } +}); + +var controller_radar = core_datasetController.extend({ + datasetElementType: elements.Line, + + dataElementType: elements.Point, + + linkScales: helpers$1.noop, + + /** + * @private + */ + _datasetElementOptions: [ + 'backgroundColor', + 'borderWidth', + 'borderColor', + 'borderCapStyle', + 'borderDash', + 'borderDashOffset', + 'borderJoinStyle', + 'fill' + ], + + /** + * @private + */ + _dataElementOptions: { + backgroundColor: 'pointBackgroundColor', + borderColor: 'pointBorderColor', + borderWidth: 'pointBorderWidth', + hitRadius: 'pointHitRadius', + hoverBackgroundColor: 'pointHoverBackgroundColor', + hoverBorderColor: 'pointHoverBorderColor', + hoverBorderWidth: 'pointHoverBorderWidth', + hoverRadius: 'pointHoverRadius', + pointStyle: 'pointStyle', + radius: 'pointRadius', + rotation: 'pointRotation' + }, + + /** + * @private + */ + _getIndexScaleId: function() { + return this.chart.scale.id; + }, + + /** + * @private + */ + _getValueScaleId: function() { + return this.chart.scale.id; + }, + + update: function(reset) { + var me = this; + var meta = me.getMeta(); + var line = meta.dataset; + var points = meta.data || []; + var scale = me.chart.scale; + var config = me._config; + var i, ilen; + + // Compatibility: If the properties are defined with only the old name, use those values + if (config.tension !== undefined && config.lineTension === undefined) { + config.lineTension = config.tension; + } + + // Utility + line._scale = scale; + line._datasetIndex = me.index; + // Data + line._children = points; + line._loop = true; + // Model + line._model = me._resolveDatasetElementOptions(line); + + line.pivot(); + + // Update Points + for (i = 0, ilen = points.length; i < ilen; ++i) { + me.updateElement(points[i], i, reset); + } + + // Update bezier control points + me.updateBezierControlPoints(); + + // Now pivot the point for animation + for (i = 0, ilen = points.length; i < ilen; ++i) { + points[i].pivot(); + } + }, + + updateElement: function(point, index, reset) { + var me = this; + var custom = point.custom || {}; + var dataset = me.getDataset(); + var scale = me.chart.scale; + var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); + var options = me._resolveDataElementOptions(point, index); + var lineModel = me.getMeta().dataset._model; + var x = reset ? scale.xCenter : pointPosition.x; + var y = reset ? scale.yCenter : pointPosition.y; + + // Utility + point._scale = scale; + point._options = options; + point._datasetIndex = me.index; + point._index = index; + + // Desired view properties + point._model = { + x: x, // value not used in dataset scale, but we want a consistent API between scales + y: y, + skip: custom.skip || isNaN(x) || isNaN(y), + // Appearance + radius: options.radius, + pointStyle: options.pointStyle, + rotation: options.rotation, + backgroundColor: options.backgroundColor, + borderColor: options.borderColor, + borderWidth: options.borderWidth, + tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0), + + // Tooltip + hitRadius: options.hitRadius + }; + }, + + /** + * @private + */ + _resolveDatasetElementOptions: function() { + var me = this; + var config = me._config; + var options = me.chart.options; + var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); + + values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps); + values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension); + + return values; + }, + + updateBezierControlPoints: function() { + var me = this; + var meta = me.getMeta(); + var area = me.chart.chartArea; + var points = meta.data || []; + var i, ilen, model, controlPoints; + + // Only consider points that are drawn in case the spanGaps option is used + if (meta.dataset._model.spanGaps) { + points = points.filter(function(pt) { + return !pt._model.skip; + }); + } + + function capControlPoint(pt, min, max) { + return Math.max(Math.min(pt, max), min); + } + + for (i = 0, ilen = points.length; i < ilen; ++i) { + model = points[i]._model; + controlPoints = helpers$1.splineCurve( + helpers$1.previousItem(points, i, true)._model, + model, + helpers$1.nextItem(points, i, true)._model, + model.tension + ); + + // Prevent the bezier going outside of the bounds of the graph + model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); + model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); + model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); + model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); + } + }, + + setHoverStyle: function(point) { + var model = point._model; + var options = point._options; + var getHoverColor = helpers$1.getHoverColor; + + point.$previousStyle = { + backgroundColor: model.backgroundColor, + borderColor: model.borderColor, + borderWidth: model.borderWidth, + radius: model.radius + }; + + model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); + model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor)); + model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth); + model.radius = valueOrDefault$7(options.hoverRadius, options.radius); + } +}); + +core_defaults._set('scatter', { + hover: { + mode: 'single' + }, + + scales: { + xAxes: [{ + id: 'x-axis-1', // need an ID so datasets can reference the scale + type: 'linear', // scatter should not use a category axis + position: 'bottom' + }], + yAxes: [{ + id: 'y-axis-1', + type: 'linear', + position: 'left' + }] + }, + + tooltips: { + callbacks: { + title: function() { + return ''; // doesn't make sense for scatter since data are formatted as a point + }, + label: function(item) { + return '(' + item.xLabel + ', ' + item.yLabel + ')'; + } + } + } +}); + +core_defaults._set('global', { + datasets: { + scatter: { + showLine: false + } + } +}); + +// Scatter charts use line controllers +var controller_scatter = controller_line; + +// NOTE export a map in which the key represents the controller type, not +// the class, and so must be CamelCase in order to be correctly retrieved +// by the controller in core.controller.js (`controllers[meta.type]`). + +var controllers = { + bar: controller_bar, + bubble: controller_bubble, + doughnut: controller_doughnut, + horizontalBar: controller_horizontalBar, + line: controller_line, + polarArea: controller_polarArea, + pie: controller_pie, + radar: controller_radar, + scatter: controller_scatter +}; + +/** + * Helper function to get relative position for an event + * @param {Event|IEvent} event - The event to get the position for + * @param {Chart} chart - The chart + * @returns {object} the event position + */ +function getRelativePosition(e, chart) { + if (e.native) { + return { + x: e.x, + y: e.y + }; + } + + return helpers$1.getRelativePosition(e, chart); +} + +/** + * Helper function to traverse all of the visible elements in the chart + * @param {Chart} chart - the chart + * @param {function} handler - the callback to execute for each visible item + */ +function parseVisibleItems(chart, handler) { + var metasets = chart._getSortedVisibleDatasetMetas(); + var metadata, i, j, ilen, jlen, element; + + for (i = 0, ilen = metasets.length; i < ilen; ++i) { + metadata = metasets[i].data; + for (j = 0, jlen = metadata.length; j < jlen; ++j) { + element = metadata[j]; + if (!element._view.skip) { + handler(element); + } + } + } +} + +/** + * Helper function to get the items that intersect the event position + * @param {ChartElement[]} items - elements to filter + * @param {object} position - the point to be nearest to + * @return {ChartElement[]} the nearest items + */ +function getIntersectItems(chart, position) { + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + } + }); + + return elements; +} + +/** + * Helper function to get the items nearest to the event position considering all visible items in teh chart + * @param {Chart} chart - the chart to look at elements from + * @param {object} position - the point to be nearest to + * @param {boolean} intersect - if true, only consider items that intersect the position + * @param {function} distanceMetric - function to provide the distance between points + * @return {ChartElement[]} the nearest items + */ +function getNearestItems(chart, position, intersect, distanceMetric) { + var minDistance = Number.POSITIVE_INFINITY; + var nearestItems = []; + + parseVisibleItems(chart, function(element) { + if (intersect && !element.inRange(position.x, position.y)) { + return; + } + + var center = element.getCenterPoint(); + var distance = distanceMetric(position, center); + if (distance < minDistance) { + nearestItems = [element]; + minDistance = distance; + } else if (distance === minDistance) { + // Can have multiple items at the same distance in which case we sort by size + nearestItems.push(element); + } + }); + + return nearestItems; +} + +/** + * Get a distance metric function for two points based on the + * axis mode setting + * @param {string} axis - the axis mode. x|y|xy + */ +function getDistanceMetricForAxis(axis) { + var useX = axis.indexOf('x') !== -1; + var useY = axis.indexOf('y') !== -1; + + return function(pt1, pt2) { + var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; + var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; + return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); + }; +} + +function indexMode(chart, e, options) { + var position = getRelativePosition(e, chart); + // Default axis for index mode is 'x' to match old behaviour + options.axis = options.axis || 'x'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + var elements = []; + + if (!items.length) { + return []; + } + + chart._getSortedVisibleDatasetMetas().forEach(function(meta) { + var element = meta.data[items[0]._index]; + + // don't count items that are skipped (null data) + if (element && !element._view.skip) { + elements.push(element); + } + }); + + return elements; +} + +/** + * @interface IInteractionOptions + */ +/** + * If true, only consider items that intersect the point + * @name IInterfaceOptions#boolean + * @type Boolean + */ + +/** + * Contains interaction related functions + * @namespace Chart.Interaction + */ +var core_interaction = { + // Helper function for different modes + modes: { + single: function(chart, e) { + var position = getRelativePosition(e, chart); + var elements = []; + + parseVisibleItems(chart, function(element) { + if (element.inRange(position.x, position.y)) { + elements.push(element); + return elements; + } + }); + + return elements.slice(0, 1); + }, + + /** + * @function Chart.Interaction.modes.label + * @deprecated since version 2.4.0 + * @todo remove at version 3 + * @private + */ + label: indexMode, + + /** + * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item + * @function Chart.Interaction.modes.index + * @since v2.4.0 + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + index: indexMode, + + /** + * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something + * If the options.intersect is false, we find the nearest item and return the items in that dataset + * @function Chart.Interaction.modes.dataset + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use during interaction + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + dataset: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); + + if (items.length > 0) { + items = chart.getDatasetMeta(items[0]._datasetIndex).data; + } + + return items; + }, + + /** + * @function Chart.Interaction.modes.x-axis + * @deprecated since version 2.4.0. Use index mode and intersect == true + * @todo remove at version 3 + * @private + */ + 'x-axis': function(chart, e) { + return indexMode(chart, e, {intersect: false}); + }, + + /** + * Point mode returns all elements that hit test based on the event position + * of the event + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + point: function(chart, e) { + var position = getRelativePosition(e, chart); + return getIntersectItems(chart, position); + }, + + /** + * nearest mode returns the element closest to the point + * @function Chart.Interaction.modes.intersect + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + nearest: function(chart, e, options) { + var position = getRelativePosition(e, chart); + options.axis = options.axis || 'xy'; + var distanceMetric = getDistanceMetricForAxis(options.axis); + return getNearestItems(chart, position, options.intersect, distanceMetric); + }, + + /** + * x mode returns the elements that hit-test at the current x coordinate + * @function Chart.Interaction.modes.x + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + x: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inXRange(position.x)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + }, + + /** + * y mode returns the elements that hit-test at the current y coordinate + * @function Chart.Interaction.modes.y + * @param {Chart} chart - the chart we are returning items from + * @param {Event} e - the event we are find things at + * @param {IInteractionOptions} options - options to use + * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned + */ + y: function(chart, e, options) { + var position = getRelativePosition(e, chart); + var items = []; + var intersectsItem = false; + + parseVisibleItems(chart, function(element) { + if (element.inYRange(position.y)) { + items.push(element); + } + + if (element.inRange(position.x, position.y)) { + intersectsItem = true; + } + }); + + // If we want to trigger on an intersect and we don't have any items + // that intersect the position, return nothing + if (options.intersect && !intersectsItem) { + items = []; + } + return items; + } + } +}; + +var extend = helpers$1.extend; + +function filterByPosition(array, position) { + return helpers$1.where(array, function(v) { + return v.pos === position; + }); +} + +function sortByWeight(array, reverse) { + return array.sort(function(a, b) { + var v0 = reverse ? b : a; + var v1 = reverse ? a : b; + return v0.weight === v1.weight ? + v0.index - v1.index : + v0.weight - v1.weight; + }); +} + +function wrapBoxes(boxes) { + var layoutBoxes = []; + var i, ilen, box; + + for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { + box = boxes[i]; + layoutBoxes.push({ + index: i, + box: box, + pos: box.position, + horizontal: box.isHorizontal(), + weight: box.weight + }); + } + return layoutBoxes; +} + +function setLayoutDims(layouts, params) { + var i, ilen, layout; + for (i = 0, ilen = layouts.length; i < ilen; ++i) { + layout = layouts[i]; + // store width used instead of chartArea.w in fitBoxes + layout.width = layout.horizontal + ? layout.box.fullWidth && params.availableWidth + : params.vBoxMaxWidth; + // store height used instead of chartArea.h in fitBoxes + layout.height = layout.horizontal && params.hBoxMaxHeight; + } +} + +function buildLayoutBoxes(boxes) { + var layoutBoxes = wrapBoxes(boxes); + var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); + var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); + var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); + var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); + + return { + leftAndTop: left.concat(top), + rightAndBottom: right.concat(bottom), + chartArea: filterByPosition(layoutBoxes, 'chartArea'), + vertical: left.concat(right), + horizontal: top.concat(bottom) + }; +} + +function getCombinedMax(maxPadding, chartArea, a, b) { + return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); +} + +function updateDims(chartArea, params, layout) { + var box = layout.box; + var maxPadding = chartArea.maxPadding; + var newWidth, newHeight; + + if (layout.size) { + // this layout was already counted for, lets first reduce old size + chartArea[layout.pos] -= layout.size; + } + layout.size = layout.horizontal ? box.height : box.width; + chartArea[layout.pos] += layout.size; + + if (box.getPadding) { + var boxPadding = box.getPadding(); + maxPadding.top = Math.max(maxPadding.top, boxPadding.top); + maxPadding.left = Math.max(maxPadding.left, boxPadding.left); + maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); + maxPadding.right = Math.max(maxPadding.right, boxPadding.right); + } + + newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); + newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); + + if (newWidth !== chartArea.w || newHeight !== chartArea.h) { + chartArea.w = newWidth; + chartArea.h = newHeight; + + // return true if chart area changed in layout's direction + return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; + } +} + +function handleMaxPadding(chartArea) { + var maxPadding = chartArea.maxPadding; + + function updatePos(pos) { + var change = Math.max(maxPadding[pos] - chartArea[pos], 0); + chartArea[pos] += change; + return change; + } + chartArea.y += updatePos('top'); + chartArea.x += updatePos('left'); + updatePos('right'); + updatePos('bottom'); +} + +function getMargins(horizontal, chartArea) { + var maxPadding = chartArea.maxPadding; + + function marginForPositions(positions) { + var margin = {left: 0, top: 0, right: 0, bottom: 0}; + positions.forEach(function(pos) { + margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); + }); + return margin; + } + + return horizontal + ? marginForPositions(['left', 'right']) + : marginForPositions(['top', 'bottom']); +} + +function fitBoxes(boxes, chartArea, params) { + var refitBoxes = []; + var i, ilen, layout, box, refit, changed; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + + box.update( + layout.width || chartArea.w, + layout.height || chartArea.h, + getMargins(layout.horizontal, chartArea) + ); + if (updateDims(chartArea, params, layout)) { + changed = true; + if (refitBoxes.length) { + // Dimensions changed and there were non full width boxes before this + // -> we have to refit those + refit = true; + } + } + if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case + refitBoxes.push(layout); + } + } + + return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; +} + +function placeBoxes(boxes, chartArea, params) { + var userPadding = params.padding; + var x = chartArea.x; + var y = chartArea.y; + var i, ilen, layout, box; + + for (i = 0, ilen = boxes.length; i < ilen; ++i) { + layout = boxes[i]; + box = layout.box; + if (layout.horizontal) { + box.left = box.fullWidth ? userPadding.left : chartArea.left; + box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; + box.top = y; + box.bottom = y + box.height; + box.width = box.right - box.left; + y = box.bottom; + } else { + box.left = x; + box.right = x + box.width; + box.top = chartArea.top; + box.bottom = chartArea.top + chartArea.h; + box.height = box.bottom - box.top; + x = box.right; + } + } + + chartArea.x = x; + chartArea.y = y; +} + +core_defaults._set('global', { + layout: { + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } +}); + +/** + * @interface ILayoutItem + * @prop {string} position - The position of the item in the chart layout. Possible values are + * 'left', 'top', 'right', 'bottom', and 'chartArea' + * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area + * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down + * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) + * @prop {function} update - Takes two parameters: width and height. Returns size of item + * @prop {function} getPadding - Returns an object with padding on the edges + * @prop {number} width - Width of item. Must be valid after update() + * @prop {number} height - Height of item. Must be valid after update() + * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update + * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update + * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update + * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update + */ + +// The layout service is very self explanatory. It's responsible for the layout within a chart. +// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need +// It is this service's responsibility of carrying out that layout. +var core_layouts = { + defaults: {}, + + /** + * Register a box to a chart. + * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. + * @param {Chart} chart - the chart to use + * @param {ILayoutItem} item - the item to add to be layed out + */ + addBox: function(chart, item) { + if (!chart.boxes) { + chart.boxes = []; + } + + // initialize item with default values + item.fullWidth = item.fullWidth || false; + item.position = item.position || 'top'; + item.weight = item.weight || 0; + item._layers = item._layers || function() { + return [{ + z: 0, + draw: function() { + item.draw.apply(item, arguments); + } + }]; + }; + + chart.boxes.push(item); + }, + + /** + * Remove a layoutItem from a chart + * @param {Chart} chart - the chart to remove the box from + * @param {ILayoutItem} layoutItem - the item to remove from the layout + */ + removeBox: function(chart, layoutItem) { + var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; + if (index !== -1) { + chart.boxes.splice(index, 1); + } + }, + + /** + * Sets (or updates) options on the given `item`. + * @param {Chart} chart - the chart in which the item lives (or will be added to) + * @param {ILayoutItem} item - the item to configure with the given options + * @param {object} options - the new item options. + */ + configure: function(chart, item, options) { + var props = ['fullWidth', 'position', 'weight']; + var ilen = props.length; + var i = 0; + var prop; + + for (; i < ilen; ++i) { + prop = props[i]; + if (options.hasOwnProperty(prop)) { + item[prop] = options[prop]; + } + } + }, + + /** + * Fits boxes of the given chart into the given size by having each box measure itself + * then running a fitting algorithm + * @param {Chart} chart - the chart + * @param {number} width - the width to fit into + * @param {number} height - the height to fit into + */ + update: function(chart, width, height) { + if (!chart) { + return; + } + + var layoutOptions = chart.options.layout || {}; + var padding = helpers$1.options.toPadding(layoutOptions.padding); + + var availableWidth = width - padding.width; + var availableHeight = height - padding.height; + var boxes = buildLayoutBoxes(chart.boxes); + var verticalBoxes = boxes.vertical; + var horizontalBoxes = boxes.horizontal; + + // Essentially we now have any number of boxes on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays + // These locations are single-box locations only, when trying to register a chartArea location that is already taken, + // an error will be thrown. + // + // |----------------------------------------------------| + // | T1 (Full Width) | + // |----------------------------------------------------| + // | | | T2 | | + // | |----|-------------------------------------|----| + // | | | C1 | | C2 | | + // | | |----| |----| | + // | | | | | + // | L1 | L2 | ChartArea (C0) | R1 | + // | | | | | + // | | |----| |----| | + // | | | C3 | | C4 | | + // | |----|-------------------------------------|----| + // | | | B1 | | + // |----------------------------------------------------| + // | B2 (Full Width) | + // |----------------------------------------------------| + // + + var params = Object.freeze({ + outerWidth: width, + outerHeight: height, + padding: padding, + availableWidth: availableWidth, + vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, + hBoxMaxHeight: availableHeight / 2 + }); + var chartArea = extend({ + maxPadding: extend({}, padding), + w: availableWidth, + h: availableHeight, + x: padding.left, + y: padding.top + }, padding); + + setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); + + // First fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + + // Then fit horizontal boxes + if (fitBoxes(horizontalBoxes, chartArea, params)) { + // if the area changed, re-fit vertical boxes + fitBoxes(verticalBoxes, chartArea, params); + } + + handleMaxPadding(chartArea); + + // Finally place the boxes to correct coordinates + placeBoxes(boxes.leftAndTop, chartArea, params); + + // Move to opposite side of chart + chartArea.x += chartArea.w; + chartArea.y += chartArea.h; + + placeBoxes(boxes.rightAndBottom, chartArea, params); + + chart.chartArea = { + left: chartArea.left, + top: chartArea.top, + right: chartArea.left + chartArea.w, + bottom: chartArea.top + chartArea.h + }; + + // Finally update boxes in chartArea (radial scale for example) + helpers$1.each(boxes.chartArea, function(layout) { + var box = layout.box; + extend(box, chart.chartArea); + box.update(chartArea.w, chartArea.h); + }); + } +}; + +/** + * Platform fallback implementation (minimal). + * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 + */ + +var platform_basic = { + acquireContext: function(item) { + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + return item && item.getContext('2d') || null; + } +}; + +var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n"; + +var platform_dom$1 = /*#__PURE__*/Object.freeze({ +__proto__: null, +'default': platform_dom +}); + +var stylesheet = getCjsExportFromNamespace(platform_dom$1); + +var EXPANDO_KEY = '$chartjs'; +var CSS_PREFIX = 'chartjs-'; +var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; +var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; +var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; +var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; + +/** + * DOM event types -> Chart.js event types. + * Note: only events with different types are mapped. + * @see https://developer.mozilla.org/en-US/docs/Web/Events + */ +var EVENT_TYPES = { + touchstart: 'mousedown', + touchmove: 'mousemove', + touchend: 'mouseup', + pointerenter: 'mouseenter', + pointerdown: 'mousedown', + pointermove: 'mousemove', + pointerup: 'mouseup', + pointerleave: 'mouseout', + pointerout: 'mouseout' +}; + +/** + * The "used" size is the final value of a dimension property after all calculations have + * been performed. This method uses the computed style of `element` but returns undefined + * if the computed style is not expressed in pixels. That can happen in some cases where + * `element` has a size relative to its parent and this last one is not yet displayed, + * for example because of `display: none` on a parent node. + * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value + * @returns {number} Size in pixels or undefined if unknown. + */ +function readUsedSize(element, property) { + var value = helpers$1.getStyle(element, property); + var matches = value && value.match(/^(\d+)(\.\d+)?px$/); + return matches ? Number(matches[1]) : undefined; +} + +/** + * Initializes the canvas style and render size without modifying the canvas display size, + * since responsiveness is handled by the controller.resize() method. The config is used + * to determine the aspect ratio to apply in case no explicit height has been specified. + */ +function initCanvas(canvas, config) { + var style = canvas.style; + + // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it + // returns null or '' if no explicit value has been set to the canvas attribute. + var renderHeight = canvas.getAttribute('height'); + var renderWidth = canvas.getAttribute('width'); + + // Chart.js modifies some canvas values that we want to restore on destroy + canvas[EXPANDO_KEY] = { + initial: { + height: renderHeight, + width: renderWidth, + style: { + display: style.display, + height: style.height, + width: style.width + } + } + }; + + // Force canvas to display as block to avoid extra space caused by inline + // elements, which would interfere with the responsive resize process. + // https://github.com/chartjs/Chart.js/issues/2538 + style.display = style.display || 'block'; + + if (renderWidth === null || renderWidth === '') { + var displayWidth = readUsedSize(canvas, 'width'); + if (displayWidth !== undefined) { + canvas.width = displayWidth; + } + } + + if (renderHeight === null || renderHeight === '') { + if (canvas.style.height === '') { + // If no explicit render height and style height, let's apply the aspect ratio, + // which one can be specified by the user but also by charts as default option + // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. + canvas.height = canvas.width / (config.options.aspectRatio || 2); + } else { + var displayHeight = readUsedSize(canvas, 'height'); + if (displayWidth !== undefined) { + canvas.height = displayHeight; + } + } + } + + return canvas; +} + +/** + * Detects support for options object argument in addEventListener. + * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support + * @private + */ +var supportsEventListenerOptions = (function() { + var supports = false; + try { + var options = Object.defineProperty({}, 'passive', { + // eslint-disable-next-line getter-return + get: function() { + supports = true; + } + }); + window.addEventListener('e', null, options); + } catch (e) { + // continue regardless of error + } + return supports; +}()); + +// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. +// https://github.com/chartjs/Chart.js/issues/4287 +var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; + +function addListener(node, type, listener) { + node.addEventListener(type, listener, eventListenerOptions); +} + +function removeListener(node, type, listener) { + node.removeEventListener(type, listener, eventListenerOptions); +} + +function createEvent(type, chart, x, y, nativeEvent) { + return { + type: type, + chart: chart, + native: nativeEvent || null, + x: x !== undefined ? x : null, + y: y !== undefined ? y : null, + }; +} + +function fromNativeEvent(event, chart) { + var type = EVENT_TYPES[event.type] || event.type; + var pos = helpers$1.getRelativePosition(event, chart); + return createEvent(type, chart, pos.x, pos.y, event); +} + +function throttled(fn, thisArg) { + var ticking = false; + var args = []; + + return function() { + args = Array.prototype.slice.call(arguments); + thisArg = thisArg || this; + + if (!ticking) { + ticking = true; + helpers$1.requestAnimFrame.call(window, function() { + ticking = false; + fn.apply(thisArg, args); + }); + } + }; +} + +function createDiv(cls) { + var el = document.createElement('div'); + el.className = cls || ''; + return el; +} + +// Implementation based on https://github.com/marcj/css-element-queries +function createResizer(handler) { + var maxSize = 1000000; + + // NOTE(SB) Don't use innerHTML because it could be considered unsafe. + // https://github.com/chartjs/Chart.js/issues/5902 + var resizer = createDiv(CSS_SIZE_MONITOR); + var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); + var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); + + expand.appendChild(createDiv()); + shrink.appendChild(createDiv()); + + resizer.appendChild(expand); + resizer.appendChild(shrink); + resizer._reset = function() { + expand.scrollLeft = maxSize; + expand.scrollTop = maxSize; + shrink.scrollLeft = maxSize; + shrink.scrollTop = maxSize; + }; + + var onScroll = function() { + resizer._reset(); + handler(); + }; + + addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); + addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); + + return resizer; +} + +// https://davidwalsh.name/detect-node-insertion +function watchForRender(node, handler) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + var proxy = expando.renderProxy = function(e) { + if (e.animationName === CSS_RENDER_ANIMATION) { + handler(); + } + }; + + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + addListener(node, type, proxy); + }); + + // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class + // is removed then added back immediately (same animation frame?). Accessing the + // `offsetParent` property will force a reflow and re-evaluate the CSS animation. + // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics + // https://github.com/chartjs/Chart.js/issues/4737 + expando.reflow = !!node.offsetParent; + + node.classList.add(CSS_RENDER_MONITOR); +} + +function unwatchForRender(node) { + var expando = node[EXPANDO_KEY] || {}; + var proxy = expando.renderProxy; + + if (proxy) { + helpers$1.each(ANIMATION_START_EVENTS, function(type) { + removeListener(node, type, proxy); + }); + + delete expando.renderProxy; + } + + node.classList.remove(CSS_RENDER_MONITOR); +} + +function addResizeListener(node, listener, chart) { + var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); + + // Let's keep track of this added resizer and thus avoid DOM query when removing it. + var resizer = expando.resizer = createResizer(throttled(function() { + if (expando.resizer) { + var container = chart.options.maintainAspectRatio && node.parentNode; + var w = container ? container.clientWidth : 0; + listener(createEvent('resize', chart)); + if (container && container.clientWidth < w && chart.canvas) { + // If the container size shrank during chart resize, let's assume + // scrollbar appeared. So we resize again with the scrollbar visible - + // effectively making chart smaller and the scrollbar hidden again. + // Because we are inside `throttled`, and currently `ticking`, scroll + // events are ignored during this whole 2 resize process. + // If we assumed wrong and something else happened, we are resizing + // twice in a frame (potential performance issue) + listener(createEvent('resize', chart)); + } + } + })); + + // The resizer needs to be attached to the node parent, so we first need to be + // sure that `node` is attached to the DOM before injecting the resizer element. + watchForRender(node, function() { + if (expando.resizer) { + var container = node.parentNode; + if (container && container !== resizer.parentNode) { + container.insertBefore(resizer, container.firstChild); + } + + // The container size might have changed, let's reset the resizer state. + resizer._reset(); + } + }); +} + +function removeResizeListener(node) { + var expando = node[EXPANDO_KEY] || {}; + var resizer = expando.resizer; + + delete expando.resizer; + unwatchForRender(node); + + if (resizer && resizer.parentNode) { + resizer.parentNode.removeChild(resizer); + } +} + +/** + * Injects CSS styles inline if the styles are not already present. + * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>. + * @param {string} css - the CSS to be injected. + */ +function injectCSS(rootNode, css) { + // https://stackoverflow.com/q/3922139 + var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {}); + if (!expando.containsStyles) { + expando.containsStyles = true; + css = '/* Chart.js */\n' + css; + var style = document.createElement('style'); + style.setAttribute('type', 'text/css'); + style.appendChild(document.createTextNode(css)); + rootNode.appendChild(style); + } +} + +var platform_dom$2 = { + /** + * When `true`, prevents the automatic injection of the stylesheet required to + * correctly detect when the chart is added to the DOM and then resized. This + * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`) + * to be manually imported to make this library compatible with any CSP. + * See https://github.com/chartjs/Chart.js/issues/5208 + */ + disableCSSInjection: false, + + /** + * This property holds whether this platform is enabled for the current environment. + * Currently used by platform.js to select the proper implementation. + * @private + */ + _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', + + /** + * Initializes resources that depend on platform options. + * @param {HTMLCanvasElement} canvas - The Canvas element. + * @private + */ + _ensureLoaded: function(canvas) { + if (!this.disableCSSInjection) { + // If the canvas is in a shadow DOM, then the styles must also be inserted + // into the same shadow DOM. + // https://github.com/chartjs/Chart.js/issues/5763 + var root = canvas.getRootNode ? canvas.getRootNode() : document; + var targetNode = root.host ? root : document.head; + injectCSS(targetNode, stylesheet); + } + }, + + acquireContext: function(item, config) { + if (typeof item === 'string') { + item = document.getElementById(item); + } else if (item.length) { + // Support for array based queries (such as jQuery) + item = item[0]; + } + + if (item && item.canvas) { + // Support for any object associated to a canvas (including a context2d) + item = item.canvas; + } + + // To prevent canvas fingerprinting, some add-ons undefine the getContext + // method, for example: https://github.com/kkapsner/CanvasBlocker + // https://github.com/chartjs/Chart.js/issues/2807 + var context = item && item.getContext && item.getContext('2d'); + + // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is + // inside an iframe or when running in a protected environment. We could guess the + // types from their toString() value but let's keep things flexible and assume it's + // a sufficient condition if the item has a context2D which has item as `canvas`. + // https://github.com/chartjs/Chart.js/issues/3887 + // https://github.com/chartjs/Chart.js/issues/4102 + // https://github.com/chartjs/Chart.js/issues/4152 + if (context && context.canvas === item) { + // Load platform resources on first chart creation, to make it possible to + // import the library before setting platform options. + this._ensureLoaded(item); + initCanvas(item, config); + return context; + } + + return null; + }, + + releaseContext: function(context) { + var canvas = context.canvas; + if (!canvas[EXPANDO_KEY]) { + return; + } + + var initial = canvas[EXPANDO_KEY].initial; + ['height', 'width'].forEach(function(prop) { + var value = initial[prop]; + if (helpers$1.isNullOrUndef(value)) { + canvas.removeAttribute(prop); + } else { + canvas.setAttribute(prop, value); + } + }); + + helpers$1.each(initial.style || {}, function(value, key) { + canvas.style[key] = value; + }); + + // The canvas render size might have been changed (and thus the state stack discarded), + // we can't use save() and restore() to restore the initial state. So make sure that at + // least the canvas context is reset to the default state by setting the canvas width. + // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html + // eslint-disable-next-line no-self-assign + canvas.width = canvas.width; + + delete canvas[EXPANDO_KEY]; + }, + + addEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + addResizeListener(canvas, listener, chart); + return; + } + + var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); + var proxies = expando.proxies || (expando.proxies = {}); + var proxy = proxies[chart.id + '_' + type] = function(event) { + listener(fromNativeEvent(event, chart)); + }; + + addListener(canvas, type, proxy); + }, + + removeEventListener: function(chart, type, listener) { + var canvas = chart.canvas; + if (type === 'resize') { + // Note: the resize event is not supported on all browsers. + removeResizeListener(canvas); + return; + } + + var expando = listener[EXPANDO_KEY] || {}; + var proxies = expando.proxies || {}; + var proxy = proxies[chart.id + '_' + type]; + if (!proxy) { + return; + } + + removeListener(canvas, type, proxy); + } +}; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use EventTarget.addEventListener instead. + * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener + * @function Chart.helpers.addEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers$1.addEvent = addListener; + +/** + * Provided for backward compatibility, use EventTarget.removeEventListener instead. + * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ + * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener + * @function Chart.helpers.removeEvent + * @deprecated since version 2.7.0 + * @todo remove at version 3 + * @private + */ +helpers$1.removeEvent = removeListener; + +// @TODO Make possible to select another platform at build time. +var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic; + +/** + * @namespace Chart.platform + * @see https://chartjs.gitbooks.io/proposals/content/Platform.html + * @since 2.4.0 + */ +var platform = helpers$1.extend({ + /** + * @since 2.7.0 + */ + initialize: function() {}, + + /** + * Called at chart construction time, returns a context2d instance implementing + * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. + * @param {*} item - The native item from which to acquire context (platform specific) + * @param {object} options - The chart options + * @returns {CanvasRenderingContext2D} context2d instance + */ + acquireContext: function() {}, + + /** + * Called at chart destruction time, releases any resources associated to the context + * previously returned by the acquireContext() method. + * @param {CanvasRenderingContext2D} context - The context2d instance + * @returns {boolean} true if the method succeeded, else false + */ + releaseContext: function() {}, + + /** + * Registers the specified listener on the given chart. + * @param {Chart} chart - Chart from which to listen for event + * @param {string} type - The ({@link IEvent}) type to listen for + * @param {function} listener - Receives a notification (an object that implements + * the {@link IEvent} interface) when an event of the specified type occurs. + */ + addEventListener: function() {}, + + /** + * Removes the specified listener previously registered with addEventListener. + * @param {Chart} chart - Chart from which to remove the listener + * @param {string} type - The ({@link IEvent}) type to remove + * @param {function} listener - The listener function to remove from the event target. + */ + removeEventListener: function() {} + +}, implementation); + +core_defaults._set('global', { + plugins: {} +}); + +/** + * The plugin service singleton + * @namespace Chart.plugins + * @since 2.1.0 + */ +var core_plugins = { + /** + * Globally registered plugins. + * @private + */ + _plugins: [], + + /** + * This identifier is used to invalidate the descriptors cache attached to each chart + * when a global plugin is registered or unregistered. In this case, the cache ID is + * incremented and descriptors are regenerated during following API calls. + * @private + */ + _cacheId: 0, + + /** + * Registers the given plugin(s) if not already registered. + * @param {IPlugin[]|IPlugin} plugins plugin instance(s). + */ + register: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + if (p.indexOf(plugin) === -1) { + p.push(plugin); + } + }); + + this._cacheId++; + }, + + /** + * Unregisters the given plugin(s) only if registered. + * @param {IPlugin[]|IPlugin} plugins plugin instance(s). + */ + unregister: function(plugins) { + var p = this._plugins; + ([]).concat(plugins).forEach(function(plugin) { + var idx = p.indexOf(plugin); + if (idx !== -1) { + p.splice(idx, 1); + } + }); + + this._cacheId++; + }, + + /** + * Remove all registered plugins. + * @since 2.1.5 + */ + clear: function() { + this._plugins = []; + this._cacheId++; + }, + + /** + * Returns the number of registered plugins? + * @returns {number} + * @since 2.1.5 + */ + count: function() { + return this._plugins.length; + }, + + /** + * Returns all registered plugin instances. + * @returns {IPlugin[]} array of plugin objects. + * @since 2.1.5 + */ + getAll: function() { + return this._plugins; + }, + + /** + * Calls enabled plugins for `chart` on the specified hook and with the given args. + * This method immediately returns as soon as a plugin explicitly returns false. The + * returned value can be used, for instance, to interrupt the current action. + * @param {Chart} chart - The chart instance for which plugins should be called. + * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). + * @param {Array} [args] - Extra arguments to apply to the hook call. + * @returns {boolean} false if any of the plugins return false, else returns true. + */ + notify: function(chart, hook, args) { + var descriptors = this.descriptors(chart); + var ilen = descriptors.length; + var i, descriptor, plugin, params, method; + + for (i = 0; i < ilen; ++i) { + descriptor = descriptors[i]; + plugin = descriptor.plugin; + method = plugin[hook]; + if (typeof method === 'function') { + params = [chart].concat(args || []); + params.push(descriptor.options); + if (method.apply(plugin, params) === false) { + return false; + } + } + } + + return true; + }, + + /** + * Returns descriptors of enabled plugins for the given chart. + * @returns {object[]} [{ plugin, options }] + * @private + */ + descriptors: function(chart) { + var cache = chart.$plugins || (chart.$plugins = {}); + if (cache.id === this._cacheId) { + return cache.descriptors; + } + + var plugins = []; + var descriptors = []; + var config = (chart && chart.config) || {}; + var options = (config.options && config.options.plugins) || {}; + + this._plugins.concat(config.plugins || []).forEach(function(plugin) { + var idx = plugins.indexOf(plugin); + if (idx !== -1) { + return; + } + + var id = plugin.id; + var opts = options[id]; + if (opts === false) { + return; + } + + if (opts === true) { + opts = helpers$1.clone(core_defaults.global.plugins[id]); + } + + plugins.push(plugin); + descriptors.push({ + plugin: plugin, + options: opts || {} + }); + }); + + cache.descriptors = descriptors; + cache.id = this._cacheId; + return descriptors; + }, + + /** + * Invalidates cache for the given chart: descriptors hold a reference on plugin option, + * but in some cases, this reference can be changed by the user when updating options. + * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + * @private + */ + _invalidate: function(chart) { + delete chart.$plugins; + } +}; + +var core_scaleService = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers + + // Scale config defaults + defaults: {}, + registerScaleType: function(type, scaleConstructor, scaleDefaults) { + this.constructors[type] = scaleConstructor; + this.defaults[type] = helpers$1.clone(scaleDefaults); + }, + getScaleConstructor: function(type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, + getScaleDefaults: function(type) { + // Return the scale defaults merged with the global settings so that we always use the latest ones + return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {}; + }, + updateScaleDefaults: function(type, additions) { + var me = this; + if (me.defaults.hasOwnProperty(type)) { + me.defaults[type] = helpers$1.extend(me.defaults[type], additions); + } + }, + addScalesToLayout: function(chart) { + // Adds each scale to the chart.boxes array to be sized accordingly + helpers$1.each(chart.scales, function(scale) { + // Set ILayoutItem parameters for backwards compatibility + scale.fullWidth = scale.options.fullWidth; + scale.position = scale.options.position; + scale.weight = scale.options.weight; + core_layouts.addBox(chart, scale); + }); + } +}; + +var valueOrDefault$8 = helpers$1.valueOrDefault; +var getRtlHelper = helpers$1.rtl.getRtlAdapter; + +core_defaults._set('global', { + tooltips: { + enabled: true, + custom: null, + mode: 'nearest', + position: 'average', + intersect: true, + backgroundColor: 'rgba(0,0,0,0.8)', + titleFontStyle: 'bold', + titleSpacing: 2, + titleMarginBottom: 6, + titleFontColor: '#fff', + titleAlign: 'left', + bodySpacing: 2, + bodyFontColor: '#fff', + bodyAlign: 'left', + footerFontStyle: 'bold', + footerSpacing: 2, + footerMarginTop: 6, + footerFontColor: '#fff', + footerAlign: 'left', + yPadding: 6, + xPadding: 6, + caretPadding: 2, + caretSize: 5, + cornerRadius: 6, + multiKeyBackground: '#fff', + displayColors: true, + borderColor: 'rgba(0,0,0,0)', + borderWidth: 0, + callbacks: { + // Args are: (tooltipItems, data) + beforeTitle: helpers$1.noop, + title: function(tooltipItems, data) { + var title = ''; + var labels = data.labels; + var labelCount = labels ? labels.length : 0; + + if (tooltipItems.length > 0) { + var item = tooltipItems[0]; + if (item.label) { + title = item.label; + } else if (item.xLabel) { + title = item.xLabel; + } else if (labelCount > 0 && item.index < labelCount) { + title = labels[item.index]; + } + } + + return title; + }, + afterTitle: helpers$1.noop, + + // Args are: (tooltipItems, data) + beforeBody: helpers$1.noop, + + // Args are: (tooltipItem, data) + beforeLabel: helpers$1.noop, + label: function(tooltipItem, data) { + var label = data.datasets[tooltipItem.datasetIndex].label || ''; + + if (label) { + label += ': '; + } + if (!helpers$1.isNullOrUndef(tooltipItem.value)) { + label += tooltipItem.value; + } else { + label += tooltipItem.yLabel; + } + return label; + }, + labelColor: function(tooltipItem, chart) { + var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); + var activeElement = meta.data[tooltipItem.index]; + var view = activeElement._view; + return { + borderColor: view.borderColor, + backgroundColor: view.backgroundColor + }; + }, + labelTextColor: function() { + return this._options.bodyFontColor; + }, + afterLabel: helpers$1.noop, + + // Args are: (tooltipItems, data) + afterBody: helpers$1.noop, + + // Args are: (tooltipItems, data) + beforeFooter: helpers$1.noop, + footer: helpers$1.noop, + afterFooter: helpers$1.noop + } + } +}); + +var positioners = { + /** + * Average mode places the tooltip at the average position of the elements shown + * @function Chart.Tooltip.positioners.average + * @param elements {ChartElement[]} the elements being displayed in the tooltip + * @returns {object} tooltip position + */ + average: function(elements) { + if (!elements.length) { + return false; + } + + var i, len; + var x = 0; + var y = 0; + var count = 0; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var pos = el.tooltipPosition(); + x += pos.x; + y += pos.y; + ++count; + } + } + + return { + x: x / count, + y: y / count + }; + }, + + /** + * Gets the tooltip position nearest of the item nearest to the event position + * @function Chart.Tooltip.positioners.nearest + * @param elements {Chart.Element[]} the tooltip elements + * @param eventPosition {object} the position of the event in canvas coordinates + * @returns {object} the tooltip position + */ + nearest: function(elements, eventPosition) { + var x = eventPosition.x; + var y = eventPosition.y; + var minDistance = Number.POSITIVE_INFINITY; + var i, len, nearestElement; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()) { + var center = el.getCenterPoint(); + var d = helpers$1.distanceBetweenPoints(eventPosition, center); + + if (d < minDistance) { + minDistance = d; + nearestElement = el; + } + } + } + + if (nearestElement) { + var tp = nearestElement.tooltipPosition(); + x = tp.x; + y = tp.y; + } + + return { + x: x, + y: y + }; + } +}; + +// Helper to push or concat based on if the 2nd parameter is an array or not +function pushOrConcat(base, toPush) { + if (toPush) { + if (helpers$1.isArray(toPush)) { + // base = base.concat(toPush); + Array.prototype.push.apply(base, toPush); + } else { + base.push(toPush); + } + } + + return base; +} + +/** + * Returns array of strings split by newline + * @param {string} value - The value to split by newline. + * @returns {string[]} value if newline present - Returned from String split() method + * @function + */ +function splitNewlines(str) { + if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { + return str.split('\n'); + } + return str; +} + + +/** + * Private helper to create a tooltip item model + * @param element - the chart element (point, arc, bar) to create the tooltip item for + * @return new tooltip item + */ +function createTooltipItem(element) { + var xScale = element._xScale; + var yScale = element._yScale || element._scale; // handle radar || polarArea charts + var index = element._index; + var datasetIndex = element._datasetIndex; + var controller = element._chart.getDatasetMeta(datasetIndex).controller; + var indexScale = controller._getIndexScale(); + var valueScale = controller._getValueScale(); + + return { + xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', + label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '', + value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '', + index: index, + datasetIndex: datasetIndex, + x: element._model.x, + y: element._model.y + }; +} + +/** + * Helper to get the reset model for the tooltip + * @param tooltipOpts {object} the tooltip options + */ +function getBaseModel(tooltipOpts) { + var globalDefaults = core_defaults.global; + + return { + // Positioning + xPadding: tooltipOpts.xPadding, + yPadding: tooltipOpts.yPadding, + xAlign: tooltipOpts.xAlign, + yAlign: tooltipOpts.yAlign, + + // Drawing direction and text direction + rtl: tooltipOpts.rtl, + textDirection: tooltipOpts.textDirection, + + // Body + bodyFontColor: tooltipOpts.bodyFontColor, + _bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), + _bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), + _bodyAlign: tooltipOpts.bodyAlign, + bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), + bodySpacing: tooltipOpts.bodySpacing, + + // Title + titleFontColor: tooltipOpts.titleFontColor, + _titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), + _titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), + titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), + _titleAlign: tooltipOpts.titleAlign, + titleSpacing: tooltipOpts.titleSpacing, + titleMarginBottom: tooltipOpts.titleMarginBottom, + + // Footer + footerFontColor: tooltipOpts.footerFontColor, + _footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), + _footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), + footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), + _footerAlign: tooltipOpts.footerAlign, + footerSpacing: tooltipOpts.footerSpacing, + footerMarginTop: tooltipOpts.footerMarginTop, + + // Appearance + caretSize: tooltipOpts.caretSize, + cornerRadius: tooltipOpts.cornerRadius, + backgroundColor: tooltipOpts.backgroundColor, + opacity: 0, + legendColorBackground: tooltipOpts.multiKeyBackground, + displayColors: tooltipOpts.displayColors, + borderColor: tooltipOpts.borderColor, + borderWidth: tooltipOpts.borderWidth + }; +} + +/** + * Get the size of the tooltip + */ +function getTooltipSize(tooltip, model) { + var ctx = tooltip._chart.ctx; + + var height = model.yPadding * 2; // Tooltip Padding + var width = 0; + + // Count of all lines in the body + var body = model.body; + var combinedBodyLength = body.reduce(function(count, bodyItem) { + return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; + }, 0); + combinedBodyLength += model.beforeBody.length + model.afterBody.length; + + var titleLineCount = model.title.length; + var footerLineCount = model.footer.length; + var titleFontSize = model.titleFontSize; + var bodyFontSize = model.bodyFontSize; + var footerFontSize = model.footerFontSize; + + height += titleLineCount * titleFontSize; // Title Lines + height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing + height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin + height += combinedBodyLength * bodyFontSize; // Body Lines + height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing + height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin + height += footerLineCount * (footerFontSize); // Footer Lines + height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing + + // Title width + var widthPadding = 0; + var maxLineWidth = function(line) { + width = Math.max(width, ctx.measureText(line).width + widthPadding); + }; + + ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); + helpers$1.each(model.title, maxLineWidth); + + // Body width + ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); + helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth); + + // Body lines may include some extra width due to the color box + widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; + helpers$1.each(body, function(bodyItem) { + helpers$1.each(bodyItem.before, maxLineWidth); + helpers$1.each(bodyItem.lines, maxLineWidth); + helpers$1.each(bodyItem.after, maxLineWidth); + }); + + // Reset back to 0 + widthPadding = 0; + + // Footer width + ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); + helpers$1.each(model.footer, maxLineWidth); + + // Add padding + width += 2 * model.xPadding; + + return { + width: width, + height: height + }; +} + +/** + * Helper to get the alignment of a tooltip given the size + */ +function determineAlignment(tooltip, size) { + var model = tooltip._model; + var chart = tooltip._chart; + var chartArea = tooltip._chart.chartArea; + var xAlign = 'center'; + var yAlign = 'center'; + + if (model.y < size.height) { + yAlign = 'top'; + } else if (model.y > (chart.height - size.height)) { + yAlign = 'bottom'; + } + + var lf, rf; // functions to determine left, right alignment + var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart + var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges + var midX = (chartArea.left + chartArea.right) / 2; + var midY = (chartArea.top + chartArea.bottom) / 2; + + if (yAlign === 'center') { + lf = function(x) { + return x <= midX; + }; + rf = function(x) { + return x > midX; + }; + } else { + lf = function(x) { + return x <= (size.width / 2); + }; + rf = function(x) { + return x >= (chart.width - (size.width / 2)); + }; + } + + olf = function(x) { + return x + size.width + model.caretSize + model.caretPadding > chart.width; + }; + orf = function(x) { + return x - size.width - model.caretSize - model.caretPadding < 0; + }; + yf = function(y) { + return y <= midY ? 'top' : 'bottom'; + }; + + if (lf(model.x)) { + xAlign = 'left'; + + // Is tooltip too wide and goes over the right side of the chart.? + if (olf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } else if (rf(model.x)) { + xAlign = 'right'; + + // Is tooltip too wide and goes outside left edge of canvas? + if (orf(model.x)) { + xAlign = 'center'; + yAlign = yf(model.y); + } + } + + var opts = tooltip._options; + return { + xAlign: opts.xAlign ? opts.xAlign : xAlign, + yAlign: opts.yAlign ? opts.yAlign : yAlign + }; +} + +/** + * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment + */ +function getBackgroundPoint(vm, size, alignment, chart) { + // Background Position + var x = vm.x; + var y = vm.y; + + var caretSize = vm.caretSize; + var caretPadding = vm.caretPadding; + var cornerRadius = vm.cornerRadius; + var xAlign = alignment.xAlign; + var yAlign = alignment.yAlign; + var paddingAndSize = caretSize + caretPadding; + var radiusAndPadding = cornerRadius + caretPadding; + + if (xAlign === 'right') { + x -= size.width; + } else if (xAlign === 'center') { + x -= (size.width / 2); + if (x + size.width > chart.width) { + x = chart.width - size.width; + } + if (x < 0) { + x = 0; + } + } + + if (yAlign === 'top') { + y += paddingAndSize; + } else if (yAlign === 'bottom') { + y -= size.height + paddingAndSize; + } else { + y -= (size.height / 2); + } + + if (yAlign === 'center') { + if (xAlign === 'left') { + x += paddingAndSize; + } else if (xAlign === 'right') { + x -= paddingAndSize; + } + } else if (xAlign === 'left') { + x -= radiusAndPadding; + } else if (xAlign === 'right') { + x += radiusAndPadding; + } + + return { + x: x, + y: y + }; +} + +function getAlignedX(vm, align) { + return align === 'center' + ? vm.x + vm.width / 2 + : align === 'right' + ? vm.x + vm.width - vm.xPadding + : vm.x + vm.xPadding; +} + +/** + * Helper to build before and after body lines + */ +function getBeforeAfterBodyLines(callback) { + return pushOrConcat([], splitNewlines(callback)); +} + +var exports$4 = core_element.extend({ + initialize: function() { + this._model = getBaseModel(this._options); + this._lastActive = []; + }, + + // Get the title + // Args are: (tooltipItem, data) + getTitle: function() { + var me = this; + var opts = me._options; + var callbacks = opts.callbacks; + + var beforeTitle = callbacks.beforeTitle.apply(me, arguments); + var title = callbacks.title.apply(me, arguments); + var afterTitle = callbacks.afterTitle.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeTitle)); + lines = pushOrConcat(lines, splitNewlines(title)); + lines = pushOrConcat(lines, splitNewlines(afterTitle)); + + return lines; + }, + + // Args are: (tooltipItem, data) + getBeforeBody: function() { + return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); + }, + + // Args are: (tooltipItem, data) + getBody: function(tooltipItems, data) { + var me = this; + var callbacks = me._options.callbacks; + var bodyItems = []; + + helpers$1.each(tooltipItems, function(tooltipItem) { + var bodyItem = { + before: [], + lines: [], + after: [] + }; + pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); + pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); + pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); + + bodyItems.push(bodyItem); + }); + + return bodyItems; + }, + + // Args are: (tooltipItem, data) + getAfterBody: function() { + return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); + }, + + // Get the footer and beforeFooter and afterFooter lines + // Args are: (tooltipItem, data) + getFooter: function() { + var me = this; + var callbacks = me._options.callbacks; + + var beforeFooter = callbacks.beforeFooter.apply(me, arguments); + var footer = callbacks.footer.apply(me, arguments); + var afterFooter = callbacks.afterFooter.apply(me, arguments); + + var lines = []; + lines = pushOrConcat(lines, splitNewlines(beforeFooter)); + lines = pushOrConcat(lines, splitNewlines(footer)); + lines = pushOrConcat(lines, splitNewlines(afterFooter)); + + return lines; + }, + + update: function(changed) { + var me = this; + var opts = me._options; + + // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition + // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time + // which breaks any animations. + var existingModel = me._model; + var model = me._model = getBaseModel(opts); + var active = me._active; + + var data = me._data; + + // In the case where active.length === 0 we need to keep these at existing values for good animations + var alignment = { + xAlign: existingModel.xAlign, + yAlign: existingModel.yAlign + }; + var backgroundPoint = { + x: existingModel.x, + y: existingModel.y + }; + var tooltipSize = { + width: existingModel.width, + height: existingModel.height + }; + var tooltipPosition = { + x: existingModel.caretX, + y: existingModel.caretY + }; + + var i, len; + + if (active.length) { + model.opacity = 1; + + var labelColors = []; + var labelTextColors = []; + tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); + + var tooltipItems = []; + for (i = 0, len = active.length; i < len; ++i) { + tooltipItems.push(createTooltipItem(active[i])); + } + + // If the user provided a filter function, use it to modify the tooltip items + if (opts.filter) { + tooltipItems = tooltipItems.filter(function(a) { + return opts.filter(a, data); + }); + } + + // If the user provided a sorting function, use it to modify the tooltip items + if (opts.itemSort) { + tooltipItems = tooltipItems.sort(function(a, b) { + return opts.itemSort(a, b, data); + }); + } + + // Determine colors for boxes + helpers$1.each(tooltipItems, function(tooltipItem) { + labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); + labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); + }); + + + // Build the Text Lines + model.title = me.getTitle(tooltipItems, data); + model.beforeBody = me.getBeforeBody(tooltipItems, data); + model.body = me.getBody(tooltipItems, data); + model.afterBody = me.getAfterBody(tooltipItems, data); + model.footer = me.getFooter(tooltipItems, data); + + // Initial positioning and colors + model.x = tooltipPosition.x; + model.y = tooltipPosition.y; + model.caretPadding = opts.caretPadding; + model.labelColors = labelColors; + model.labelTextColors = labelTextColors; + + // data points + model.dataPoints = tooltipItems; + + // We need to determine alignment of the tooltip + tooltipSize = getTooltipSize(this, model); + alignment = determineAlignment(this, tooltipSize); + // Final Size and Position + backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); + } else { + model.opacity = 0; + } + + model.xAlign = alignment.xAlign; + model.yAlign = alignment.yAlign; + model.x = backgroundPoint.x; + model.y = backgroundPoint.y; + model.width = tooltipSize.width; + model.height = tooltipSize.height; + + // Point where the caret on the tooltip points to + model.caretX = tooltipPosition.x; + model.caretY = tooltipPosition.y; + + me._model = model; + + if (changed && opts.custom) { + opts.custom.call(me, model); + } + + return me; + }, + + drawCaret: function(tooltipPoint, size) { + var ctx = this._chart.ctx; + var vm = this._view; + var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); + + ctx.lineTo(caretPosition.x1, caretPosition.y1); + ctx.lineTo(caretPosition.x2, caretPosition.y2); + ctx.lineTo(caretPosition.x3, caretPosition.y3); + }, + getCaretPosition: function(tooltipPoint, size, vm) { + var x1, x2, x3, y1, y2, y3; + var caretSize = vm.caretSize; + var cornerRadius = vm.cornerRadius; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var ptX = tooltipPoint.x; + var ptY = tooltipPoint.y; + var width = size.width; + var height = size.height; + + if (yAlign === 'center') { + y2 = ptY + (height / 2); + + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; + x3 = x1; + + y1 = y2 + caretSize; + y3 = y2 - caretSize; + } else { + x1 = ptX + width; + x2 = x1 + caretSize; + x3 = x1; + + y1 = y2 - caretSize; + y3 = y2 + caretSize; + } + } else { + if (xAlign === 'left') { + x2 = ptX + cornerRadius + (caretSize); + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else if (xAlign === 'right') { + x2 = ptX + width - cornerRadius - caretSize; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } else { + x2 = vm.caretX; + x1 = x2 - caretSize; + x3 = x2 + caretSize; + } + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; + y3 = y1; + } else { + y1 = ptY + height; + y2 = y1 + caretSize; + y3 = y1; + // invert drawing order + var tmp = x3; + x3 = x1; + x1 = tmp; + } + } + return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; + }, + + drawTitle: function(pt, vm, ctx) { + var title = vm.title; + var length = title.length; + var titleFontSize, titleSpacing, i; + + if (length) { + var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); + + pt.x = getAlignedX(vm, vm._titleAlign); + + ctx.textAlign = rtlHelper.textAlign(vm._titleAlign); + ctx.textBaseline = 'middle'; + + titleFontSize = vm.titleFontSize; + titleSpacing = vm.titleSpacing; + + ctx.fillStyle = vm.titleFontColor; + ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + + for (i = 0; i < length; ++i) { + ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2); + pt.y += titleFontSize + titleSpacing; // Line Height and spacing + + if (i + 1 === length) { + pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing + } + } + } + }, + + drawBody: function(pt, vm, ctx) { + var bodyFontSize = vm.bodyFontSize; + var bodySpacing = vm.bodySpacing; + var bodyAlign = vm._bodyAlign; + var body = vm.body; + var drawColorBoxes = vm.displayColors; + var xLinePadding = 0; + var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0; + + var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); + + var fillLineOfText = function(line) { + ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2); + pt.y += bodyFontSize + bodySpacing; + }; + + var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen; + var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); + + ctx.textAlign = bodyAlign; + ctx.textBaseline = 'middle'; + ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + + pt.x = getAlignedX(vm, bodyAlignForCalculation); + + // Before body lines + ctx.fillStyle = vm.bodyFontColor; + helpers$1.each(vm.beforeBody, fillLineOfText); + + xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right' + ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2) + : 0; + + // Draw body lines now + for (i = 0, ilen = body.length; i < ilen; ++i) { + bodyItem = body[i]; + textColor = vm.labelTextColors[i]; + labelColors = vm.labelColors[i]; + + ctx.fillStyle = textColor; + helpers$1.each(bodyItem.before, fillLineOfText); + + lines = bodyItem.lines; + for (j = 0, jlen = lines.length; j < jlen; ++j) { + // Draw Legend-like boxes if needed + if (drawColorBoxes) { + var rtlColorX = rtlHelper.x(colorX); + + // Fill a white rect so that colours merge nicely if the opacity is < 1 + ctx.fillStyle = vm.legendColorBackground; + ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); + + // Border + ctx.lineWidth = 1; + ctx.strokeStyle = labelColors.borderColor; + ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); + + // Inner square + ctx.fillStyle = labelColors.backgroundColor; + ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); + ctx.fillStyle = textColor; + } + + fillLineOfText(lines[j]); + } + + helpers$1.each(bodyItem.after, fillLineOfText); + } + + // Reset back to 0 for after body + xLinePadding = 0; + + // After body lines + helpers$1.each(vm.afterBody, fillLineOfText); + pt.y -= bodySpacing; // Remove last body spacing + }, + + drawFooter: function(pt, vm, ctx) { + var footer = vm.footer; + var length = footer.length; + var footerFontSize, i; + + if (length) { + var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); + + pt.x = getAlignedX(vm, vm._footerAlign); + pt.y += vm.footerMarginTop; + + ctx.textAlign = rtlHelper.textAlign(vm._footerAlign); + ctx.textBaseline = 'middle'; + + footerFontSize = vm.footerFontSize; + + ctx.fillStyle = vm.footerFontColor; + ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + + for (i = 0; i < length; ++i) { + ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2); + pt.y += footerFontSize + vm.footerSpacing; + } + } + }, + + drawBackground: function(pt, vm, ctx, tooltipSize) { + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + var xAlign = vm.xAlign; + var yAlign = vm.yAlign; + var x = pt.x; + var y = pt.y; + var width = tooltipSize.width; + var height = tooltipSize.height; + var radius = vm.cornerRadius; + + ctx.beginPath(); + ctx.moveTo(x + radius, y); + if (yAlign === 'top') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + if (yAlign === 'center' && xAlign === 'right') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + if (yAlign === 'bottom') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + if (yAlign === 'center' && xAlign === 'left') { + this.drawCaret(pt, tooltipSize); + } + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + + ctx.fill(); + + if (vm.borderWidth > 0) { + ctx.stroke(); + } + }, + + draw: function() { + var ctx = this._chart.ctx; + var vm = this._view; + + if (vm.opacity === 0) { + return; + } + + var tooltipSize = { + width: vm.width, + height: vm.height + }; + var pt = { + x: vm.x, + y: vm.y + }; + + // IE11/Edge does not like very small opacities, so snap to 0 + var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; + + // Truthy/falsey value for empty tooltip + var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; + + if (this._options.enabled && hasTooltipContent) { + ctx.save(); + ctx.globalAlpha = opacity; + + // Draw Background + this.drawBackground(pt, vm, ctx, tooltipSize); + + // Draw Title, Body, and Footer + pt.y += vm.yPadding; + + helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection); + + // Titles + this.drawTitle(pt, vm, ctx); + + // Body + this.drawBody(pt, vm, ctx); + + // Footer + this.drawFooter(pt, vm, ctx); + + helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection); + + ctx.restore(); + } + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + * @returns {boolean} true if the tooltip changed + */ + handleEvent: function(e) { + var me = this; + var options = me._options; + var changed = false; + + me._lastActive = me._lastActive || []; + + // Find Active Elements for tooltips + if (e.type === 'mouseout') { + me._active = []; + } else { + me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); + if (options.reverse) { + me._active.reverse(); + } + } + + // Remember Last Actives + changed = !helpers$1.arrayEquals(me._active, me._lastActive); + + // Only handle target event on tooltip change + if (changed) { + me._lastActive = me._active; + + if (options.enabled || options.custom) { + me._eventPosition = { + x: e.x, + y: e.y + }; + + me.update(true); + me.pivot(); + } + } + + return changed; + } +}); + +/** + * @namespace Chart.Tooltip.positioners + */ +var positioners_1 = positioners; + +var core_tooltip = exports$4; +core_tooltip.positioners = positioners_1; + +var valueOrDefault$9 = helpers$1.valueOrDefault; + +core_defaults._set('global', { + elements: {}, + events: [ + 'mousemove', + 'mouseout', + 'click', + 'touchstart', + 'touchmove' + ], + hover: { + onHover: null, + mode: 'nearest', + intersect: true, + animationDuration: 400 + }, + onClick: null, + maintainAspectRatio: true, + responsive: true, + responsiveAnimationDuration: 0 +}); + +/** + * Recursively merge the given config objects representing the `scales` option + * by incorporating scale defaults in `xAxes` and `yAxes` array items, then + * returns a deep copy of the result, thus doesn't alter inputs. + */ +function mergeScaleConfig(/* config objects ... */) { + return helpers$1.merge({}, [].slice.call(arguments), { + merger: function(key, target, source, options) { + if (key === 'xAxes' || key === 'yAxes') { + var slen = source[key].length; + var i, type, scale; + + if (!target[key]) { + target[key] = []; + } + + for (i = 0; i < slen; ++i) { + scale = source[key][i]; + type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear'); + + if (i >= target[key].length) { + target[key].push({}); + } + + if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { + // new/untyped scale or type changed: let's apply the new defaults + // then merge source scale to correctly overwrite the defaults. + helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]); + } else { + // scales type are the same + helpers$1.merge(target[key][i], scale); + } + } + } else { + helpers$1._merger(key, target, source, options); + } + } + }); +} + +/** + * Recursively merge the given config objects as the root options by handling + * default scale options for the `scales` and `scale` properties, then returns + * a deep copy of the result, thus doesn't alter inputs. + */ +function mergeConfig(/* config objects ... */) { + return helpers$1.merge({}, [].slice.call(arguments), { + merger: function(key, target, source, options) { + var tval = target[key] || {}; + var sval = source[key]; + + if (key === 'scales') { + // scale config merging is complex. Add our own function here for that + target[key] = mergeScaleConfig(tval, sval); + } else if (key === 'scale') { + // used in polar area & radar charts since there is only one scale + target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]); + } else { + helpers$1._merger(key, target, source, options); + } + } + }); +} + +function initConfig(config) { + config = config || {}; + + // Do NOT use mergeConfig for the data object because this method merges arrays + // and so would change references to labels and datasets, preventing data updates. + var data = config.data = config.data || {}; + data.datasets = data.datasets || []; + data.labels = data.labels || []; + + config.options = mergeConfig( + core_defaults.global, + core_defaults[config.type], + config.options || {}); + + return config; +} + +function updateConfig(chart) { + var newOptions = chart.options; + + helpers$1.each(chart.scales, function(scale) { + core_layouts.removeBox(chart, scale); + }); + + newOptions = mergeConfig( + core_defaults.global, + core_defaults[chart.config.type], + newOptions); + + chart.options = chart.config.options = newOptions; + chart.ensureScalesHaveIDs(); + chart.buildOrUpdateScales(); + + // Tooltip + chart.tooltip._options = newOptions.tooltips; + chart.tooltip.initialize(); +} + +function nextAvailableScaleId(axesOpts, prefix, index) { + var id; + var hasId = function(obj) { + return obj.id === id; + }; + + do { + id = prefix + index++; + } while (helpers$1.findIndex(axesOpts, hasId) >= 0); + + return id; +} + +function positionIsHorizontal(position) { + return position === 'top' || position === 'bottom'; +} + +function compare2Level(l1, l2) { + return function(a, b) { + return a[l1] === b[l1] + ? a[l2] - b[l2] + : a[l1] - b[l1]; + }; +} + +var Chart = function(item, config) { + this.construct(item, config); + return this; +}; + +helpers$1.extend(Chart.prototype, /** @lends Chart */ { + /** + * @private + */ + construct: function(item, config) { + var me = this; + + config = initConfig(config); + + var context = platform.acquireContext(item, config); + var canvas = context && context.canvas; + var height = canvas && canvas.height; + var width = canvas && canvas.width; + + me.id = helpers$1.uid(); + me.ctx = context; + me.canvas = canvas; + me.config = config; + me.width = width; + me.height = height; + me.aspectRatio = height ? width / height : null; + me.options = config.options; + me._bufferedRender = false; + me._layers = []; + + /** + * Provided for backward compatibility, Chart and Chart.Controller have been merged, + * the "instance" still need to be defined since it might be called from plugins. + * @prop Chart#chart + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ + me.chart = me; + me.controller = me; // chart.chart.controller #inception + + // Add the chart instance to the global namespace + Chart.instances[me.id] = me; + + // Define alias to the config data: `chart.data === chart.config.data` + Object.defineProperty(me, 'data', { + get: function() { + return me.config.data; + }, + set: function(value) { + me.config.data = value; + } + }); + + if (!context || !canvas) { + // The given item is not a compatible context2d element, let's return before finalizing + // the chart initialization but after setting basic chart / controller properties that + // can help to figure out that the chart is not valid (e.g chart.canvas !== null); + // https://github.com/chartjs/Chart.js/issues/2807 + console.error("Failed to create chart: can't acquire context from the given item"); + return; + } + + me.initialize(); + me.update(); + }, + + /** + * @private + */ + initialize: function() { + var me = this; + + // Before init plugin notification + core_plugins.notify(me, 'beforeInit'); + + helpers$1.retinaScale(me, me.options.devicePixelRatio); + + me.bindEvents(); + + if (me.options.responsive) { + // Initial resize before chart draws (must be silent to preserve initial animations). + me.resize(true); + } + + me.initToolTip(); + + // After init plugin notification + core_plugins.notify(me, 'afterInit'); + + return me; + }, + + clear: function() { + helpers$1.canvas.clear(this); + return this; + }, + + stop: function() { + // Stops any current animation loop occurring + core_animations.cancelAnimation(this); + return this; + }, + + resize: function(silent) { + var me = this; + var options = me.options; + var canvas = me.canvas; + var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; + + // the canvas render width and height will be casted to integers so make sure that + // the canvas display style uses the same integer values to avoid blurring effect. + + // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed + var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas))); + var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas))); + + if (me.width === newWidth && me.height === newHeight) { + return; + } + + canvas.width = me.width = newWidth; + canvas.height = me.height = newHeight; + canvas.style.width = newWidth + 'px'; + canvas.style.height = newHeight + 'px'; + + helpers$1.retinaScale(me, options.devicePixelRatio); + + if (!silent) { + // Notify any plugins about the resize + var newSize = {width: newWidth, height: newHeight}; + core_plugins.notify(me, 'resize', [newSize]); + + // Notify of resize + if (options.onResize) { + options.onResize(me, newSize); + } + + me.stop(); + me.update({ + duration: options.responsiveAnimationDuration + }); + } + }, + + ensureScalesHaveIDs: function() { + var options = this.options; + var scalesOptions = options.scales || {}; + var scaleOptions = options.scale; + + helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) { + if (!xAxisOptions.id) { + xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index); + } + }); + + helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) { + if (!yAxisOptions.id) { + yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index); + } + }); + + if (scaleOptions) { + scaleOptions.id = scaleOptions.id || 'scale'; + } + }, + + /** + * Builds a map of scale ID to scale object for future lookup. + */ + buildOrUpdateScales: function() { + var me = this; + var options = me.options; + var scales = me.scales || {}; + var items = []; + var updated = Object.keys(scales).reduce(function(obj, id) { + obj[id] = false; + return obj; + }, {}); + + if (options.scales) { + items = items.concat( + (options.scales.xAxes || []).map(function(xAxisOptions) { + return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; + }), + (options.scales.yAxes || []).map(function(yAxisOptions) { + return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; + }) + ); + } + + if (options.scale) { + items.push({ + options: options.scale, + dtype: 'radialLinear', + isDefault: true, + dposition: 'chartArea' + }); + } + + helpers$1.each(items, function(item) { + var scaleOptions = item.options; + var id = scaleOptions.id; + var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype); + + if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { + scaleOptions.position = item.dposition; + } + + updated[id] = true; + var scale = null; + if (id in scales && scales[id].type === scaleType) { + scale = scales[id]; + scale.options = scaleOptions; + scale.ctx = me.ctx; + scale.chart = me; + } else { + var scaleClass = core_scaleService.getScaleConstructor(scaleType); + if (!scaleClass) { + return; + } + scale = new scaleClass({ + id: id, + type: scaleType, + options: scaleOptions, + ctx: me.ctx, + chart: me + }); + scales[scale.id] = scale; + } + + scale.mergeTicksOptions(); + + // TODO(SB): I think we should be able to remove this custom case (options.scale) + // and consider it as a regular scale part of the "scales"" map only! This would + // make the logic easier and remove some useless? custom code. + if (item.isDefault) { + me.scale = scale; + } + }); + // clear up discarded scales + helpers$1.each(updated, function(hasUpdated, id) { + if (!hasUpdated) { + delete scales[id]; + } + }); + + me.scales = scales; + + core_scaleService.addScalesToLayout(this); + }, + + buildOrUpdateControllers: function() { + var me = this; + var newControllers = []; + var datasets = me.data.datasets; + var i, ilen; + + for (i = 0, ilen = datasets.length; i < ilen; i++) { + var dataset = datasets[i]; + var meta = me.getDatasetMeta(i); + var type = dataset.type || me.config.type; + + if (meta.type && meta.type !== type) { + me.destroyDatasetMeta(i); + meta = me.getDatasetMeta(i); + } + meta.type = type; + meta.order = dataset.order || 0; + meta.index = i; + + if (meta.controller) { + meta.controller.updateIndex(i); + meta.controller.linkScales(); + } else { + var ControllerClass = controllers[meta.type]; + if (ControllerClass === undefined) { + throw new Error('"' + meta.type + '" is not a chart type.'); + } + + meta.controller = new ControllerClass(me, i); + newControllers.push(meta.controller); + } + } + + return newControllers; + }, + + /** + * Reset the elements of all datasets + * @private + */ + resetElements: function() { + var me = this; + helpers$1.each(me.data.datasets, function(dataset, datasetIndex) { + me.getDatasetMeta(datasetIndex).controller.reset(); + }, me); + }, + + /** + * Resets the chart back to it's state before the initial animation + */ + reset: function() { + this.resetElements(); + this.tooltip.initialize(); + }, + + update: function(config) { + var me = this; + var i, ilen; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + updateConfig(me); + + // plugins options references might have change, let's invalidate the cache + // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 + core_plugins._invalidate(me); + + if (core_plugins.notify(me, 'beforeUpdate') === false) { + return; + } + + // In case the entire data object changed + me.tooltip._data = me.data; + + // Make sure dataset controllers are updated and new controllers are reset + var newControllers = me.buildOrUpdateControllers(); + + // Make sure all dataset controllers have correct meta data counts + for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) { + me.getDatasetMeta(i).controller.buildOrUpdateElements(); + } + + me.updateLayout(); + + // Can only reset the new controllers after the scales have been updated + if (me.options.animation && me.options.animation.duration) { + helpers$1.each(newControllers, function(controller) { + controller.reset(); + }); + } + + me.updateDatasets(); + + // Need to reset tooltip in case it is displayed with elements that are removed + // after update. + me.tooltip.initialize(); + + // Last active contains items that were previously in the tooltip. + // When we reset the tooltip, we need to clear it + me.lastActive = []; + + // Do this before render so that any plugins that need final scale updates can use it + core_plugins.notify(me, 'afterUpdate'); + + me._layers.sort(compare2Level('z', '_idx')); + + if (me._bufferedRender) { + me._bufferedRequest = { + duration: config.duration, + easing: config.easing, + lazy: config.lazy + }; + } else { + me.render(config); + } + }, + + /** + * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` + * hook, in which case, plugins will not be called on `afterLayout`. + * @private + */ + updateLayout: function() { + var me = this; + + if (core_plugins.notify(me, 'beforeLayout') === false) { + return; + } + + core_layouts.update(this, this.width, this.height); + + me._layers = []; + helpers$1.each(me.boxes, function(box) { + // _configure is called twice, once in core.scale.update and once here. + // Here the boxes are fully updated and at their final positions. + if (box._configure) { + box._configure(); + } + me._layers.push.apply(me._layers, box._layers()); + }, me); + + me._layers.forEach(function(item, index) { + item._idx = index; + }); + + /** + * Provided for backward compatibility, use `afterLayout` instead. + * @method IPlugin#afterScaleUpdate + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ + core_plugins.notify(me, 'afterScaleUpdate'); + core_plugins.notify(me, 'afterLayout'); + }, + + /** + * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` + * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. + * @private + */ + updateDatasets: function() { + var me = this; + + if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) { + return; + } + + for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.updateDataset(i); + } + + core_plugins.notify(me, 'afterDatasetsUpdate'); + }, + + /** + * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` + * hook, in which case, plugins will not be called on `afterDatasetUpdate`. + * @private + */ + updateDataset: function(index) { + var me = this; + var meta = me.getDatasetMeta(index); + var args = { + meta: meta, + index: index + }; + + if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { + return; + } + + meta.controller._update(); + + core_plugins.notify(me, 'afterDatasetUpdate', [args]); + }, + + render: function(config) { + var me = this; + + if (!config || typeof config !== 'object') { + // backwards compatibility + config = { + duration: config, + lazy: arguments[1] + }; + } + + var animationOptions = me.options.animation; + var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration); + var lazy = config.lazy; + + if (core_plugins.notify(me, 'beforeRender') === false) { + return; + } + + var onComplete = function(animation) { + core_plugins.notify(me, 'afterRender'); + helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me); + }; + + if (animationOptions && duration) { + var animation = new core_animation({ + numSteps: duration / 16.66, // 60 fps + easing: config.easing || animationOptions.easing, + + render: function(chart, animationObject) { + var easingFunction = helpers$1.easing.effects[animationObject.easing]; + var currentStep = animationObject.currentStep; + var stepDecimal = currentStep / animationObject.numSteps; + + chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); + }, + + onAnimationProgress: animationOptions.onProgress, + onAnimationComplete: onComplete + }); + + core_animations.addAnimation(me, animation, duration, lazy); + } else { + me.draw(); + + // See https://github.com/chartjs/Chart.js/issues/3781 + onComplete(new core_animation({numSteps: 0, chart: me})); + } + + return me; + }, + + draw: function(easingValue) { + var me = this; + var i, layers; + + me.clear(); + + if (helpers$1.isNullOrUndef(easingValue)) { + easingValue = 1; + } + + me.transition(easingValue); + + if (me.width <= 0 || me.height <= 0) { + return; + } + + if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) { + return; + } + + // Because of plugin hooks (before/afterDatasetsDraw), datasets can't + // currently be part of layers. Instead, we draw + // layers <= 0 before(default, backward compat), and the rest after + layers = me._layers; + for (i = 0; i < layers.length && layers[i].z <= 0; ++i) { + layers[i].draw(me.chartArea); + } + + me.drawDatasets(easingValue); + + // Rest of layers + for (; i < layers.length; ++i) { + layers[i].draw(me.chartArea); + } + + me._drawTooltip(easingValue); + + core_plugins.notify(me, 'afterDraw', [easingValue]); + }, + + /** + * @private + */ + transition: function(easingValue) { + var me = this; + + for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { + if (me.isDatasetVisible(i)) { + me.getDatasetMeta(i).controller.transition(easingValue); + } + } + + me.tooltip.transition(easingValue); + }, + + /** + * @private + */ + _getSortedDatasetMetas: function(filterVisible) { + var me = this; + var datasets = me.data.datasets || []; + var result = []; + var i, ilen; + + for (i = 0, ilen = datasets.length; i < ilen; ++i) { + if (!filterVisible || me.isDatasetVisible(i)) { + result.push(me.getDatasetMeta(i)); + } + } + + result.sort(compare2Level('order', 'index')); + + return result; + }, + + /** + * @private + */ + _getSortedVisibleDatasetMetas: function() { + return this._getSortedDatasetMetas(true); + }, + + /** + * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` + * hook, in which case, plugins will not be called on `afterDatasetsDraw`. + * @private + */ + drawDatasets: function(easingValue) { + var me = this; + var metasets, i; + + if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { + return; + } + + metasets = me._getSortedVisibleDatasetMetas(); + for (i = metasets.length - 1; i >= 0; --i) { + me.drawDataset(metasets[i], easingValue); + } + + core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]); + }, + + /** + * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` + * hook, in which case, plugins will not be called on `afterDatasetDraw`. + * @private + */ + drawDataset: function(meta, easingValue) { + var me = this; + var args = { + meta: meta, + index: meta.index, + easingValue: easingValue + }; + + if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { + return; + } + + meta.controller.draw(easingValue); + + core_plugins.notify(me, 'afterDatasetDraw', [args]); + }, + + /** + * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` + * hook, in which case, plugins will not be called on `afterTooltipDraw`. + * @private + */ + _drawTooltip: function(easingValue) { + var me = this; + var tooltip = me.tooltip; + var args = { + tooltip: tooltip, + easingValue: easingValue + }; + + if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { + return; + } + + tooltip.draw(); + + core_plugins.notify(me, 'afterTooltipDraw', [args]); + }, + + /** + * Get the single element that was clicked on + * @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw + */ + getElementAtEvent: function(e) { + return core_interaction.modes.single(this, e); + }, + + getElementsAtEvent: function(e) { + return core_interaction.modes.label(this, e, {intersect: true}); + }, + + getElementsAtXAxis: function(e) { + return core_interaction.modes['x-axis'](this, e, {intersect: true}); + }, + + getElementsAtEventForMode: function(e, mode, options) { + var method = core_interaction.modes[mode]; + if (typeof method === 'function') { + return method(this, e, options); + } + + return []; + }, + + getDatasetAtEvent: function(e) { + return core_interaction.modes.dataset(this, e, {intersect: true}); + }, + + getDatasetMeta: function(datasetIndex) { + var me = this; + var dataset = me.data.datasets[datasetIndex]; + if (!dataset._meta) { + dataset._meta = {}; + } + + var meta = dataset._meta[me.id]; + if (!meta) { + meta = dataset._meta[me.id] = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, // See isDatasetVisible() comment + xAxisID: null, + yAxisID: null, + order: dataset.order || 0, + index: datasetIndex + }; + } + + return meta; + }, + + getVisibleDatasetCount: function() { + var count = 0; + for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { + if (this.isDatasetVisible(i)) { + count++; + } + } + return count; + }, + + isDatasetVisible: function(datasetIndex) { + var meta = this.getDatasetMeta(datasetIndex); + + // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, + // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. + return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; + }, + + generateLegend: function() { + return this.options.legendCallback(this); + }, + + /** + * @private + */ + destroyDatasetMeta: function(datasetIndex) { + var id = this.id; + var dataset = this.data.datasets[datasetIndex]; + var meta = dataset._meta && dataset._meta[id]; + + if (meta) { + meta.controller.destroy(); + delete dataset._meta[id]; + } + }, + + destroy: function() { + var me = this; + var canvas = me.canvas; + var i, ilen; + + me.stop(); + + // dataset controllers need to cleanup associated data + for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { + me.destroyDatasetMeta(i); + } + + if (canvas) { + me.unbindEvents(); + helpers$1.canvas.clear(me); + platform.releaseContext(me.ctx); + me.canvas = null; + me.ctx = null; + } + + core_plugins.notify(me, 'destroy'); + + delete Chart.instances[me.id]; + }, + + toBase64Image: function() { + return this.canvas.toDataURL.apply(this.canvas, arguments); + }, + + initToolTip: function() { + var me = this; + me.tooltip = new core_tooltip({ + _chart: me, + _chartInstance: me, // deprecated, backward compatibility + _data: me.data, + _options: me.options.tooltips + }, me); + }, + + /** + * @private + */ + bindEvents: function() { + var me = this; + var listeners = me._listeners = {}; + var listener = function() { + me.eventHandler.apply(me, arguments); + }; + + helpers$1.each(me.options.events, function(type) { + platform.addEventListener(me, type, listener); + listeners[type] = listener; + }); + + // Elements used to detect size change should not be injected for non responsive charts. + // See https://github.com/chartjs/Chart.js/issues/2210 + if (me.options.responsive) { + listener = function() { + me.resize(); + }; + + platform.addEventListener(me, 'resize', listener); + listeners.resize = listener; + } + }, + + /** + * @private + */ + unbindEvents: function() { + var me = this; + var listeners = me._listeners; + if (!listeners) { + return; + } + + delete me._listeners; + helpers$1.each(listeners, function(listener, type) { + platform.removeEventListener(me, type, listener); + }); + }, + + updateHoverStyle: function(elements, mode, enabled) { + var prefix = enabled ? 'set' : 'remove'; + var element, i, ilen; + + for (i = 0, ilen = elements.length; i < ilen; ++i) { + element = elements[i]; + if (element) { + this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element); + } + } + + if (mode === 'dataset') { + this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle'](); + } + }, + + /** + * @private + */ + eventHandler: function(e) { + var me = this; + var tooltip = me.tooltip; + + if (core_plugins.notify(me, 'beforeEvent', [e]) === false) { + return; + } + + // Buffer any update calls so that renders do not occur + me._bufferedRender = true; + me._bufferedRequest = null; + + var changed = me.handleEvent(e); + // for smooth tooltip animations issue #4989 + // the tooltip should be the source of change + // Animation check workaround: + // tooltip._start will be null when tooltip isn't animating + if (tooltip) { + changed = tooltip._start + ? tooltip.handleEvent(e) + : changed | tooltip.handleEvent(e); + } + + core_plugins.notify(me, 'afterEvent', [e]); + + var bufferedRequest = me._bufferedRequest; + if (bufferedRequest) { + // If we have an update that was triggered, we need to do a normal render + me.render(bufferedRequest); + } else if (changed && !me.animating) { + // If entering, leaving, or changing elements, animate the change via pivot + me.stop(); + + // We only need to render at this point. Updating will cause scales to be + // recomputed generating flicker & using more memory than necessary. + me.render({ + duration: me.options.hover.animationDuration, + lazy: true + }); + } + + me._bufferedRender = false; + me._bufferedRequest = null; + + return me; + }, + + /** + * Handle an event + * @private + * @param {IEvent} event the event to handle + * @return {boolean} true if the chart needs to re-render + */ + handleEvent: function(e) { + var me = this; + var options = me.options || {}; + var hoverOptions = options.hover; + var changed = false; + + me.lastActive = me.lastActive || []; + + // Find Active Elements for hover and tooltips + if (e.type === 'mouseout') { + me.active = []; + } else { + me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); + } + + // Invoke onHover hook + // Need to call with native event here to not break backwards compatibility + helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); + + if (e.type === 'mouseup' || e.type === 'click') { + if (options.onClick) { + // Use e.native here for backwards compatibility + options.onClick.call(me, e.native, me.active); + } + } + + // Remove styling for last active (even if it may still be active) + if (me.lastActive.length) { + me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); + } + + // Built in hover styling + if (me.active.length && hoverOptions.mode) { + me.updateHoverStyle(me.active, hoverOptions.mode, true); + } + + changed = !helpers$1.arrayEquals(me.active, me.lastActive); + + // Remember Last Actives + me.lastActive = me.active; + + return changed; + } +}); + +/** + * NOTE(SB) We actually don't use this container anymore but we need to keep it + * for backward compatibility. Though, it can still be useful for plugins that + * would need to work on multiple charts?! + */ +Chart.instances = {}; + +var core_controller = Chart; + +// DEPRECATIONS + +/** + * Provided for backward compatibility, use Chart instead. + * @class Chart.Controller + * @deprecated since version 2.6 + * @todo remove at version 3 + * @private + */ +Chart.Controller = Chart; + +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart + * @deprecated since version 2.8 + * @todo remove at version 3 + * @private + */ +Chart.types = {}; + +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart.helpers.configMerge + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ +helpers$1.configMerge = mergeConfig; + +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart.helpers.scaleMerge + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ +helpers$1.scaleMerge = mergeScaleConfig; + +var core_helpers = function() { + + // -- Basic js utility methods + + helpers$1.where = function(collection, filterCallback) { + if (helpers$1.isArray(collection) && Array.prototype.filter) { + return collection.filter(filterCallback); + } + var filtered = []; + + helpers$1.each(collection, function(item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); + + return filtered; + }; + helpers$1.findIndex = Array.prototype.findIndex ? + function(array, callback, scope) { + return array.findIndex(callback, scope); + } : + function(array, callback, scope) { + scope = scope === undefined ? array : scope; + for (var i = 0, ilen = array.length; i < ilen; ++i) { + if (callback.call(scope, array[i], i, array)) { + return i; + } + } + return -1; + }; + helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to start of the array + if (helpers$1.isNullOrUndef(startIndex)) { + startIndex = -1; + } + for (var i = startIndex + 1; i < arrayToSearch.length; i++) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to end of the array + if (helpers$1.isNullOrUndef(startIndex)) { + startIndex = arrayToSearch.length; + } + for (var i = startIndex - 1; i >= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }; + + // -- Math methods + helpers$1.isNumber = function(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + helpers$1.almostEquals = function(x, y, epsilon) { + return Math.abs(x - y) < epsilon; + }; + helpers$1.almostWhole = function(x, epsilon) { + var rounded = Math.round(x); + return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x); + }; + helpers$1.max = function(array) { + return array.reduce(function(max, value) { + if (!isNaN(value)) { + return Math.max(max, value); + } + return max; + }, Number.NEGATIVE_INFINITY); + }; + helpers$1.min = function(array) { + return array.reduce(function(min, value) { + if (!isNaN(value)) { + return Math.min(min, value); + } + return min; + }, Number.POSITIVE_INFINITY); + }; + helpers$1.sign = Math.sign ? + function(x) { + return Math.sign(x); + } : + function(x) { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + }; + helpers$1.toRadians = function(degrees) { + return degrees * (Math.PI / 180); + }; + helpers$1.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }; + + /** + * Returns the number of decimal places + * i.e. the number of digits after the decimal point, of the value of this Number. + * @param {number} x - A number. + * @returns {number} The number of decimal places. + * @private + */ + helpers$1._decimalPlaces = function(x) { + if (!helpers$1.isFinite(x)) { + return; + } + var e = 1; + var p = 0; + while (Math.round(x * e) / e !== x) { + e *= 10; + p++; + } + return p; + }; + + // Gets the angle from vertical upright to the point about a centre. + helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) { + var distanceFromXCenter = anglePoint.x - centrePoint.x; + var distanceFromYCenter = anglePoint.y - centrePoint.y; + var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + + var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } + + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }; + helpers$1.distanceBetweenPoints = function(pt1, pt2) { + return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); + }; + + /** + * Provided for backward compatibility, not available anymore + * @function Chart.helpers.aliasPixel + * @deprecated since version 2.8.0 + * @todo remove at version 3 + */ + helpers$1.aliasPixel = function(pixelWidth) { + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }; + + /** + * Returns the aligned pixel value to avoid anti-aliasing blur + * @param {Chart} chart - The chart instance. + * @param {number} pixel - A pixel value. + * @param {number} width - The width of the element. + * @returns {number} The aligned pixel value. + * @private + */ + helpers$1._alignPixel = function(chart, pixel, width) { + var devicePixelRatio = chart.currentDevicePixelRatio; + var halfWidth = width / 2; + return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; + }; + + helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { + // Props to Rob Spencer at scaled innovation for his post on splining between points + // http://scaledinnovation.com/analytics/splines/aboutSplines.html + + // This function must also respect "skipped" points + + var previous = firstPoint.skip ? middlePoint : firstPoint; + var current = middlePoint; + var next = afterPoint.skip ? middlePoint : afterPoint; + + var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); + var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); + + var s01 = d01 / (d01 + d12); + var s12 = d12 / (d01 + d12); + + // If all points are the same, s01 & s02 will be inf + s01 = isNaN(s01) ? 0 : s01; + s12 = isNaN(s12) ? 0 : s12; + + var fa = t * s01; // scaling factor for triangle Ta + var fb = t * s12; + + return { + previous: { + x: current.x - fa * (next.x - previous.x), + y: current.y - fa * (next.y - previous.y) + }, + next: { + x: current.x + fb * (next.x - previous.x), + y: current.y + fb * (next.y - previous.y) + } + }; + }; + helpers$1.EPSILON = Number.EPSILON || 1e-14; + helpers$1.splineCurveMonotone = function(points) { + // This function calculates Bézier control points in a similar way than |splineCurve|, + // but preserves monotonicity of the provided data and ensures no local extremums are added + // between the dataset discrete points due to the interpolation. + // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation + + var pointsWithTangents = (points || []).map(function(point) { + return { + model: point._model, + deltaK: 0, + mK: 0 + }; + }); + + // Calculate slopes (deltaK) and initialize tangents (mK) + var pointsLen = pointsWithTangents.length; + var i, pointBefore, pointCurrent, pointAfter; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointAfter && !pointAfter.model.skip) { + var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); + + // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 + pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; + } + + if (!pointBefore || pointBefore.model.skip) { + pointCurrent.mK = pointCurrent.deltaK; + } else if (!pointAfter || pointAfter.model.skip) { + pointCurrent.mK = pointBefore.deltaK; + } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { + pointCurrent.mK = 0; + } else { + pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; + } + } + + // Adjust tangents to ensure monotonic properties + var alphaK, betaK, tauK, squaredMagnitude; + for (i = 0; i < pointsLen - 1; ++i) { + pointCurrent = pointsWithTangents[i]; + pointAfter = pointsWithTangents[i + 1]; + if (pointCurrent.model.skip || pointAfter.model.skip) { + continue; + } + + if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { + pointCurrent.mK = pointAfter.mK = 0; + continue; + } + + alphaK = pointCurrent.mK / pointCurrent.deltaK; + betaK = pointAfter.mK / pointCurrent.deltaK; + squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); + if (squaredMagnitude <= 9) { + continue; + } + + tauK = 3 / Math.sqrt(squaredMagnitude); + pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; + pointAfter.mK = betaK * tauK * pointCurrent.deltaK; + } + + // Compute control points + var deltaX; + for (i = 0; i < pointsLen; ++i) { + pointCurrent = pointsWithTangents[i]; + if (pointCurrent.model.skip) { + continue; + } + + pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; + pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; + if (pointBefore && !pointBefore.model.skip) { + deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; + pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; + pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; + } + if (pointAfter && !pointAfter.model.skip) { + deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; + pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; + pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; + } + } + }; + helpers$1.nextItem = function(collection, index, loop) { + if (loop) { + return index >= collection.length - 1 ? collection[0] : collection[index + 1]; + } + return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; + }; + helpers$1.previousItem = function(collection, index, loop) { + if (loop) { + return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; + } + return index <= 0 ? collection[0] : collection[index - 1]; + }; + // Implementation of the nice number algorithm used in determining where axis labels will go + helpers$1.niceNum = function(range, round) { + var exponent = Math.floor(helpers$1.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + + return niceFraction * Math.pow(10, exponent); + }; + // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + helpers$1.requestAnimFrame = (function() { + if (typeof window === 'undefined') { + return function(callback) { + callback(); + }; + } + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + }()); + // -- DOM methods + helpers$1.getRelativePosition = function(evt, chart) { + var mouseX, mouseY; + var e = evt.originalEvent || evt; + var canvas = evt.target || evt.srcElement; + var boundingRect = canvas.getBoundingClientRect(); + + var touches = e.touches; + if (touches && touches.length > 0) { + mouseX = touches[0].clientX; + mouseY = touches[0].clientY; + + } else { + mouseX = e.clientX; + mouseY = e.clientY; + } + + // Scale mouse coordinates into canvas coordinates + // by following the pattern laid out by 'jerryj' in the comments of + // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ + var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left')); + var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top')); + var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right')); + var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom')); + var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; + var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; + + // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However + // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here + mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); + mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); + + return { + x: mouseX, + y: mouseY + }; + + }; + + // Private helper function to convert max-width/max-height values that may be percentages into a number + function parseMaxStyle(styleValue, node, parentProperty) { + var valueInPixels; + if (typeof styleValue === 'string') { + valueInPixels = parseInt(styleValue, 10); + + if (styleValue.indexOf('%') !== -1) { + // percentage * size in dimension + valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; + } + } else { + valueInPixels = styleValue; + } + + return valueInPixels; + } + + /** + * Returns if the given value contains an effective constraint. + * @private + */ + function isConstrainedValue(value) { + return value !== undefined && value !== null && value !== 'none'; + } + + /** + * Returns the max width or height of the given DOM node in a cross-browser compatible fashion + * @param {HTMLElement} domNode - the node to check the constraint on + * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height') + * @param {string} percentageProperty - property of parent to use when calculating width as a percentage + * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser} + */ + function getConstraintDimension(domNode, maxStyle, percentageProperty) { + var view = document.defaultView; + var parentNode = helpers$1._getParentNode(domNode); + var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; + var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; + var hasCNode = isConstrainedValue(constrainedNode); + var hasCContainer = isConstrainedValue(constrainedContainer); + var infinity = Number.POSITIVE_INFINITY; + + if (hasCNode || hasCContainer) { + return Math.min( + hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, + hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); + } + + return 'none'; + } + // returns Number or undefined if no constraint + helpers$1.getConstraintWidth = function(domNode) { + return getConstraintDimension(domNode, 'max-width', 'clientWidth'); + }; + // returns Number or undefined if no constraint + helpers$1.getConstraintHeight = function(domNode) { + return getConstraintDimension(domNode, 'max-height', 'clientHeight'); + }; + /** + * @private + */ + helpers$1._calculatePadding = function(container, padding, parentDimension) { + padding = helpers$1.getStyle(container, padding); + + return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10); + }; + /** + * @private + */ + helpers$1._getParentNode = function(domNode) { + var parent = domNode.parentNode; + if (parent && parent.toString() === '[object ShadowRoot]') { + parent = parent.host; + } + return parent; + }; + helpers$1.getMaximumWidth = function(domNode) { + var container = helpers$1._getParentNode(domNode); + if (!container) { + return domNode.clientWidth; + } + + var clientWidth = container.clientWidth; + var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth); + var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth); + + var w = clientWidth - paddingLeft - paddingRight; + var cw = helpers$1.getConstraintWidth(domNode); + return isNaN(cw) ? w : Math.min(w, cw); + }; + helpers$1.getMaximumHeight = function(domNode) { + var container = helpers$1._getParentNode(domNode); + if (!container) { + return domNode.clientHeight; + } + + var clientHeight = container.clientHeight; + var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight); + var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight); + + var h = clientHeight - paddingTop - paddingBottom; + var ch = helpers$1.getConstraintHeight(domNode); + return isNaN(ch) ? h : Math.min(h, ch); + }; + helpers$1.getStyle = function(el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }; + helpers$1.retinaScale = function(chart, forceRatio) { + var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; + if (pixelRatio === 1) { + return; + } + + var canvas = chart.canvas; + var height = chart.height; + var width = chart.width; + + canvas.height = height * pixelRatio; + canvas.width = width * pixelRatio; + chart.ctx.scale(pixelRatio, pixelRatio); + + // If no style has been set on the canvas, the render size is used as display size, + // making the chart visually bigger, so let's enforce it to the "correct" values. + // See https://github.com/chartjs/Chart.js/issues/3575 + if (!canvas.style.height && !canvas.style.width) { + canvas.style.height = height + 'px'; + canvas.style.width = width + 'px'; + } + }; + // -- Canvas methods + helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) { + return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; + }; + helpers$1.longestText = function(ctx, font, arrayOfThings, cache) { + cache = cache || {}; + var data = cache.data = cache.data || {}; + var gc = cache.garbageCollect = cache.garbageCollect || []; + + if (cache.font !== font) { + data = cache.data = {}; + gc = cache.garbageCollect = []; + cache.font = font; + } + + ctx.font = font; + var longest = 0; + var ilen = arrayOfThings.length; + var i, j, jlen, thing, nestedThing; + for (i = 0; i < ilen; i++) { + thing = arrayOfThings[i]; + + // Undefined strings and arrays should not be measured + if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) { + longest = helpers$1.measureText(ctx, data, gc, longest, thing); + } else if (helpers$1.isArray(thing)) { + // if it is an array lets measure each element + // to do maybe simplify this function a bit so we can do this more recursively? + for (j = 0, jlen = thing.length; j < jlen; j++) { + nestedThing = thing[j]; + // Undefined strings and arrays should not be measured + if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) { + longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing); + } + } + } + } + + var gcLen = gc.length / 2; + if (gcLen > arrayOfThings.length) { + for (i = 0; i < gcLen; i++) { + delete data[gc[i]]; + } + gc.splice(0, gcLen); + } + return longest; + }; + helpers$1.measureText = function(ctx, data, gc, longest, string) { + var textWidth = data[string]; + if (!textWidth) { + textWidth = data[string] = ctx.measureText(string).width; + gc.push(string); + } + if (textWidth > longest) { + longest = textWidth; + } + return longest; + }; + + /** + * @deprecated + */ + helpers$1.numberOfLabelLines = function(arrayOfThings) { + var numberOfLines = 1; + helpers$1.each(arrayOfThings, function(thing) { + if (helpers$1.isArray(thing)) { + if (thing.length > numberOfLines) { + numberOfLines = thing.length; + } + } + }); + return numberOfLines; + }; + + helpers$1.color = !chartjsColor ? + function(value) { + console.error('Color.js not found!'); + return value; + } : + function(value) { + /* global CanvasGradient */ + if (value instanceof CanvasGradient) { + value = core_defaults.global.defaultColor; + } + + return chartjsColor(value); + }; + + helpers$1.getHoverColor = function(colorValue) { + /* global CanvasPattern */ + return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ? + colorValue : + helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString(); + }; +}; + +function abstract() { + throw new Error( + 'This method is not implemented: either no adapter can ' + + 'be found or an incomplete integration was provided.' + ); +} + +/** + * Date adapter (current used by the time scale) + * @namespace Chart._adapters._date + * @memberof Chart._adapters + * @private + */ + +/** + * Currently supported unit string values. + * @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')} + * @memberof Chart._adapters._date + * @name Unit + */ + +/** + * @class + */ +function DateAdapter(options) { + this.options = options || {}; +} + +helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ { + /** + * Returns a map of time formats for the supported formatting units defined + * in Unit as well as 'datetime' representing a detailed date/time string. + * @returns {{string: string}} + */ + formats: abstract, + + /** + * Parses the given `value` and return the associated timestamp. + * @param {any} value - the value to parse (usually comes from the data) + * @param {string} [format] - the expected data format + * @returns {(number|null)} + * @function + */ + parse: abstract, + + /** + * Returns the formatted date in the specified `format` for a given `timestamp`. + * @param {number} timestamp - the timestamp to format + * @param {string} format - the date/time token + * @return {string} + * @function + */ + format: abstract, + + /** + * Adds the specified `amount` of `unit` to the given `timestamp`. + * @param {number} timestamp - the input timestamp + * @param {number} amount - the amount to add + * @param {Unit} unit - the unit as string + * @return {number} + * @function + */ + add: abstract, + + /** + * Returns the number of `unit` between the given timestamps. + * @param {number} max - the input timestamp (reference) + * @param {number} min - the timestamp to substract + * @param {Unit} unit - the unit as string + * @return {number} + * @function + */ + diff: abstract, + + /** + * Returns start of `unit` for the given `timestamp`. + * @param {number} timestamp - the input timestamp + * @param {Unit} unit - the unit as string + * @param {number} [weekday] - the ISO day of the week with 1 being Monday + * and 7 being Sunday (only needed if param *unit* is `isoWeek`). + * @function + */ + startOf: abstract, + + /** + * Returns end of `unit` for the given `timestamp`. + * @param {number} timestamp - the input timestamp + * @param {Unit} unit - the unit as string + * @function + */ + endOf: abstract, + + // DEPRECATIONS + + /** + * Provided for backward compatibility for scale.getValueForPixel(), + * this method should be overridden only by the moment adapter. + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ + _create: function(value) { + return value; + } +}); + +DateAdapter.override = function(members) { + helpers$1.extend(DateAdapter.prototype, members); +}; + +var _date = DateAdapter; + +var core_adapters = { + _date: _date +}; + +/** + * Namespace to hold static tick generation functions + * @namespace Chart.Ticks + */ +var core_ticks = { + /** + * Namespace to hold formatters for different types of ticks + * @namespace Chart.Ticks.formatters + */ + formatters: { + /** + * Formatter for value labels + * @method Chart.Ticks.formatters.values + * @param value the value to display + * @return {string|string[]} the label to display + */ + values: function(value) { + return helpers$1.isArray(value) ? value : '' + value; + }, + + /** + * Formatter for linear numeric ticks + * @method Chart.Ticks.formatters.linear + * @param tickValue {number} the value to be formatted + * @param index {number} the position of the tickValue parameter in the ticks array + * @param ticks {number[]} the list of ticks being converted + * @return {string} string representation of the tickValue parameter + */ + linear: function(tickValue, index, ticks) { + // If we have lots of ticks, don't use the ones + var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; + + // If we have a number like 2.5 as the delta, figure out how many decimal places we need + if (Math.abs(delta) > 1) { + if (tickValue !== Math.floor(tickValue)) { + // not an integer + delta = tickValue - Math.floor(tickValue); + } + } + + var logDelta = helpers$1.log10(Math.abs(delta)); + var tickString = ''; + + if (tickValue !== 0) { + var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); + if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation + var logTick = helpers$1.log10(Math.abs(tickValue)); + var numExponential = Math.floor(logTick) - Math.floor(logDelta); + numExponential = Math.max(Math.min(numExponential, 20), 0); + tickString = tickValue.toExponential(numExponential); + } else { + var numDecimal = -1 * Math.floor(logDelta); + numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places + tickString = tickValue.toFixed(numDecimal); + } + } else { + tickString = '0'; // never show decimal places for 0 + } + + return tickString; + }, + + logarithmic: function(tickValue, index, ticks) { + var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue)))); + + if (tickValue === 0) { + return '0'; + } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { + return tickValue.toExponential(); + } + return ''; + } + } +}; + +var isArray = helpers$1.isArray; +var isNullOrUndef = helpers$1.isNullOrUndef; +var valueOrDefault$a = helpers$1.valueOrDefault; +var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault; + +core_defaults._set('scale', { + display: true, + position: 'left', + offset: false, + + // grid line settings + gridLines: { + display: true, + color: 'rgba(0,0,0,0.1)', + lineWidth: 1, + drawBorder: true, + drawOnChartArea: true, + drawTicks: true, + tickMarkLength: 10, + zeroLineWidth: 1, + zeroLineColor: 'rgba(0,0,0,0.25)', + zeroLineBorderDash: [], + zeroLineBorderDashOffset: 0.0, + offsetGridLines: false, + borderDash: [], + borderDashOffset: 0.0 + }, + + // scale label + scaleLabel: { + // display property + display: false, + + // actual label + labelString: '', + + // top/bottom padding + padding: { + top: 4, + bottom: 4 + } + }, + + // label settings + ticks: { + beginAtZero: false, + minRotation: 0, + maxRotation: 50, + mirror: false, + padding: 0, + reverse: false, + display: true, + autoSkip: true, + autoSkipPadding: 0, + labelOffset: 0, + // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. + callback: core_ticks.formatters.values, + minor: {}, + major: {} + } +}); + +/** Returns a new array containing numItems from arr */ +function sample(arr, numItems) { + var result = []; + var increment = arr.length / numItems; + var i = 0; + var len = arr.length; + + for (; i < len; i += increment) { + result.push(arr[Math.floor(i)]); + } + return result; +} + +function getPixelForGridLine(scale, index, offsetGridLines) { + var length = scale.getTicks().length; + var validIndex = Math.min(index, length - 1); + var lineValue = scale.getPixelForTick(validIndex); + var start = scale._startPixel; + var end = scale._endPixel; + var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. + var offset; + + if (offsetGridLines) { + if (length === 1) { + offset = Math.max(lineValue - start, end - lineValue); + } else if (index === 0) { + offset = (scale.getPixelForTick(1) - lineValue) / 2; + } else { + offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; + } + lineValue += validIndex < index ? offset : -offset; + + // Return undefined if the pixel is out of the range + if (lineValue < start - epsilon || lineValue > end + epsilon) { + return; + } + } + return lineValue; +} + +function garbageCollect(caches, length) { + helpers$1.each(caches, function(cache) { + var gc = cache.gc; + var gcLen = gc.length / 2; + var i; + if (gcLen > length) { + for (i = 0; i < gcLen; ++i) { + delete cache.data[gc[i]]; + } + gc.splice(0, gcLen); + } + }); +} + +/** + * Returns {width, height, offset} objects for the first, last, widest, highest tick + * labels where offset indicates the anchor point offset from the top in pixels. + */ +function computeLabelSizes(ctx, tickFonts, ticks, caches) { + var length = ticks.length; + var widths = []; + var heights = []; + var offsets = []; + var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest; + + for (i = 0; i < length; ++i) { + label = ticks[i].label; + tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor; + ctx.font = fontString = tickFont.string; + cache = caches[fontString] = caches[fontString] || {data: {}, gc: []}; + lineHeight = tickFont.lineHeight; + width = height = 0; + // Undefined labels and arrays should not be measured + if (!isNullOrUndef(label) && !isArray(label)) { + width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label); + height = lineHeight; + } else if (isArray(label)) { + // if it is an array let's measure each element + for (j = 0, jlen = label.length; j < jlen; ++j) { + nestedLabel = label[j]; + // Undefined labels and arrays should not be measured + if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { + width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel); + height += lineHeight; + } + } + } + widths.push(width); + heights.push(height); + offsets.push(lineHeight / 2); + } + garbageCollect(caches, length); + + widest = widths.indexOf(Math.max.apply(null, widths)); + highest = heights.indexOf(Math.max.apply(null, heights)); + + function valueAt(idx) { + return { + width: widths[idx] || 0, + height: heights[idx] || 0, + offset: offsets[idx] || 0 + }; + } + + return { + first: valueAt(0), + last: valueAt(length - 1), + widest: valueAt(widest), + highest: valueAt(highest) + }; +} + +function getTickMarkLength(options) { + return options.drawTicks ? options.tickMarkLength : 0; +} + +function getScaleLabelHeight(options) { + var font, padding; + + if (!options.display) { + return 0; + } + + font = helpers$1.options._parseFont(options); + padding = helpers$1.options.toPadding(options.padding); + + return font.lineHeight + padding.height; +} + +function parseFontOptions(options, nestedOpts) { + return helpers$1.extend(helpers$1.options._parseFont({ + fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily), + fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize), + fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle), + lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight) + }), { + color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor]) + }); +} + +function parseTickFontOptions(options) { + var minor = parseFontOptions(options, options.minor); + var major = options.major.enabled ? parseFontOptions(options, options.major) : minor; + + return {minor: minor, major: major}; +} + +function nonSkipped(ticksToFilter) { + var filtered = []; + var item, index, len; + for (index = 0, len = ticksToFilter.length; index < len; ++index) { + item = ticksToFilter[index]; + if (typeof item._index !== 'undefined') { + filtered.push(item); + } + } + return filtered; +} + +function getEvenSpacing(arr) { + var len = arr.length; + var i, diff; + + if (len < 2) { + return false; + } + + for (diff = arr[0], i = 1; i < len; ++i) { + if (arr[i] - arr[i - 1] !== diff) { + return false; + } + } + return diff; +} + +function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) { + var evenMajorSpacing = getEvenSpacing(majorIndices); + var spacing = (ticks.length - 1) / ticksLimit; + var factors, factor, i, ilen; + + // If the major ticks are evenly spaced apart, place the minor ticks + // so that they divide the major ticks into even chunks + if (!evenMajorSpacing) { + return Math.max(spacing, 1); + } + + factors = helpers$1.math._factorize(evenMajorSpacing); + for (i = 0, ilen = factors.length - 1; i < ilen; i++) { + factor = factors[i]; + if (factor > spacing) { + return factor; + } + } + return Math.max(spacing, 1); +} + +function getMajorIndices(ticks) { + var result = []; + var i, ilen; + for (i = 0, ilen = ticks.length; i < ilen; i++) { + if (ticks[i].major) { + result.push(i); + } + } + return result; +} + +function skipMajors(ticks, majorIndices, spacing) { + var count = 0; + var next = majorIndices[0]; + var i, tick; + + spacing = Math.ceil(spacing); + for (i = 0; i < ticks.length; i++) { + tick = ticks[i]; + if (i === next) { + tick._index = i; + count++; + next = majorIndices[count * spacing]; + } else { + delete tick.label; + } + } +} + +function skip(ticks, spacing, majorStart, majorEnd) { + var start = valueOrDefault$a(majorStart, 0); + var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length); + var count = 0; + var length, i, tick, next; + + spacing = Math.ceil(spacing); + if (majorEnd) { + length = majorEnd - majorStart; + spacing = length / Math.floor(length / spacing); + } + + next = start; + + while (next < 0) { + count++; + next = Math.round(start + count * spacing); + } + + for (i = Math.max(start, 0); i < end; i++) { + tick = ticks[i]; + if (i === next) { + tick._index = i; + count++; + next = Math.round(start + count * spacing); + } else { + delete tick.label; + } + } +} + +var Scale = core_element.extend({ + + zeroLineIndex: 0, + + /** + * Get the padding needed for the scale + * @method getPadding + * @private + * @returns {Padding} the necessary padding + */ + getPadding: function() { + var me = this; + return { + left: me.paddingLeft || 0, + top: me.paddingTop || 0, + right: me.paddingRight || 0, + bottom: me.paddingBottom || 0 + }; + }, + + /** + * Returns the scale tick objects ({label, major}) + * @since 2.7 + */ + getTicks: function() { + return this._ticks; + }, + + /** + * @private + */ + _getLabels: function() { + var data = this.chart.data; + return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; + }, + + // These methods are ordered by lifecyle. Utilities then follow. + // Any function defined here is inherited by all scale types. + // Any function can be extended by the scale type + + /** + * Provided for backward compatibility, not available anymore + * @function Chart.Scale.mergeTicksOptions + * @deprecated since version 2.8.0 + * @todo remove at version 3 + */ + mergeTicksOptions: function() { + // noop + }, + + beforeUpdate: function() { + helpers$1.callback(this.options.beforeUpdate, [this]); + }, + + /** + * @param {number} maxWidth - the max width in pixels + * @param {number} maxHeight - the max height in pixels + * @param {object} margins - the space between the edge of the other scales and edge of the chart + * This space comes from two sources: + * - padding - space that's required to show the labels at the edges of the scale + * - thickness of scales or legends in another orientation + */ + update: function(maxWidth, maxHeight, margins) { + var me = this; + var tickOpts = me.options.ticks; + var sampleSize = tickOpts.sampleSize; + var i, ilen, labels, ticks, samplingEnabled; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = helpers$1.extend({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, margins); + + me._ticks = null; + me.ticks = null; + me._labelSizes = null; + me._maxLabelLines = 0; + me.longestLabelWidth = 0; + me.longestTextCache = me.longestTextCache || {}; + me._gridLineItems = null; + me._labelItems = null; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + + // Data min/max + me.beforeDataLimits(); + me.determineDataLimits(); + me.afterDataLimits(); + + // Ticks - `this.ticks` is now DEPRECATED! + // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member + // and must not be accessed directly from outside this class. `this.ticks` being + // around for long time and not marked as private, we can't change its structure + // without unexpected breaking changes. If you need to access the scale ticks, + // use scale.getTicks() instead. + + me.beforeBuildTicks(); + + // New implementations should return an array of objects but for BACKWARD COMPAT, + // we still support no return (`this.ticks` internally set by calling this method). + ticks = me.buildTicks() || []; + + // Allow modification of ticks in callback. + ticks = me.afterBuildTicks(ticks) || ticks; + + // Ensure ticks contains ticks in new tick format + if ((!ticks || !ticks.length) && me.ticks) { + ticks = []; + for (i = 0, ilen = me.ticks.length; i < ilen; ++i) { + ticks.push({ + value: me.ticks[i], + major: false + }); + } + } + + me._ticks = ticks; + + // Compute tick rotation and fit using a sampled subset of labels + // We generally don't need to compute the size of every single label for determining scale size + samplingEnabled = sampleSize < ticks.length; + labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks); + + // _configure is called twice, once here, once from core.controller.updateLayout. + // Here we haven't been positioned yet, but dimensions are correct. + // Variables set in _configure are needed for calculateTickRotation, and + // it's ok that coordinates are not correct there, only dimensions matter. + me._configure(); + + // Tick Rotation + me.beforeCalculateTickRotation(); + me.calculateTickRotation(); + me.afterCalculateTickRotation(); + + me.beforeFit(); + me.fit(); + me.afterFit(); + + // Auto-skip + me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks; + + if (samplingEnabled) { + // Generate labels using all non-skipped ticks + labels = me._convertTicksToLabels(me._ticksToDraw); + } + + me.ticks = labels; // BACKWARD COMPATIBILITY + + // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change! + + me.afterUpdate(); + + // TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused + // make maxWidth and maxHeight private + return me.minSize; + }, + + /** + * @private + */ + _configure: function() { + var me = this; + var reversePixels = me.options.ticks.reverse; + var startPixel, endPixel; + + if (me.isHorizontal()) { + startPixel = me.left; + endPixel = me.right; + } else { + startPixel = me.top; + endPixel = me.bottom; + // by default vertical scales are from bottom to top, so pixels are reversed + reversePixels = !reversePixels; + } + me._startPixel = startPixel; + me._endPixel = endPixel; + me._reversePixels = reversePixels; + me._length = endPixel - startPixel; + }, + + afterUpdate: function() { + helpers$1.callback(this.options.afterUpdate, [this]); + }, + + // + + beforeSetDimensions: function() { + helpers$1.callback(this.options.beforeSetDimensions, [this]); + }, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + }, + afterSetDimensions: function() { + helpers$1.callback(this.options.afterSetDimensions, [this]); + }, + + // Data limits + beforeDataLimits: function() { + helpers$1.callback(this.options.beforeDataLimits, [this]); + }, + determineDataLimits: helpers$1.noop, + afterDataLimits: function() { + helpers$1.callback(this.options.afterDataLimits, [this]); + }, + + // + beforeBuildTicks: function() { + helpers$1.callback(this.options.beforeBuildTicks, [this]); + }, + buildTicks: helpers$1.noop, + afterBuildTicks: function(ticks) { + var me = this; + // ticks is empty for old axis implementations here + if (isArray(ticks) && ticks.length) { + return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]); + } + // Support old implementations (that modified `this.ticks` directly in buildTicks) + me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks; + return ticks; + }, + + beforeTickToLabelConversion: function() { + helpers$1.callback(this.options.beforeTickToLabelConversion, [this]); + }, + convertTicksToLabels: function() { + var me = this; + // Convert ticks to strings + var tickOpts = me.options.ticks; + me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); + }, + afterTickToLabelConversion: function() { + helpers$1.callback(this.options.afterTickToLabelConversion, [this]); + }, + + // + + beforeCalculateTickRotation: function() { + helpers$1.callback(this.options.beforeCalculateTickRotation, [this]); + }, + calculateTickRotation: function() { + var me = this; + var options = me.options; + var tickOpts = options.ticks; + var numTicks = me.getTicks().length; + var minRotation = tickOpts.minRotation || 0; + var maxRotation = tickOpts.maxRotation; + var labelRotation = minRotation; + var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal; + + if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) { + me.labelRotation = minRotation; + return; + } + + labelSizes = me._getLabelSizes(); + maxLabelWidth = labelSizes.widest.width; + maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset; + + // Estimate the width of each grid based on the canvas width, the maximum + // label width and the number of tick intervals + maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth); + tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1); + + // Allow 3 pixels x2 padding either side for label readability + if (maxLabelWidth + 6 > tickWidth) { + tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); + maxHeight = me.maxHeight - getTickMarkLength(options.gridLines) + - tickOpts.padding - getScaleLabelHeight(options.scaleLabel); + maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); + labelRotation = helpers$1.toDegrees(Math.min( + Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)), + Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal) + )); + labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); + } + + me.labelRotation = labelRotation; + }, + afterCalculateTickRotation: function() { + helpers$1.callback(this.options.afterCalculateTickRotation, [this]); + }, + + // + + beforeFit: function() { + helpers$1.callback(this.options.beforeFit, [this]); + }, + fit: function() { + var me = this; + // Reset + var minSize = me.minSize = { + width: 0, + height: 0 + }; + + var chart = me.chart; + var opts = me.options; + var tickOpts = opts.ticks; + var scaleLabelOpts = opts.scaleLabel; + var gridLineOpts = opts.gridLines; + var display = me._isVisible(); + var isBottom = opts.position === 'bottom'; + var isHorizontal = me.isHorizontal(); + + // Width + if (isHorizontal) { + minSize.width = me.maxWidth; + } else if (display) { + minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); + } + + // height + if (!isHorizontal) { + minSize.height = me.maxHeight; // fill all the height + } else if (display) { + minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); + } + + // Don't bother fitting the ticks if we are not showing the labels + if (tickOpts.display && display) { + var tickFonts = parseTickFontOptions(tickOpts); + var labelSizes = me._getLabelSizes(); + var firstLabelSize = labelSizes.first; + var lastLabelSize = labelSizes.last; + var widestLabelSize = labelSizes.widest; + var highestLabelSize = labelSizes.highest; + var lineSpace = tickFonts.minor.lineHeight * 0.4; + var tickPadding = tickOpts.padding; + + if (isHorizontal) { + // A horizontal axis is more constrained by the height. + var isRotated = me.labelRotation !== 0; + var angleRadians = helpers$1.toRadians(me.labelRotation); + var cosRotation = Math.cos(angleRadians); + var sinRotation = Math.sin(angleRadians); + + var labelHeight = sinRotation * widestLabelSize.width + + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0)) + + (isRotated ? 0 : lineSpace); // padding + + minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); + + var offsetLeft = me.getPixelForTick(0) - me.left; + var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1); + var paddingLeft, paddingRight; + + // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned + // which means that the right padding is dominated by the font height + if (isRotated) { + paddingLeft = isBottom ? + cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : + sinRotation * (firstLabelSize.height - firstLabelSize.offset); + paddingRight = isBottom ? + sinRotation * (lastLabelSize.height - lastLabelSize.offset) : + cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; + } else { + paddingLeft = firstLabelSize.width / 2; + paddingRight = lastLabelSize.width / 2; + } + + // Adjust padding taking into account changes in offsets + // and add 3 px to move away from canvas edges + me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3; + me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3; + } else { + // A vertical axis is more constrained by the width. Labels are the + // dominant factor here, so get that length first and account for padding + var labelWidth = tickOpts.mirror ? 0 : + // use lineSpace for consistency with horizontal axis + // tickPadding is not implemented for horizontal + widestLabelSize.width + tickPadding + lineSpace; + + minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); + + me.paddingTop = firstLabelSize.height / 2; + me.paddingBottom = lastLabelSize.height / 2; + } + } + + me.handleMargins(); + + if (isHorizontal) { + me.width = me._length = chart.width - me.margins.left - me.margins.right; + me.height = minSize.height; + } else { + me.width = minSize.width; + me.height = me._length = chart.height - me.margins.top - me.margins.bottom; + } + }, + + /** + * Handle margins and padding interactions + * @private + */ + handleMargins: function() { + var me = this; + if (me.margins) { + me.margins.left = Math.max(me.paddingLeft, me.margins.left); + me.margins.top = Math.max(me.paddingTop, me.margins.top); + me.margins.right = Math.max(me.paddingRight, me.margins.right); + me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom); + } + }, + + afterFit: function() { + helpers$1.callback(this.options.afterFit, [this]); + }, + + // Shared Methods + isHorizontal: function() { + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + }, + isFullWidth: function() { + return this.options.fullWidth; + }, + + // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not + getRightValue: function(rawValue) { + // Null and undefined values first + if (isNullOrUndef(rawValue)) { + return NaN; + } + // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values + if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) { + return NaN; + } + + // If it is in fact an object, dive in one more level + if (rawValue) { + if (this.isHorizontal()) { + if (rawValue.x !== undefined) { + return this.getRightValue(rawValue.x); + } + } else if (rawValue.y !== undefined) { + return this.getRightValue(rawValue.y); + } + } + + // Value is good, return it + return rawValue; + }, + + _convertTicksToLabels: function(ticks) { + var me = this; + var labels, i, ilen; + + me.ticks = ticks.map(function(tick) { + return tick.value; + }); + + me.beforeTickToLabelConversion(); + + // New implementations should return the formatted tick labels but for BACKWARD + // COMPAT, we still support no return (`this.ticks` internally changed by calling + // this method and supposed to contain only string values). + labels = me.convertTicksToLabels(ticks) || me.ticks; + + me.afterTickToLabelConversion(); + + // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + ticks[i].label = labels[i]; + } + + return labels; + }, + + /** + * @private + */ + _getLabelSizes: function() { + var me = this; + var labelSizes = me._labelSizes; + + if (!labelSizes) { + me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache); + me.longestLabelWidth = labelSizes.widest.width; + } + + return labelSizes; + }, + + /** + * @private + */ + _parseValue: function(value) { + var start, end, min, max; + + if (isArray(value)) { + start = +this.getRightValue(value[0]); + end = +this.getRightValue(value[1]); + min = Math.min(start, end); + max = Math.max(start, end); + } else { + value = +this.getRightValue(value); + start = undefined; + end = value; + min = value; + max = value; + } + + return { + min: min, + max: max, + start: start, + end: end + }; + }, + + /** + * @private + */ + _getScaleLabel: function(rawValue) { + var v = this._parseValue(rawValue); + if (v.start !== undefined) { + return '[' + v.start + ', ' + v.end + ']'; + } + + return +this.getRightValue(rawValue); + }, + + /** + * Used to get the value to display in the tooltip for the data at the given index + * @param index + * @param datasetIndex + */ + getLabelForIndex: helpers$1.noop, + + /** + * Returns the location of the given data point. Value can either be an index or a numerical value + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param value + * @param index + * @param datasetIndex + */ + getPixelForValue: helpers$1.noop, + + /** + * Used to get the data value from a given pixel. This is the inverse of getPixelForValue + * The coordinate (0, 0) is at the upper-left corner of the canvas + * @param pixel + */ + getValueForPixel: helpers$1.noop, + + /** + * Returns the location of the tick at the given index + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForTick: function(index) { + var me = this; + var offset = me.options.offset; + var numTicks = me._ticks.length; + var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1); + + return index < 0 || index > numTicks - 1 + ? null + : me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0)); + }, + + /** + * Utility for getting the pixel location of a percentage of scale + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getPixelForDecimal: function(decimal) { + var me = this; + + if (me._reversePixels) { + decimal = 1 - decimal; + } + + return me._startPixel + decimal * me._length; + }, + + getDecimalForPixel: function(pixel) { + var decimal = (pixel - this._startPixel) / this._length; + return this._reversePixels ? 1 - decimal : decimal; + }, + + /** + * Returns the pixel for the minimum chart value + * The coordinate (0, 0) is at the upper-left corner of the canvas + */ + getBasePixel: function() { + return this.getPixelForValue(this.getBaseValue()); + }, + + getBaseValue: function() { + var me = this; + var min = me.min; + var max = me.max; + + return me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0; + }, + + /** + * Returns a subset of ticks to be plotted to avoid overlapping labels. + * @private + */ + _autoSkip: function(ticks) { + var me = this; + var tickOpts = me.options.ticks; + var axisLength = me._length; + var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1; + var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; + var numMajorIndices = majorIndices.length; + var first = majorIndices[0]; + var last = majorIndices[numMajorIndices - 1]; + var i, ilen, spacing, avgMajorSpacing; + + // If there are too many major ticks to display them all + if (numMajorIndices > ticksLimit) { + skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit); + return nonSkipped(ticks); + } + + spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit); + + if (numMajorIndices > 0) { + for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) { + skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]); + } + avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null; + skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); + skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); + return nonSkipped(ticks); + } + skip(ticks, spacing); + return nonSkipped(ticks); + }, + + /** + * @private + */ + _tickSize: function() { + var me = this; + var optionTicks = me.options.ticks; + + // Calculate space needed by label in axis direction. + var rot = helpers$1.toRadians(me.labelRotation); + var cos = Math.abs(Math.cos(rot)); + var sin = Math.abs(Math.sin(rot)); + + var labelSizes = me._getLabelSizes(); + var padding = optionTicks.autoSkipPadding || 0; + var w = labelSizes ? labelSizes.widest.width + padding : 0; + var h = labelSizes ? labelSizes.highest.height + padding : 0; + + // Calculate space needed for 1 tick in axis direction. + return me.isHorizontal() + ? h * cos > w * sin ? w / cos : h / sin + : h * sin < w * cos ? h / cos : w / sin; + }, + + /** + * @private + */ + _isVisible: function() { + var me = this; + var chart = me.chart; + var display = me.options.display; + var i, ilen, meta; + + if (display !== 'auto') { + return !!display; + } + + // When 'auto', the scale is visible if at least one associated dataset is visible. + for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + meta = chart.getDatasetMeta(i); + if (meta.xAxisID === me.id || meta.yAxisID === me.id) { + return true; + } + } + } + + return false; + }, + + /** + * @private + */ + _computeGridLineItems: function(chartArea) { + var me = this; + var chart = me.chart; + var options = me.options; + var gridLines = options.gridLines; + var position = options.position; + var offsetGridLines = gridLines.offsetGridLines; + var isHorizontal = me.isHorizontal(); + var ticks = me._ticksToDraw; + var ticksLength = ticks.length + (offsetGridLines ? 1 : 0); + + var tl = getTickMarkLength(gridLines); + var items = []; + var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; + var axisHalfWidth = axisWidth / 2; + var alignPixel = helpers$1._alignPixel; + var alignBorderValue = function(pixel) { + return alignPixel(chart, pixel, axisWidth); + }; + var borderValue, i, tick, lineValue, alignedLineValue; + var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset; + + if (position === 'top') { + borderValue = alignBorderValue(me.bottom); + ty1 = me.bottom - tl; + ty2 = borderValue - axisHalfWidth; + y1 = alignBorderValue(chartArea.top) + axisHalfWidth; + y2 = chartArea.bottom; + } else if (position === 'bottom') { + borderValue = alignBorderValue(me.top); + y1 = chartArea.top; + y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; + ty1 = borderValue + axisHalfWidth; + ty2 = me.top + tl; + } else if (position === 'left') { + borderValue = alignBorderValue(me.right); + tx1 = me.right - tl; + tx2 = borderValue - axisHalfWidth; + x1 = alignBorderValue(chartArea.left) + axisHalfWidth; + x2 = chartArea.right; + } else { + borderValue = alignBorderValue(me.left); + x1 = chartArea.left; + x2 = alignBorderValue(chartArea.right) - axisHalfWidth; + tx1 = borderValue + axisHalfWidth; + tx2 = me.left + tl; + } + + for (i = 0; i < ticksLength; ++i) { + tick = ticks[i] || {}; + + // autoskipper skipped this tick (#4635) + if (isNullOrUndef(tick.label) && i < ticks.length) { + continue; + } + + if (i === me.zeroLineIndex && options.offset === offsetGridLines) { + // Draw the first index specially + lineWidth = gridLines.zeroLineWidth; + lineColor = gridLines.zeroLineColor; + borderDash = gridLines.zeroLineBorderDash || []; + borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0; + } else { + lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1); + lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)'); + borderDash = gridLines.borderDash || []; + borderDashOffset = gridLines.borderDashOffset || 0.0; + } + + lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines); + + // Skip if the pixel is out of the range + if (lineValue === undefined) { + continue; + } + + alignedLineValue = alignPixel(chart, lineValue, lineWidth); + + if (isHorizontal) { + tx1 = tx2 = x1 = x2 = alignedLineValue; + } else { + ty1 = ty2 = y1 = y2 = alignedLineValue; + } + + items.push({ + tx1: tx1, + ty1: ty1, + tx2: tx2, + ty2: ty2, + x1: x1, + y1: y1, + x2: x2, + y2: y2, + width: lineWidth, + color: lineColor, + borderDash: borderDash, + borderDashOffset: borderDashOffset, + }); + } + + items.ticksLength = ticksLength; + items.borderValue = borderValue; + + return items; + }, + + /** + * @private + */ + _computeLabelItems: function() { + var me = this; + var options = me.options; + var optionTicks = options.ticks; + var position = options.position; + var isMirrored = optionTicks.mirror; + var isHorizontal = me.isHorizontal(); + var ticks = me._ticksToDraw; + var fonts = parseTickFontOptions(optionTicks); + var tickPadding = optionTicks.padding; + var tl = getTickMarkLength(options.gridLines); + var rotation = -helpers$1.toRadians(me.labelRotation); + var items = []; + var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; + + if (position === 'top') { + y = me.bottom - tl - tickPadding; + textAlign = !rotation ? 'center' : 'left'; + } else if (position === 'bottom') { + y = me.top + tl + tickPadding; + textAlign = !rotation ? 'center' : 'right'; + } else if (position === 'left') { + x = me.right - (isMirrored ? 0 : tl) - tickPadding; + textAlign = isMirrored ? 'left' : 'right'; + } else { + x = me.left + (isMirrored ? 0 : tl) + tickPadding; + textAlign = isMirrored ? 'right' : 'left'; + } + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + tick = ticks[i]; + label = tick.label; + + // autoskipper skipped this tick (#4635) + if (isNullOrUndef(label)) { + continue; + } + + pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset; + font = tick.major ? fonts.major : fonts.minor; + lineHeight = font.lineHeight; + lineCount = isArray(label) ? label.length : 1; + + if (isHorizontal) { + x = pixel; + textOffset = position === 'top' + ? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight + : (!rotation ? 0.5 : 0) * lineHeight; + } else { + y = pixel; + textOffset = (1 - lineCount) * lineHeight / 2; + } + + items.push({ + x: x, + y: y, + rotation: rotation, + label: label, + font: font, + textOffset: textOffset, + textAlign: textAlign + }); + } + + return items; + }, + + /** + * @private + */ + _drawGrid: function(chartArea) { + var me = this; + var gridLines = me.options.gridLines; + + if (!gridLines.display) { + return; + } + + var ctx = me.ctx; + var chart = me.chart; + var alignPixel = helpers$1._alignPixel; + var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; + var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea)); + var width, color, i, ilen, item; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + width = item.width; + color = item.color; + + if (width && color) { + ctx.save(); + ctx.lineWidth = width; + ctx.strokeStyle = color; + if (ctx.setLineDash) { + ctx.setLineDash(item.borderDash); + ctx.lineDashOffset = item.borderDashOffset; + } + + ctx.beginPath(); + + if (gridLines.drawTicks) { + ctx.moveTo(item.tx1, item.ty1); + ctx.lineTo(item.tx2, item.ty2); + } + + if (gridLines.drawOnChartArea) { + ctx.moveTo(item.x1, item.y1); + ctx.lineTo(item.x2, item.y2); + } + + ctx.stroke(); + ctx.restore(); + } + } + + if (axisWidth) { + // Draw the line at the edge of the axis + var firstLineWidth = axisWidth; + var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1); + var borderValue = items.borderValue; + var x1, x2, y1, y2; + + if (me.isHorizontal()) { + x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2; + x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2; + y1 = y2 = borderValue; + } else { + y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2; + y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2; + x1 = x2 = borderValue; + } + + ctx.lineWidth = axisWidth; + ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0); + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + } + }, + + /** + * @private + */ + _drawLabels: function() { + var me = this; + var optionTicks = me.options.ticks; + + if (!optionTicks.display) { + return; + } + + var ctx = me.ctx; + var items = me._labelItems || (me._labelItems = me._computeLabelItems()); + var i, j, ilen, jlen, item, tickFont, label, y; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + tickFont = item.font; + + // Make sure we draw text in the correct color and font + ctx.save(); + ctx.translate(item.x, item.y); + ctx.rotate(item.rotation); + ctx.font = tickFont.string; + ctx.fillStyle = tickFont.color; + ctx.textBaseline = 'middle'; + ctx.textAlign = item.textAlign; + + label = item.label; + y = item.textOffset; + if (isArray(label)) { + for (j = 0, jlen = label.length; j < jlen; ++j) { + // We just make sure the multiline element is a string here.. + ctx.fillText('' + label[j], 0, y); + y += tickFont.lineHeight; + } + } else { + ctx.fillText(label, 0, y); + } + ctx.restore(); + } + }, + + /** + * @private + */ + _drawTitle: function() { + var me = this; + var ctx = me.ctx; + var options = me.options; + var scaleLabel = options.scaleLabel; + + if (!scaleLabel.display) { + return; + } + + var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor); + var scaleLabelFont = helpers$1.options._parseFont(scaleLabel); + var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding); + var halfLineHeight = scaleLabelFont.lineHeight / 2; + var position = options.position; + var rotation = 0; + var scaleLabelX, scaleLabelY; + + if (me.isHorizontal()) { + scaleLabelX = me.left + me.width / 2; // midpoint of the width + scaleLabelY = position === 'bottom' + ? me.bottom - halfLineHeight - scaleLabelPadding.bottom + : me.top + halfLineHeight + scaleLabelPadding.top; + } else { + var isLeft = position === 'left'; + scaleLabelX = isLeft + ? me.left + halfLineHeight + scaleLabelPadding.top + : me.right - halfLineHeight - scaleLabelPadding.top; + scaleLabelY = me.top + me.height / 2; + rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; + } + + ctx.save(); + ctx.translate(scaleLabelX, scaleLabelY); + ctx.rotate(rotation); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillStyle = scaleLabelFontColor; // render in correct colour + ctx.font = scaleLabelFont.string; + ctx.fillText(scaleLabel.labelString, 0, 0); + ctx.restore(); + }, + + draw: function(chartArea) { + var me = this; + + if (!me._isVisible()) { + return; + } + + me._drawGrid(chartArea); + me._drawTitle(); + me._drawLabels(); + }, + + /** + * @private + */ + _layers: function() { + var me = this; + var opts = me.options; + var tz = opts.ticks && opts.ticks.z || 0; + var gz = opts.gridLines && opts.gridLines.z || 0; + + if (!me._isVisible() || tz === gz || me.draw !== me._draw) { + // backward compatibility: draw has been overridden by custom scale + return [{ + z: tz, + draw: function() { + me.draw.apply(me, arguments); + } + }]; + } + + return [{ + z: gz, + draw: function() { + me._drawGrid.apply(me, arguments); + me._drawTitle.apply(me, arguments); + } + }, { + z: tz, + draw: function() { + me._drawLabels.apply(me, arguments); + } + }]; + }, + + /** + * @private + */ + _getMatchingVisibleMetas: function(type) { + var me = this; + var isHorizontal = me.isHorizontal(); + return me.chart._getSortedVisibleDatasetMetas() + .filter(function(meta) { + return (!type || meta.type === type) + && (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id); + }); + } +}); + +Scale.prototype._draw = Scale.prototype.draw; + +var core_scale = Scale; + +var isNullOrUndef$1 = helpers$1.isNullOrUndef; + +var defaultConfig = { + position: 'bottom' +}; + +var scale_category = core_scale.extend({ + determineDataLimits: function() { + var me = this; + var labels = me._getLabels(); + var ticksOpts = me.options.ticks; + var min = ticksOpts.min; + var max = ticksOpts.max; + var minIndex = 0; + var maxIndex = labels.length - 1; + var findIndex; + + if (min !== undefined) { + // user specified min value + findIndex = labels.indexOf(min); + if (findIndex >= 0) { + minIndex = findIndex; + } + } + + if (max !== undefined) { + // user specified max value + findIndex = labels.indexOf(max); + if (findIndex >= 0) { + maxIndex = findIndex; + } + } + + me.minIndex = minIndex; + me.maxIndex = maxIndex; + me.min = labels[minIndex]; + me.max = labels[maxIndex]; + }, + + buildTicks: function() { + var me = this; + var labels = me._getLabels(); + var minIndex = me.minIndex; + var maxIndex = me.maxIndex; + + // If we are viewing some subset of labels, slice the original array + me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1); + }, + + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var chart = me.chart; + + if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) { + return me.getRightValue(chart.data.datasets[datasetIndex].data[index]); + } + + return me._getLabels()[index]; + }, + + _configure: function() { + var me = this; + var offset = me.options.offset; + var ticks = me.ticks; + + core_scale.prototype._configure.call(me); + + if (!me.isHorizontal()) { + // For backward compatibility, vertical category scale reverse is inverted. + me._reversePixels = !me._reversePixels; + } + + if (!ticks) { + return; + } + + me._startValue = me.minIndex - (offset ? 0.5 : 0); + me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1); + }, + + // Used to get data value locations. Value can either be an index or a numerical value + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var valueCategory, labels, idx; + + if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) { + value = me.chart.data.datasets[datasetIndex].data[index]; + } + + // If value is a data object, then index is the index in the data array, + // not the index of the scale. We need to change that. + if (!isNullOrUndef$1(value)) { + valueCategory = me.isHorizontal() ? value.x : value.y; + } + if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { + labels = me._getLabels(); + value = helpers$1.valueOrDefault(valueCategory, value); + idx = labels.indexOf(value); + index = idx !== -1 ? idx : index; + if (isNaN(index)) { + index = value; + } + } + return me.getPixelForDecimal((index - me._startValue) / me._valueRange); + }, + + getPixelForTick: function(index) { + var ticks = this.ticks; + return index < 0 || index > ticks.length - 1 + ? null + : this.getPixelForValue(ticks[index], index + this.minIndex); + }, + + getValueForPixel: function(pixel) { + var me = this; + var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange); + return Math.min(Math.max(value, 0), me.ticks.length - 1); + }, + + getBasePixel: function() { + return this.bottom; + } +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults = defaultConfig; +scale_category._defaults = _defaults; + +var noop = helpers$1.noop; +var isNullOrUndef$2 = helpers$1.isNullOrUndef; + +/** + * Generate a set of linear ticks + * @param generationOptions the options used to generate the ticks + * @param dataRange the range of the data + * @returns {number[]} array of tick values + */ +function generateTicks(generationOptions, dataRange) { + var ticks = []; + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + var MIN_SPACING = 1e-14; + var stepSize = generationOptions.stepSize; + var unit = stepSize || 1; + var maxNumSpaces = generationOptions.maxTicks - 1; + var min = generationOptions.min; + var max = generationOptions.max; + var precision = generationOptions.precision; + var rmin = dataRange.min; + var rmax = dataRange.max; + var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; + var factor, niceMin, niceMax, numSpaces; + + // Beyond MIN_SPACING floating point numbers being to lose precision + // such that we can't do the math necessary to generate ticks + if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) { + return [rmin, rmax]; + } + + numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); + if (numSpaces > maxNumSpaces) { + // If the calculated num of spaces exceeds maxNumSpaces, recalculate it + spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; + } + + if (stepSize || isNullOrUndef$2(precision)) { + // If a precision is not specified, calculate factor based on spacing + factor = Math.pow(10, helpers$1._decimalPlaces(spacing)); + } else { + // If the user specified a precision, round to that number of decimal places + factor = Math.pow(10, precision); + spacing = Math.ceil(spacing * factor) / factor; + } + + niceMin = Math.floor(rmin / spacing) * spacing; + niceMax = Math.ceil(rmax / spacing) * spacing; + + // If min, max and stepSize is set and they make an evenly spaced scale use it. + if (stepSize) { + // If very close to our whole number, use it. + if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) { + niceMin = min; + } + if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) { + niceMax = max; + } + } + + numSpaces = (niceMax - niceMin) / spacing; + // If very close to our rounded value, use it. + if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { + numSpaces = Math.round(numSpaces); + } else { + numSpaces = Math.ceil(numSpaces); + } + + niceMin = Math.round(niceMin * factor) / factor; + niceMax = Math.round(niceMax * factor) / factor; + ticks.push(isNullOrUndef$2(min) ? niceMin : min); + for (var j = 1; j < numSpaces; ++j) { + ticks.push(Math.round((niceMin + j * spacing) * factor) / factor); + } + ticks.push(isNullOrUndef$2(max) ? niceMax : max); + + return ticks; +} + +var scale_linearbase = core_scale.extend({ + getRightValue: function(value) { + if (typeof value === 'string') { + return +value; + } + return core_scale.prototype.getRightValue.call(this, value); + }, + + handleTickRangeOptions: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (tickOpts.beginAtZero) { + var minSign = helpers$1.sign(me.min); + var maxSign = helpers$1.sign(me.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + me.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the bottom down to 0 + me.min = 0; + } + } + + var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; + var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; + + if (tickOpts.min !== undefined) { + me.min = tickOpts.min; + } else if (tickOpts.suggestedMin !== undefined) { + if (me.min === null) { + me.min = tickOpts.suggestedMin; + } else { + me.min = Math.min(me.min, tickOpts.suggestedMin); + } + } + + if (tickOpts.max !== undefined) { + me.max = tickOpts.max; + } else if (tickOpts.suggestedMax !== undefined) { + if (me.max === null) { + me.max = tickOpts.suggestedMax; + } else { + me.max = Math.max(me.max, tickOpts.suggestedMax); + } + } + + if (setMin !== setMax) { + // We set the min or the max but not both. + // So ensure that our range is good + // Inverted or 0 length range can happen when + // ticks.min is set, and no datasets are visible + if (me.min >= me.max) { + if (setMin) { + me.max = me.min + 1; + } else { + me.min = me.max - 1; + } + } + } + + if (me.min === me.max) { + me.max++; + + if (!tickOpts.beginAtZero) { + me.min--; + } + } + }, + + getTickLimit: function() { + var me = this; + var tickOpts = me.options.ticks; + var stepSize = tickOpts.stepSize; + var maxTicksLimit = tickOpts.maxTicksLimit; + var maxTicks; + + if (stepSize) { + maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; + } else { + maxTicks = me._computeTickLimit(); + maxTicksLimit = maxTicksLimit || 11; + } + + if (maxTicksLimit) { + maxTicks = Math.min(maxTicksLimit, maxTicks); + } + + return maxTicks; + }, + + _computeTickLimit: function() { + return Number.POSITIVE_INFINITY; + }, + + handleDirectionalChanges: noop, + + buildTicks: function() { + var me = this; + var opts = me.options; + var tickOpts = opts.ticks; + + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph. Make sure we always have at least 2 ticks + var maxTicks = me.getTickLimit(); + maxTicks = Math.max(2, maxTicks); + + var numericGeneratorOptions = { + maxTicks: maxTicks, + min: tickOpts.min, + max: tickOpts.max, + precision: tickOpts.precision, + stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) + }; + var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); + + me.handleDirectionalChanges(); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers$1.max(ticks); + me.min = helpers$1.min(ticks); + + if (tickOpts.reverse) { + ticks.reverse(); + + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + }, + + convertTicksToLabels: function() { + var me = this; + me.ticksAsNumbers = me.ticks.slice(); + me.zeroLineIndex = me.ticks.indexOf(0); + + core_scale.prototype.convertTicksToLabels.call(me); + }, + + _configure: function() { + var me = this; + var ticks = me.getTicks(); + var start = me.min; + var end = me.max; + var offset; + + core_scale.prototype._configure.call(me); + + if (me.options.offset && ticks.length) { + offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; + start -= offset; + end += offset; + } + me._startValue = start; + me._endValue = end; + me._valueRange = end - start; + } +}); + +var defaultConfig$1 = { + position: 'left', + ticks: { + callback: core_ticks.formatters.linear + } +}; + +var DEFAULT_MIN = 0; +var DEFAULT_MAX = 1; + +function getOrCreateStack(stacks, stacked, meta) { + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + stacked === undefined && meta.stack === undefined ? meta.index : '', + meta.stack + ].join('.'); + + if (stacks[key] === undefined) { + stacks[key] = { + pos: [], + neg: [] + }; + } + + return stacks[key]; +} + +function stackData(scale, stacks, meta, data) { + var opts = scale.options; + var stacked = opts.stacked; + var stack = getOrCreateStack(stacks, stacked, meta); + var pos = stack.pos; + var neg = stack.neg; + var ilen = data.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = scale._parseValue(data[i]); + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { + continue; + } + + pos[i] = pos[i] || 0; + neg[i] = neg[i] || 0; + + if (opts.relativePoints) { + pos[i] = 100; + } else if (value.min < 0 || value.max < 0) { + neg[i] += value.min; + } else { + pos[i] += value.max; + } + } +} + +function updateMinMax(scale, meta, data) { + var ilen = data.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = scale._parseValue(data[i]); + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { + continue; + } + + scale.min = Math.min(scale.min, value.min); + scale.max = Math.max(scale.max, value.max); + } +} + +var scale_linear = scale_linearbase.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var datasets = chart.data.datasets; + var metasets = me._getMatchingVisibleMetas(); + var hasStacks = opts.stacked; + var stacks = {}; + var ilen = metasets.length; + var i, meta, data, values; + + me.min = Number.POSITIVE_INFINITY; + me.max = Number.NEGATIVE_INFINITY; + + if (hasStacks === undefined) { + for (i = 0; !hasStacks && i < ilen; ++i) { + meta = metasets[i]; + hasStacks = meta.stack !== undefined; + } + } + + for (i = 0; i < ilen; ++i) { + meta = metasets[i]; + data = datasets[meta.index].data; + if (hasStacks) { + stackData(me, stacks, meta, data); + } else { + updateMinMax(me, meta, data); + } + } + + helpers$1.each(stacks, function(stackValues) { + values = stackValues.pos.concat(stackValues.neg); + me.min = Math.min(me.min, helpers$1.min(values)); + me.max = Math.max(me.max, helpers$1.max(values)); + }); + + me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; + me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, + + // Returns the maximum number of ticks based on the scale dimension + _computeTickLimit: function() { + var me = this; + var tickFont; + + if (me.isHorizontal()) { + return Math.ceil(me.width / 40); + } + tickFont = helpers$1.options._parseFont(me.options.ticks); + return Math.ceil(me.height / tickFont.lineHeight); + }, + + // Called after the ticks are built. We need + handleDirectionalChanges: function() { + if (!this.isHorizontal()) { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + }, + + getLabelForIndex: function(index, datasetIndex) { + return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); + }, + + // Utils + getPixelForValue: function(value) { + var me = this; + return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange); + }, + + getValueForPixel: function(pixel) { + return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; + }, + + getPixelForTick: function(index) { + var ticks = this.ticksAsNumbers; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index]); + } +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults$1 = defaultConfig$1; +scale_linear._defaults = _defaults$1; + +var valueOrDefault$b = helpers$1.valueOrDefault; +var log10 = helpers$1.math.log10; + +/** + * Generate a set of logarithmic ticks + * @param generationOptions the options used to generate the ticks + * @param dataRange the range of the data + * @returns {number[]} array of tick values + */ +function generateTicks$1(generationOptions, dataRange) { + var ticks = []; + + var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); + + var endExp = Math.floor(log10(dataRange.max)); + var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); + var exp, significand; + + if (tickVal === 0) { + exp = Math.floor(log10(dataRange.minNotZero)); + significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); + + ticks.push(tickVal); + tickVal = significand * Math.pow(10, exp); + } else { + exp = Math.floor(log10(tickVal)); + significand = Math.floor(tickVal / Math.pow(10, exp)); + } + var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; + + do { + ticks.push(tickVal); + + ++significand; + if (significand === 10) { + significand = 1; + ++exp; + precision = exp >= 0 ? 1 : precision; + } + + tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; + } while (exp < endExp || (exp === endExp && significand < endSignificand)); + + var lastTick = valueOrDefault$b(generationOptions.max, tickVal); + ticks.push(lastTick); + + return ticks; +} + +var defaultConfig$2 = { + position: 'left', + + // label settings + ticks: { + callback: core_ticks.formatters.logarithmic + } +}; + +// TODO(v3): change this to positiveOrDefault +function nonNegativeOrDefault(value, defaultValue) { + return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue; +} + +var scale_logarithmic = core_scale.extend({ + determineDataLimits: function() { + var me = this; + var opts = me.options; + var chart = me.chart; + var datasets = chart.data.datasets; + var isHorizontal = me.isHorizontal(); + function IDMatches(meta) { + return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; + } + var datasetIndex, meta, value, data, i, ilen; + + // Calculate Range + me.min = Number.POSITIVE_INFINITY; + me.max = Number.NEGATIVE_INFINITY; + me.minNotZero = Number.POSITIVE_INFINITY; + + var hasStacks = opts.stacked; + if (hasStacks === undefined) { + for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { + meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && + meta.stack !== undefined) { + hasStacks = true; + break; + } + } + } + + if (opts.stacked || hasStacks) { + var valuesPerStack = {}; + + for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { + meta = chart.getDatasetMeta(datasetIndex); + var key = [ + meta.type, + // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined + ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), + meta.stack + ].join('.'); + + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + if (valuesPerStack[key] === undefined) { + valuesPerStack[key] = []; + } + + data = datasets[datasetIndex].data; + for (i = 0, ilen = data.length; i < ilen; i++) { + var values = valuesPerStack[key]; + value = me._parseValue(data[i]); + // invalid, hidden and negative values are ignored + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { + continue; + } + values[i] = values[i] || 0; + values[i] += value.max; + } + } + } + + helpers$1.each(valuesPerStack, function(valuesForType) { + if (valuesForType.length > 0) { + var minVal = helpers$1.min(valuesForType); + var maxVal = helpers$1.max(valuesForType); + me.min = Math.min(me.min, minVal); + me.max = Math.max(me.max, maxVal); + } + }); + + } else { + for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { + meta = chart.getDatasetMeta(datasetIndex); + if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { + data = datasets[datasetIndex].data; + for (i = 0, ilen = data.length; i < ilen; i++) { + value = me._parseValue(data[i]); + // invalid, hidden and negative values are ignored + if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { + continue; + } + + me.min = Math.min(value.min, me.min); + me.max = Math.max(value.max, me.max); + + if (value.min !== 0) { + me.minNotZero = Math.min(value.min, me.minNotZero); + } + } + } + } + } + + me.min = helpers$1.isFinite(me.min) ? me.min : null; + me.max = helpers$1.isFinite(me.max) ? me.max : null; + me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null; + + // Common base implementation to handle ticks.min, ticks.max + this.handleTickRangeOptions(); + }, + + handleTickRangeOptions: function() { + var me = this; + var tickOpts = me.options.ticks; + var DEFAULT_MIN = 1; + var DEFAULT_MAX = 10; + + me.min = nonNegativeOrDefault(tickOpts.min, me.min); + me.max = nonNegativeOrDefault(tickOpts.max, me.max); + + if (me.min === me.max) { + if (me.min !== 0 && me.min !== null) { + me.min = Math.pow(10, Math.floor(log10(me.min)) - 1); + me.max = Math.pow(10, Math.floor(log10(me.max)) + 1); + } else { + me.min = DEFAULT_MIN; + me.max = DEFAULT_MAX; + } + } + if (me.min === null) { + me.min = Math.pow(10, Math.floor(log10(me.max)) - 1); + } + if (me.max === null) { + me.max = me.min !== 0 + ? Math.pow(10, Math.floor(log10(me.min)) + 1) + : DEFAULT_MAX; + } + if (me.minNotZero === null) { + if (me.min > 0) { + me.minNotZero = me.min; + } else if (me.max < 1) { + me.minNotZero = Math.pow(10, Math.floor(log10(me.max))); + } else { + me.minNotZero = DEFAULT_MIN; + } + } + }, + + buildTicks: function() { + var me = this; + var tickOpts = me.options.ticks; + var reverse = !me.isHorizontal(); + + var generationOptions = { + min: nonNegativeOrDefault(tickOpts.min), + max: nonNegativeOrDefault(tickOpts.max) + }; + var ticks = me.ticks = generateTicks$1(generationOptions, me); + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + me.max = helpers$1.max(ticks); + me.min = helpers$1.min(ticks); + + if (tickOpts.reverse) { + reverse = !reverse; + me.start = me.max; + me.end = me.min; + } else { + me.start = me.min; + me.end = me.max; + } + if (reverse) { + ticks.reverse(); + } + }, + + convertTicksToLabels: function() { + this.tickValues = this.ticks.slice(); + + core_scale.prototype.convertTicksToLabels.call(this); + }, + + // Get the correct tooltip label + getLabelForIndex: function(index, datasetIndex) { + return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); + }, + + getPixelForTick: function(index) { + var ticks = this.tickValues; + if (index < 0 || index > ticks.length - 1) { + return null; + } + return this.getPixelForValue(ticks[index]); + }, + + /** + * Returns the value of the first tick. + * @param {number} value - The minimum not zero value. + * @return {number} The first tick value. + * @private + */ + _getFirstTickValue: function(value) { + var exp = Math.floor(log10(value)); + var significand = Math.floor(value / Math.pow(10, exp)); + + return significand * Math.pow(10, exp); + }, + + _configure: function() { + var me = this; + var start = me.min; + var offset = 0; + + core_scale.prototype._configure.call(me); + + if (start === 0) { + start = me._getFirstTickValue(me.minNotZero); + offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length; + } + + me._startValue = log10(start); + me._valueOffset = offset; + me._valueRange = (log10(me.max) - log10(start)) / (1 - offset); + }, + + getPixelForValue: function(value) { + var me = this; + var decimal = 0; + + value = +me.getRightValue(value); + + if (value > me.min && value > 0) { + decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset; + } + return me.getPixelForDecimal(decimal); + }, + + getValueForPixel: function(pixel) { + var me = this; + var decimal = me.getDecimalForPixel(pixel); + return decimal === 0 && me.min === 0 + ? 0 + : Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange); + } +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults$2 = defaultConfig$2; +scale_logarithmic._defaults = _defaults$2; + +var valueOrDefault$c = helpers$1.valueOrDefault; +var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault; +var resolve$4 = helpers$1.options.resolve; + +var defaultConfig$3 = { + display: true, + + // Boolean - Whether to animate scaling the chart from the centre + animate: true, + position: 'chartArea', + + angleLines: { + display: true, + color: 'rgba(0,0,0,0.1)', + lineWidth: 1, + borderDash: [], + borderDashOffset: 0.0 + }, + + gridLines: { + circular: false + }, + + // label settings + ticks: { + // Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + // String - The colour of the label backdrop + backdropColor: 'rgba(255,255,255,0.75)', + + // Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + // Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + + callback: core_ticks.formatters.linear + }, + + pointLabels: { + // Boolean - if true, show point labels + display: true, + + // Number - Point label font size in pixels + fontSize: 10, + + // Function - Used to convert point labels + callback: function(label) { + return label; + } + } +}; + +function getTickBackdropHeight(opts) { + var tickOpts = opts.ticks; + + if (tickOpts.display && opts.display) { + return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; + } + return 0; +} + +function measureLabelSize(ctx, lineHeight, label) { + if (helpers$1.isArray(label)) { + return { + w: helpers$1.longestText(ctx, ctx.font, label), + h: label.length * lineHeight + }; + } + + return { + w: ctx.measureText(label).width, + h: lineHeight + }; +} + +function determineLimits(angle, pos, size, min, max) { + if (angle === min || angle === max) { + return { + start: pos - (size / 2), + end: pos + (size / 2) + }; + } else if (angle < min || angle > max) { + return { + start: pos - size, + end: pos + }; + } + + return { + start: pos, + end: pos + size + }; +} + +/** + * Helper function to fit a radial linear scale with point labels + */ +function fitWithPointLabels(scale) { + + // Right, this is really confusing and there is a lot of maths going on here + // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 + // + // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif + // + // Solution: + // + // We assume the radius of the polygon is half the size of the canvas at first + // at each index we check if the text overlaps. + // + // Where it does, we store that angle and that index. + // + // After finding the largest index and angle we calculate how much we need to remove + // from the shape radius to move the point inwards by that x. + // + // We average the left and right distances to get the maximum shape radius that can fit in the box + // along with labels. + // + // Once we have that, we can find the centre point for the chart, by taking the x text protrusion + // on each side, removing that from the size, halving it and adding the left x protrusion width. + // + // This will mean we have a shape fitted to the canvas, as large as it can be with the labels + // and position it in the most space efficient manner + // + // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif + + var plFont = helpers$1.options._parseFont(scale.options.pointLabels); + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var furthestLimits = { + l: 0, + r: scale.width, + t: 0, + b: scale.height - scale.paddingTop + }; + var furthestAngles = {}; + var i, textSize, pointPosition; + + scale.ctx.font = plFont.string; + scale._pointLabelSizes = []; + + var valueCount = scale.chart.data.labels.length; + for (i = 0; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); + textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]); + scale._pointLabelSizes[i] = textSize; + + // Add quarter circle to make degree 0 mean top of circle + var angleRadians = scale.getIndexAngle(i); + var angle = helpers$1.toDegrees(angleRadians) % 360; + var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); + var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); + + if (hLimits.start < furthestLimits.l) { + furthestLimits.l = hLimits.start; + furthestAngles.l = angleRadians; + } + + if (hLimits.end > furthestLimits.r) { + furthestLimits.r = hLimits.end; + furthestAngles.r = angleRadians; + } + + if (vLimits.start < furthestLimits.t) { + furthestLimits.t = vLimits.start; + furthestAngles.t = angleRadians; + } + + if (vLimits.end > furthestLimits.b) { + furthestLimits.b = vLimits.end; + furthestAngles.b = angleRadians; + } + } + + scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); +} + +function getTextAlignForAngle(angle) { + if (angle === 0 || angle === 180) { + return 'center'; + } else if (angle < 180) { + return 'left'; + } + + return 'right'; +} + +function fillText(ctx, text, position, lineHeight) { + var y = position.y + lineHeight / 2; + var i, ilen; + + if (helpers$1.isArray(text)) { + for (i = 0, ilen = text.length; i < ilen; ++i) { + ctx.fillText(text[i], position.x, y); + y += lineHeight; + } + } else { + ctx.fillText(text, position.x, y); + } +} + +function adjustPointPositionForLabelHeight(angle, textSize, position) { + if (angle === 90 || angle === 270) { + position.y -= (textSize.h / 2); + } else if (angle > 270 || angle < 90) { + position.y -= textSize.h; + } +} + +function drawPointLabels(scale) { + var ctx = scale.ctx; + var opts = scale.options; + var pointLabelOpts = opts.pointLabels; + var tickBackdropHeight = getTickBackdropHeight(opts); + var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); + var plFont = helpers$1.options._parseFont(pointLabelOpts); + + ctx.save(); + + ctx.font = plFont.string; + ctx.textBaseline = 'middle'; + + for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) { + // Extra pixels out for some label spacing + var extra = (i === 0 ? tickBackdropHeight / 2 : 0); + var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); + + // Keep this in loop since we may support array properties here + var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor); + ctx.fillStyle = pointLabelFontColor; + + var angleRadians = scale.getIndexAngle(i); + var angle = helpers$1.toDegrees(angleRadians); + ctx.textAlign = getTextAlignForAngle(angle); + adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); + fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight); + } + ctx.restore(); +} + +function drawRadiusLine(scale, gridLineOpts, radius, index) { + var ctx = scale.ctx; + var circular = gridLineOpts.circular; + var valueCount = scale.chart.data.labels.length; + var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1); + var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1); + var pointPosition; + + if ((!circular && !valueCount) || !lineColor || !lineWidth) { + return; + } + + ctx.save(); + ctx.strokeStyle = lineColor; + ctx.lineWidth = lineWidth; + if (ctx.setLineDash) { + ctx.setLineDash(gridLineOpts.borderDash || []); + ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; + } + + ctx.beginPath(); + if (circular) { + // Draw circular arcs between the points + ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); + } else { + // Draw straight lines connecting each index + pointPosition = scale.getPointPosition(0, radius); + ctx.moveTo(pointPosition.x, pointPosition.y); + + for (var i = 1; i < valueCount; i++) { + pointPosition = scale.getPointPosition(i, radius); + ctx.lineTo(pointPosition.x, pointPosition.y); + } + } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); +} + +function numberOrZero(param) { + return helpers$1.isNumber(param) ? param : 0; +} + +var scale_radialLinear = scale_linearbase.extend({ + setDimensions: function() { + var me = this; + + // Set the unconstrained dimension before label rotation + me.width = me.maxWidth; + me.height = me.maxHeight; + me.paddingTop = getTickBackdropHeight(me.options) / 2; + me.xCenter = Math.floor(me.width / 2); + me.yCenter = Math.floor((me.height - me.paddingTop) / 2); + me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; + }, + + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var min = Number.POSITIVE_INFINITY; + var max = Number.NEGATIVE_INFINITY; + + helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) { + if (chart.isDatasetVisible(datasetIndex)) { + var meta = chart.getDatasetMeta(datasetIndex); + + helpers$1.each(dataset.data, function(rawValue, index) { + var value = +me.getRightValue(rawValue); + if (isNaN(value) || meta.data[index].hidden) { + return; + } + + min = Math.min(value, min); + max = Math.max(value, max); + }); + } + }); + + me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); + me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); + + // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero + me.handleTickRangeOptions(); + }, + + // Returns the maximum number of ticks based on the scale dimension + _computeTickLimit: function() { + return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); + }, + + convertTicksToLabels: function() { + var me = this; + + scale_linearbase.prototype.convertTicksToLabels.call(me); + + // Point labels + me.pointLabels = me.chart.data.labels.map(function() { + var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me); + return label || label === 0 ? label : ''; + }); + }, + + getLabelForIndex: function(index, datasetIndex) { + return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); + }, + + fit: function() { + var me = this; + var opts = me.options; + + if (opts.display && opts.pointLabels.display) { + fitWithPointLabels(me); + } else { + me.setCenterPoint(0, 0, 0, 0); + } + }, + + /** + * Set radius reductions and determine new radius and center point + * @private + */ + setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { + var me = this; + var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); + var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); + var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); + var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); + + radiusReductionLeft = numberOrZero(radiusReductionLeft); + radiusReductionRight = numberOrZero(radiusReductionRight); + radiusReductionTop = numberOrZero(radiusReductionTop); + radiusReductionBottom = numberOrZero(radiusReductionBottom); + + me.drawingArea = Math.min( + Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), + Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); + me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); + }, + + setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { + var me = this; + var maxRight = me.width - rightMovement - me.drawingArea; + var maxLeft = leftMovement + me.drawingArea; + var maxTop = topMovement + me.drawingArea; + var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; + + me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); + me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); + }, + + getIndexAngle: function(index) { + var chart = this.chart; + var angleMultiplier = 360 / chart.data.labels.length; + var options = chart.options || {}; + var startAngle = options.startAngle || 0; + + // Start from the top instead of right, so remove a quarter of the circle + var angle = (index * angleMultiplier + startAngle) % 360; + + return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360; + }, + + getDistanceFromCenterForValue: function(value) { + var me = this; + + if (helpers$1.isNullOrUndef(value)) { + return NaN; + } + + // Take into account half font size + the yPadding of the top value + var scalingFactor = me.drawingArea / (me.max - me.min); + if (me.options.ticks.reverse) { + return (me.max - value) * scalingFactor; + } + return (value - me.min) * scalingFactor; + }, + + getPointPosition: function(index, distanceFromCenter) { + var me = this; + var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); + return { + x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter, + y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter + }; + }, + + getPointPositionForValue: function(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, + + getBasePosition: function(index) { + var me = this; + var min = me.min; + var max = me.max; + + return me.getPointPositionForValue(index || 0, + me.beginAtZero ? 0 : + min < 0 && max < 0 ? max : + min > 0 && max > 0 ? min : + 0); + }, + + /** + * @private + */ + _drawGrid: function() { + var me = this; + var ctx = me.ctx; + var opts = me.options; + var gridLineOpts = opts.gridLines; + var angleLineOpts = opts.angleLines; + var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth); + var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color); + var i, offset, position; + + if (opts.pointLabels.display) { + drawPointLabels(me); + } + + if (gridLineOpts.display) { + helpers$1.each(me.ticks, function(label, index) { + if (index !== 0) { + offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + drawRadiusLine(me, gridLineOpts, offset, index); + } + }); + } + + if (angleLineOpts.display && lineWidth && lineColor) { + ctx.save(); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = lineColor; + if (ctx.setLineDash) { + ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []])); + ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]); + } + + for (i = me.chart.data.labels.length - 1; i >= 0; i--) { + offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max); + position = me.getPointPosition(i, offset); + ctx.beginPath(); + ctx.moveTo(me.xCenter, me.yCenter); + ctx.lineTo(position.x, position.y); + ctx.stroke(); + } + + ctx.restore(); + } + }, + + /** + * @private + */ + _drawLabels: function() { + var me = this; + var ctx = me.ctx; + var opts = me.options; + var tickOpts = opts.ticks; + + if (!tickOpts.display) { + return; + } + + var startAngle = me.getIndexAngle(0); + var tickFont = helpers$1.options._parseFont(tickOpts); + var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor); + var offset, width; + + ctx.save(); + ctx.font = tickFont.string; + ctx.translate(me.xCenter, me.yCenter); + ctx.rotate(startAngle); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + helpers$1.each(me.ticks, function(label, index) { + if (index === 0 && !tickOpts.reverse) { + return; + } + + offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); + + if (tickOpts.showLabelBackdrop) { + width = ctx.measureText(label).width; + ctx.fillStyle = tickOpts.backdropColor; + + ctx.fillRect( + -width / 2 - tickOpts.backdropPaddingX, + -offset - tickFont.size / 2 - tickOpts.backdropPaddingY, + width + tickOpts.backdropPaddingX * 2, + tickFont.size + tickOpts.backdropPaddingY * 2 + ); + } + + ctx.fillStyle = tickFontColor; + ctx.fillText(label, 0, -offset); + }); + + ctx.restore(); + }, + + /** + * @private + */ + _drawTitle: helpers$1.noop +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults$3 = defaultConfig$3; +scale_radialLinear._defaults = _defaults$3; + +var deprecated$1 = helpers$1._deprecated; +var resolve$5 = helpers$1.options.resolve; +var valueOrDefault$d = helpers$1.valueOrDefault; + +// Integer constants are from the ES6 spec. +var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; +var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + +var INTERVALS = { + millisecond: { + common: true, + size: 1, + steps: 1000 + }, + second: { + common: true, + size: 1000, + steps: 60 + }, + minute: { + common: true, + size: 60000, + steps: 60 + }, + hour: { + common: true, + size: 3600000, + steps: 24 + }, + day: { + common: true, + size: 86400000, + steps: 30 + }, + week: { + common: false, + size: 604800000, + steps: 4 + }, + month: { + common: true, + size: 2.628e9, + steps: 12 + }, + quarter: { + common: false, + size: 7.884e9, + steps: 4 + }, + year: { + common: true, + size: 3.154e10 + } +}; + +var UNITS = Object.keys(INTERVALS); + +function sorter(a, b) { + return a - b; +} + +function arrayUnique(items) { + var hash = {}; + var out = []; + var i, ilen, item; + + for (i = 0, ilen = items.length; i < ilen; ++i) { + item = items[i]; + if (!hash[item]) { + hash[item] = true; + out.push(item); + } + } + + return out; +} + +function getMin(options) { + return helpers$1.valueOrDefault(options.time.min, options.ticks.min); +} + +function getMax(options) { + return helpers$1.valueOrDefault(options.time.max, options.ticks.max); +} + +/** + * Returns an array of {time, pos} objects used to interpolate a specific `time` or position + * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is + * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other + * extremity (left + width or top + height). Note that it would be more optimized to directly + * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need + * to create the lookup table. The table ALWAYS contains at least two items: min and max. + * + * @param {number[]} timestamps - timestamps sorted from lowest to highest. + * @param {string} distribution - If 'linear', timestamps will be spread linearly along the min + * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. + * If 'series', timestamps will be positioned at the same distance from each other. In this + * case, only timestamps that break the time linearity are registered, meaning that in the + * best case, all timestamps are linear, the table contains only min and max. + */ +function buildLookupTable(timestamps, min, max, distribution) { + if (distribution === 'linear' || !timestamps.length) { + return [ + {time: min, pos: 0}, + {time: max, pos: 1} + ]; + } + + var table = []; + var items = [min]; + var i, ilen, prev, curr, next; + + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + curr = timestamps[i]; + if (curr > min && curr < max) { + items.push(curr); + } + } + + items.push(max); + + for (i = 0, ilen = items.length; i < ilen; ++i) { + next = items[i + 1]; + prev = items[i - 1]; + curr = items[i]; + + // only add points that breaks the scale linearity + if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { + table.push({time: curr, pos: i / (ilen - 1)}); + } + } + + return table; +} + +// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ +function lookup(table, key, value) { + var lo = 0; + var hi = table.length - 1; + var mid, i0, i1; + + while (lo >= 0 && lo <= hi) { + mid = (lo + hi) >> 1; + i0 = table[mid - 1] || null; + i1 = table[mid]; + + if (!i0) { + // given value is outside table (before first item) + return {lo: null, hi: i1}; + } else if (i1[key] < value) { + lo = mid + 1; + } else if (i0[key] > value) { + hi = mid - 1; + } else { + return {lo: i0, hi: i1}; + } + } + + // given value is outside table (after last item) + return {lo: i1, hi: null}; +} + +/** + * Linearly interpolates the given source `value` using the table items `skey` values and + * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') + * returns the position for a timestamp equal to 42. If value is out of bounds, values at + * index [0, 1] or [n - 1, n] are used for the interpolation. + */ +function interpolate$1(table, skey, sval, tkey) { + var range = lookup(table, skey, sval); + + // Note: the lookup table ALWAYS contains at least 2 items (min and max) + var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; + var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; + + var span = next[skey] - prev[skey]; + var ratio = span ? (sval - prev[skey]) / span : 0; + var offset = (next[tkey] - prev[tkey]) * ratio; + + return prev[tkey] + offset; +} + +function toTimestamp(scale, input) { + var adapter = scale._adapter; + var options = scale.options.time; + var parser = options.parser; + var format = parser || options.format; + var value = input; + + if (typeof parser === 'function') { + value = parser(value); + } + + // Only parse if its not a timestamp already + if (!helpers$1.isFinite(value)) { + value = typeof format === 'string' + ? adapter.parse(value, format) + : adapter.parse(value); + } + + if (value !== null) { + return +value; + } + + // Labels are in an incompatible format and no `parser` has been provided. + // The user might still use the deprecated `format` option for parsing. + if (!parser && typeof format === 'function') { + value = format(input); + + // `format` could return something else than a timestamp, if so, parse it + if (!helpers$1.isFinite(value)) { + value = adapter.parse(value); + } + } + + return value; +} + +function parse(scale, input) { + if (helpers$1.isNullOrUndef(input)) { + return null; + } + + var options = scale.options.time; + var value = toTimestamp(scale, scale.getRightValue(input)); + if (value === null) { + return value; + } + + if (options.round) { + value = +scale._adapter.startOf(value, options.round); + } + + return value; +} + +/** + * Figures out what unit results in an appropriate number of auto-generated ticks + */ +function determineUnitForAutoTicks(minUnit, min, max, capacity) { + var ilen = UNITS.length; + var i, interval, factor; + + for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { + interval = INTERVALS[UNITS[i]]; + factor = interval.steps ? interval.steps : MAX_INTEGER; + + if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { + return UNITS[i]; + } + } + + return UNITS[ilen - 1]; +} + +/** + * Figures out what unit to format a set of ticks with + */ +function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { + var i, unit; + + for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) { + unit = UNITS[i]; + if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { + return unit; + } + } + + return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; +} + +function determineMajorUnit(unit) { + for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { + if (INTERVALS[UNITS[i]].common) { + return UNITS[i]; + } + } +} + +/** + * Generates a maximum of `capacity` timestamps between min and max, rounded to the + * `minor` unit using the given scale time `options`. + * Important: this method can return ticks outside the min and max range, it's the + * responsibility of the calling code to clamp values if needed. + */ +function generate(scale, min, max, capacity) { + var adapter = scale._adapter; + var options = scale.options; + var timeOpts = options.time; + var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); + var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]); + var weekday = minor === 'week' ? timeOpts.isoWeekday : false; + var first = min; + var ticks = []; + var time; + + // For 'week' unit, handle the first day of week option + if (weekday) { + first = +adapter.startOf(first, 'isoWeek', weekday); + } + + // Align first ticks on unit + first = +adapter.startOf(first, weekday ? 'day' : minor); + + // Prevent browser from freezing in case user options request millions of milliseconds + if (adapter.diff(max, min, minor) > 100000 * stepSize) { + throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor; + } + + for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) { + ticks.push(time); + } + + if (time === max || options.bounds === 'ticks') { + ticks.push(time); + } + + return ticks; +} + +/** + * Returns the start and end offsets from edges in the form of {start, end} + * where each value is a relative width to the scale and ranges between 0 and 1. + * They add extra margins on the both sides by scaling down the original scale. + * Offsets are added when the `offset` option is true. + */ +function computeOffsets(table, ticks, min, max, options) { + var start = 0; + var end = 0; + var first, last; + + if (options.offset && ticks.length) { + first = interpolate$1(table, 'time', ticks[0], 'pos'); + if (ticks.length === 1) { + start = 1 - first; + } else { + start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2; + } + last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos'); + if (ticks.length === 1) { + end = last; + } else { + end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2; + } + } + + return {start: start, end: end, factor: 1 / (start + 1 + end)}; +} + +function setMajorTicks(scale, ticks, map, majorUnit) { + var adapter = scale._adapter; + var first = +adapter.startOf(ticks[0].value, majorUnit); + var last = ticks[ticks.length - 1].value; + var major, index; + + for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) { + index = map[major]; + if (index >= 0) { + ticks[index].major = true; + } + } + return ticks; +} + +function ticksFromTimestamps(scale, values, majorUnit) { + var ticks = []; + var map = {}; + var ilen = values.length; + var i, value; + + for (i = 0; i < ilen; ++i) { + value = values[i]; + map[value] = i; + + ticks.push({ + value: value, + major: false + }); + } + + // We set the major ticks separately from the above loop because calling startOf for every tick + // is expensive when there is a large number of ticks + return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit); +} + +var defaultConfig$4 = { + position: 'bottom', + + /** + * Data distribution along the scale: + * - 'linear': data are spread according to their time (distances can vary), + * - 'series': data are spread at the same distance from each other. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + distribution: 'linear', + + /** + * Scale boundary strategy (bypassed by min/max time options) + * - `data`: make sure data are fully visible, ticks outside are removed + * - `ticks`: make sure ticks are fully visible, data outside are truncated + * @see https://github.com/chartjs/Chart.js/pull/4556 + * @since 2.7.0 + */ + bounds: 'data', + + adapters: {}, + time: { + parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment + unit: false, // false == automatic or override with week, month, year, etc. + round: false, // none, or override with week, month, year, etc. + displayFormat: false, // DEPRECATED + isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/ + minUnit: 'millisecond', + displayFormats: {} + }, + ticks: { + autoSkip: false, + + /** + * Ticks generation input values: + * - 'auto': generates "optimal" ticks based on scale size and time options. + * - 'data': generates ticks from data (including labels from data {t|x|y} objects). + * - 'labels': generates ticks from user given `data.labels` values ONLY. + * @see https://github.com/chartjs/Chart.js/pull/4507 + * @since 2.7.0 + */ + source: 'auto', + + major: { + enabled: false + } + } +}; + +var scale_time = core_scale.extend({ + initialize: function() { + this.mergeTicksOptions(); + core_scale.prototype.initialize.call(this); + }, + + update: function() { + var me = this; + var options = me.options; + var time = options.time || (options.time = {}); + var adapter = me._adapter = new core_adapters._date(options.adapters.date); + + // DEPRECATIONS: output a message only one time per update + deprecated$1('time scale', time.format, 'time.format', 'time.parser'); + deprecated$1('time scale', time.min, 'time.min', 'ticks.min'); + deprecated$1('time scale', time.max, 'time.max', 'ticks.max'); + + // Backward compatibility: before introducing adapter, `displayFormats` was + // supposed to contain *all* unit/string pairs but this can't be resolved + // when loading the scale (adapters are loaded afterward), so let's populate + // missing formats on update + helpers$1.mergeIf(time.displayFormats, adapter.formats()); + + return core_scale.prototype.update.apply(me, arguments); + }, + + /** + * Allows data to be referenced via 't' attribute + */ + getRightValue: function(rawValue) { + if (rawValue && rawValue.t !== undefined) { + rawValue = rawValue.t; + } + return core_scale.prototype.getRightValue.call(this, rawValue); + }, + + determineDataLimits: function() { + var me = this; + var chart = me.chart; + var adapter = me._adapter; + var options = me.options; + var unit = options.time.unit || 'day'; + var min = MAX_INTEGER; + var max = MIN_INTEGER; + var timestamps = []; + var datasets = []; + var labels = []; + var i, j, ilen, jlen, data, timestamp, labelsAdded; + var dataLabels = me._getLabels(); + + for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { + labels.push(parse(me, dataLabels[i])); + } + + for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { + if (chart.isDatasetVisible(i)) { + data = chart.data.datasets[i].data; + + // Let's consider that all data have the same format. + if (helpers$1.isObject(data[0])) { + datasets[i] = []; + + for (j = 0, jlen = data.length; j < jlen; ++j) { + timestamp = parse(me, data[j]); + timestamps.push(timestamp); + datasets[i][j] = timestamp; + } + } else { + datasets[i] = labels.slice(0); + if (!labelsAdded) { + timestamps = timestamps.concat(labels); + labelsAdded = true; + } + } + } else { + datasets[i] = []; + } + } + + if (labels.length) { + min = Math.min(min, labels[0]); + max = Math.max(max, labels[labels.length - 1]); + } + + if (timestamps.length) { + timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter); + min = Math.min(min, timestamps[0]); + max = Math.max(max, timestamps[timestamps.length - 1]); + } + + min = parse(me, getMin(options)) || min; + max = parse(me, getMax(options)) || max; + + // In case there is no valid min/max, set limits based on unit time option + min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min; + max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max; + + // Make sure that max is strictly higher than min (required by the lookup table) + me.min = Math.min(min, max); + me.max = Math.max(min + 1, max); + + // PRIVATE + me._table = []; + me._timestamps = { + data: timestamps, + datasets: datasets, + labels: labels + }; + }, + + buildTicks: function() { + var me = this; + var min = me.min; + var max = me.max; + var options = me.options; + var tickOpts = options.ticks; + var timeOpts = options.time; + var timestamps = me._timestamps; + var ticks = []; + var capacity = me.getLabelCapacity(min); + var source = tickOpts.source; + var distribution = options.distribution; + var i, ilen, timestamp; + + if (source === 'data' || (source === 'auto' && distribution === 'series')) { + timestamps = timestamps.data; + } else if (source === 'labels') { + timestamps = timestamps.labels; + } else { + timestamps = generate(me, min, max, capacity); + } + + if (options.bounds === 'ticks' && timestamps.length) { + min = timestamps[0]; + max = timestamps[timestamps.length - 1]; + } + + // Enforce limits with user min/max options + min = parse(me, getMin(options)) || min; + max = parse(me, getMax(options)) || max; + + // Remove ticks outside the min/max range + for (i = 0, ilen = timestamps.length; i < ilen; ++i) { + timestamp = timestamps[i]; + if (timestamp >= min && timestamp <= max) { + ticks.push(timestamp); + } + } + + me.min = min; + me.max = max; + + // PRIVATE + // determineUnitForFormatting relies on the number of ticks so we don't use it when + // autoSkip is enabled because we don't yet know what the final number of ticks will be + me._unit = timeOpts.unit || (tickOpts.autoSkip + ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity) + : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max)); + me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined + : determineMajorUnit(me._unit); + me._table = buildLookupTable(me._timestamps.data, min, max, distribution); + me._offsets = computeOffsets(me._table, ticks, min, max, options); + + if (tickOpts.reverse) { + ticks.reverse(); + } + + return ticksFromTimestamps(me, ticks, me._majorUnit); + }, + + getLabelForIndex: function(index, datasetIndex) { + var me = this; + var adapter = me._adapter; + var data = me.chart.data; + var timeOpts = me.options.time; + var label = data.labels && index < data.labels.length ? data.labels[index] : ''; + var value = data.datasets[datasetIndex].data[index]; + + if (helpers$1.isObject(value)) { + label = me.getRightValue(value); + } + if (timeOpts.tooltipFormat) { + return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat); + } + if (typeof label === 'string') { + return label; + } + return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime); + }, + + /** + * Function to format an individual tick mark + * @private + */ + tickFormatFunction: function(time, index, ticks, format) { + var me = this; + var adapter = me._adapter; + var options = me.options; + var formats = options.time.displayFormats; + var minorFormat = formats[me._unit]; + var majorUnit = me._majorUnit; + var majorFormat = formats[majorUnit]; + var tick = ticks[index]; + var tickOpts = options.ticks; + var major = majorUnit && majorFormat && tick && tick.major; + var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat); + var nestedTickOpts = major ? tickOpts.major : tickOpts.minor; + var formatter = resolve$5([ + nestedTickOpts.callback, + nestedTickOpts.userCallback, + tickOpts.callback, + tickOpts.userCallback + ]); + + return formatter ? formatter(label, index, ticks) : label; + }, + + convertTicksToLabels: function(ticks) { + var labels = []; + var i, ilen; + + for (i = 0, ilen = ticks.length; i < ilen; ++i) { + labels.push(this.tickFormatFunction(ticks[i].value, i, ticks)); + } + + return labels; + }, + + /** + * @private + */ + getPixelForOffset: function(time) { + var me = this; + var offsets = me._offsets; + var pos = interpolate$1(me._table, 'time', time, 'pos'); + return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); + }, + + getPixelForValue: function(value, index, datasetIndex) { + var me = this; + var time = null; + + if (index !== undefined && datasetIndex !== undefined) { + time = me._timestamps.datasets[datasetIndex][index]; + } + + if (time === null) { + time = parse(me, value); + } + + if (time !== null) { + return me.getPixelForOffset(time); + } + }, + + getPixelForTick: function(index) { + var ticks = this.getTicks(); + return index >= 0 && index < ticks.length ? + this.getPixelForOffset(ticks[index].value) : + null; + }, + + getValueForPixel: function(pixel) { + var me = this; + var offsets = me._offsets; + var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; + var time = interpolate$1(me._table, 'pos', pos, 'time'); + + // DEPRECATION, we should return time directly + return me._adapter._create(time); + }, + + /** + * @private + */ + _getLabelSize: function(label) { + var me = this; + var ticksOpts = me.options.ticks; + var tickLabelWidth = me.ctx.measureText(label).width; + var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); + var cosRotation = Math.cos(angle); + var sinRotation = Math.sin(angle); + var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize); + + return { + w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation), + h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation) + }; + }, + + /** + * Crude approximation of what the label width might be + * @private + */ + getLabelWidth: function(label) { + return this._getLabelSize(label).w; + }, + + /** + * @private + */ + getLabelCapacity: function(exampleTime) { + var me = this; + var timeOpts = me.options.time; + var displayFormats = timeOpts.displayFormats; + + // pick the longest format (milliseconds) for guestimation + var format = displayFormats[timeOpts.unit] || displayFormats.millisecond; + var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format); + var size = me._getLabelSize(exampleLabel); + var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h); + + if (me.options.offset) { + capacity--; + } + + return capacity > 0 ? capacity : 1; + } +}); + +// INTERNAL: static default options, registered in src/index.js +var _defaults$4 = defaultConfig$4; +scale_time._defaults = _defaults$4; + +var scales = { + category: scale_category, + linear: scale_linear, + logarithmic: scale_logarithmic, + radialLinear: scale_radialLinear, + time: scale_time +}; + +var FORMATS = { + datetime: 'MMM D, YYYY, h:mm:ss a', + millisecond: 'h:mm:ss.SSS a', + second: 'h:mm:ss a', + minute: 'h:mm a', + hour: 'hA', + day: 'MMM D', + week: 'll', + month: 'MMM YYYY', + quarter: '[Q]Q - YYYY', + year: 'YYYY' +}; + +core_adapters._date.override(typeof moment === 'function' ? { + _id: 'moment', // DEBUG ONLY + + formats: function() { + return FORMATS; + }, + + parse: function(value, format) { + if (typeof value === 'string' && typeof format === 'string') { + value = moment(value, format); + } else if (!(value instanceof moment)) { + value = moment(value); + } + return value.isValid() ? value.valueOf() : null; + }, + + format: function(time, format) { + return moment(time).format(format); + }, + + add: function(time, amount, unit) { + return moment(time).add(amount, unit).valueOf(); + }, + + diff: function(max, min, unit) { + return moment(max).diff(moment(min), unit); + }, + + startOf: function(time, unit, weekday) { + time = moment(time); + if (unit === 'isoWeek') { + return time.isoWeekday(weekday).valueOf(); + } + return time.startOf(unit).valueOf(); + }, + + endOf: function(time, unit) { + return moment(time).endOf(unit).valueOf(); + }, + + // DEPRECATIONS + + /** + * Provided for backward compatibility with scale.getValueForPixel(). + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ + _create: function(time) { + return moment(time); + }, +} : {}); + +core_defaults._set('global', { + plugins: { + filler: { + propagate: true + } + } +}); + +var mappers = { + dataset: function(source) { + var index = source.fill; + var chart = source.chart; + var meta = chart.getDatasetMeta(index); + var visible = meta && chart.isDatasetVisible(index); + var points = (visible && meta.dataset._children) || []; + var length = points.length || 0; + + return !length ? null : function(point, i) { + return (i < length && points[i]._view) || null; + }; + }, + + boundary: function(source) { + var boundary = source.boundary; + var x = boundary ? boundary.x : null; + var y = boundary ? boundary.y : null; + + if (helpers$1.isArray(boundary)) { + return function(point, i) { + return boundary[i]; + }; + } + + return function(point) { + return { + x: x === null ? point.x : x, + y: y === null ? point.y : y, + }; + }; + } +}; + +// @todo if (fill[0] === '#') +function decodeFill(el, index, count) { + var model = el._model || {}; + var fill = model.fill; + var target; + + if (fill === undefined) { + fill = !!model.backgroundColor; + } + + if (fill === false || fill === null) { + return false; + } + + if (fill === true) { + return 'origin'; + } + + target = parseFloat(fill, 10); + if (isFinite(target) && Math.floor(target) === target) { + if (fill[0] === '-' || fill[0] === '+') { + target = index + target; + } + + if (target === index || target < 0 || target >= count) { + return false; + } + + return target; + } + + switch (fill) { + // compatibility + case 'bottom': + return 'start'; + case 'top': + return 'end'; + case 'zero': + return 'origin'; + // supported boundaries + case 'origin': + case 'start': + case 'end': + return fill; + // invalid fill values + default: + return false; + } +} + +function computeLinearBoundary(source) { + var model = source.el._model || {}; + var scale = source.el._scale || {}; + var fill = source.fill; + var target = null; + var horizontal; + + if (isFinite(fill)) { + return null; + } + + // Backward compatibility: until v3, we still need to support boundary values set on + // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and + // controllers might still use it (e.g. the Smith chart). + + if (fill === 'start') { + target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; + } else if (fill === 'end') { + target = model.scaleTop === undefined ? scale.top : model.scaleTop; + } else if (model.scaleZero !== undefined) { + target = model.scaleZero; + } else if (scale.getBasePixel) { + target = scale.getBasePixel(); + } + + if (target !== undefined && target !== null) { + if (target.x !== undefined && target.y !== undefined) { + return target; + } + + if (helpers$1.isFinite(target)) { + horizontal = scale.isHorizontal(); + return { + x: horizontal ? target : null, + y: horizontal ? null : target + }; + } + } + + return null; +} + +function computeCircularBoundary(source) { + var scale = source.el._scale; + var options = scale.options; + var length = scale.chart.data.labels.length; + var fill = source.fill; + var target = []; + var start, end, center, i, point; + + if (!length) { + return null; + } + + start = options.ticks.reverse ? scale.max : scale.min; + end = options.ticks.reverse ? scale.min : scale.max; + center = scale.getPointPositionForValue(0, start); + for (i = 0; i < length; ++i) { + point = fill === 'start' || fill === 'end' + ? scale.getPointPositionForValue(i, fill === 'start' ? start : end) + : scale.getBasePosition(i); + if (options.gridLines.circular) { + point.cx = center.x; + point.cy = center.y; + point.angle = scale.getIndexAngle(i) - Math.PI / 2; + } + target.push(point); + } + return target; +} + +function computeBoundary(source) { + var scale = source.el._scale || {}; + + if (scale.getPointPositionForValue) { + return computeCircularBoundary(source); + } + return computeLinearBoundary(source); +} + +function resolveTarget(sources, index, propagate) { + var source = sources[index]; + var fill = source.fill; + var visited = [index]; + var target; + + if (!propagate) { + return fill; + } + + while (fill !== false && visited.indexOf(fill) === -1) { + if (!isFinite(fill)) { + return fill; + } + + target = sources[fill]; + if (!target) { + return false; + } + + if (target.visible) { + return fill; + } + + visited.push(fill); + fill = target.fill; + } + + return false; +} + +function createMapper(source) { + var fill = source.fill; + var type = 'dataset'; + + if (fill === false) { + return null; + } + + if (!isFinite(fill)) { + type = 'boundary'; + } + + return mappers[type](source); +} + +function isDrawable(point) { + return point && !point.skip; +} + +function drawArea(ctx, curve0, curve1, len0, len1) { + var i, cx, cy, r; + + if (!len0 || !len1) { + return; + } + + // building first area curve (normal) + ctx.moveTo(curve0[0].x, curve0[0].y); + for (i = 1; i < len0; ++i) { + helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); + } + + if (curve1[0].angle !== undefined) { + cx = curve1[0].cx; + cy = curve1[0].cy; + r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2)); + for (i = len1 - 1; i > 0; --i) { + ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true); + } + return; + } + + // joining the two area curves + ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); + + // building opposite area curve (reverse) + for (i = len1 - 1; i > 0; --i) { + helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); + } +} + +function doFill(ctx, points, mapper, view, color, loop) { + var count = points.length; + var span = view.spanGaps; + var curve0 = []; + var curve1 = []; + var len0 = 0; + var len1 = 0; + var i, ilen, index, p0, p1, d0, d1, loopOffset; + + ctx.beginPath(); + + for (i = 0, ilen = count; i < ilen; ++i) { + index = i % count; + p0 = points[index]._view; + p1 = mapper(p0, index, view); + d0 = isDrawable(p0); + d1 = isDrawable(p1); + + if (loop && loopOffset === undefined && d0) { + loopOffset = i + 1; + ilen = count + loopOffset; + } + + if (d0 && d1) { + len0 = curve0.push(p0); + len1 = curve1.push(p1); + } else if (len0 && len1) { + if (!span) { + drawArea(ctx, curve0, curve1, len0, len1); + len0 = len1 = 0; + curve0 = []; + curve1 = []; + } else { + if (d0) { + curve0.push(p0); + } + if (d1) { + curve1.push(p1); + } + } + } + } + + drawArea(ctx, curve0, curve1, len0, len1); + + ctx.closePath(); + ctx.fillStyle = color; + ctx.fill(); +} + +var plugin_filler = { + id: 'filler', + + afterDatasetsUpdate: function(chart, options) { + var count = (chart.data.datasets || []).length; + var propagate = options.propagate; + var sources = []; + var meta, i, el, source; + + for (i = 0; i < count; ++i) { + meta = chart.getDatasetMeta(i); + el = meta.dataset; + source = null; + + if (el && el._model && el instanceof elements.Line) { + source = { + visible: chart.isDatasetVisible(i), + fill: decodeFill(el, i, count), + chart: chart, + el: el + }; + } + + meta.$filler = source; + sources.push(source); + } + + for (i = 0; i < count; ++i) { + source = sources[i]; + if (!source) { + continue; + } + + source.fill = resolveTarget(sources, i, propagate); + source.boundary = computeBoundary(source); + source.mapper = createMapper(source); + } + }, + + beforeDatasetsDraw: function(chart) { + var metasets = chart._getSortedVisibleDatasetMetas(); + var ctx = chart.ctx; + var meta, i, el, view, points, mapper, color; + + for (i = metasets.length - 1; i >= 0; --i) { + meta = metasets[i].$filler; + + if (!meta || !meta.visible) { + continue; + } + + el = meta.el; + view = el._view; + points = el._children || []; + mapper = meta.mapper; + color = view.backgroundColor || core_defaults.global.defaultColor; + + if (mapper && color && points.length) { + helpers$1.canvas.clipArea(ctx, chart.chartArea); + doFill(ctx, points, mapper, view, color, el._loop); + helpers$1.canvas.unclipArea(ctx); + } + } + } +}; + +var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter; +var noop$1 = helpers$1.noop; +var valueOrDefault$e = helpers$1.valueOrDefault; + +core_defaults._set('global', { + legend: { + display: true, + position: 'top', + align: 'center', + fullWidth: true, + reverse: false, + weight: 1000, + + // a callback that will handle + onClick: function(e, legendItem) { + var index = legendItem.datasetIndex; + var ci = this.chart; + var meta = ci.getDatasetMeta(index); + + // See controller.isDatasetVisible comment + meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; + + // We hid a dataset ... rerender the chart + ci.update(); + }, + + onHover: null, + onLeave: null, + + labels: { + boxWidth: 40, + padding: 10, + // Generates labels shown in the legend + // Valid properties to return: + // text : text to display + // fillStyle : fill of coloured box + // strokeStyle: stroke of coloured box + // hidden : if this legend item refers to a hidden item + // lineCap : cap style for line + // lineDash + // lineDashOffset : + // lineJoin : + // lineWidth : + generateLabels: function(chart) { + var datasets = chart.data.datasets; + var options = chart.options.legend || {}; + var usePointStyle = options.labels && options.labels.usePointStyle; + + return chart._getSortedDatasetMetas().map(function(meta) { + var style = meta.controller.getStyle(usePointStyle ? 0 : undefined); + + return { + text: datasets[meta.index].label, + fillStyle: style.backgroundColor, + hidden: !chart.isDatasetVisible(meta.index), + lineCap: style.borderCapStyle, + lineDash: style.borderDash, + lineDashOffset: style.borderDashOffset, + lineJoin: style.borderJoinStyle, + lineWidth: style.borderWidth, + strokeStyle: style.borderColor, + pointStyle: style.pointStyle, + rotation: style.rotation, + + // Below is extra data used for toggling the datasets + datasetIndex: meta.index + }; + }, this); + } + } + }, + + legendCallback: function(chart) { + var list = document.createElement('ul'); + var datasets = chart.data.datasets; + var i, ilen, listItem, listItemSpan; + + list.setAttribute('class', chart.id + '-legend'); + + for (i = 0, ilen = datasets.length; i < ilen; i++) { + listItem = list.appendChild(document.createElement('li')); + listItemSpan = listItem.appendChild(document.createElement('span')); + listItemSpan.style.backgroundColor = datasets[i].backgroundColor; + if (datasets[i].label) { + listItem.appendChild(document.createTextNode(datasets[i].label)); + } + } + + return list.outerHTML; + } +}); + +/** + * Helper function to get the box width based on the usePointStyle option + * @param {object} labelopts - the label options on the legend + * @param {number} fontSize - the label font size + * @return {number} width of the color box area + */ +function getBoxWidth(labelOpts, fontSize) { + return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ? + fontSize : + labelOpts.boxWidth; +} + +/** + * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! + */ +var Legend = core_element.extend({ + + initialize: function(config) { + var me = this; + helpers$1.extend(me, config); + + // Contains hit boxes for each dataset (in dataset order) + me.legendHitBoxes = []; + + /** + * @private + */ + me._hoveredItem = null; + + // Are we in doughnut mode which has a different data type + me.doughnutMode = false; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + // Any function defined here is inherited by all legend types. + // Any function can be extended by the legend type + + beforeUpdate: noop$1, + update: function(maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + }, + afterUpdate: noop$1, + + // + + beforeSetDimensions: noop$1, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop$1, + + // + + beforeBuildLabels: noop$1, + buildLabels: function() { + var me = this; + var labelOpts = me.options.labels || {}; + var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || []; + + if (labelOpts.filter) { + legendItems = legendItems.filter(function(item) { + return labelOpts.filter(item, me.chart.data); + }); + } + + if (me.options.reverse) { + legendItems.reverse(); + } + + me.legendItems = legendItems; + }, + afterBuildLabels: noop$1, + + // + + beforeFit: noop$1, + fit: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var display = opts.display; + + var ctx = me.ctx; + + var labelFont = helpers$1.options._parseFont(labelOpts); + var fontSize = labelFont.size; + + // Reset hit boxes + var hitboxes = me.legendHitBoxes = []; + + var minSize = me.minSize; + var isHorizontal = me.isHorizontal(); + + if (isHorizontal) { + minSize.width = me.maxWidth; // fill all the width + minSize.height = display ? 10 : 0; + } else { + minSize.width = display ? 10 : 0; + minSize.height = me.maxHeight; // fill all the height + } + + // Increase sizes here + if (!display) { + me.width = minSize.width = me.height = minSize.height = 0; + return; + } + ctx.font = labelFont.string; + + if (isHorizontal) { + // Labels + + // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one + var lineWidths = me.lineWidths = [0]; + var totalHeight = 0; + + ctx.textAlign = 'left'; + ctx.textBaseline = 'middle'; + + helpers$1.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) { + totalHeight += fontSize + labelOpts.padding; + lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; + } + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: width, + height: fontSize + }; + + lineWidths[lineWidths.length - 1] += width + labelOpts.padding; + }); + + minSize.height += totalHeight; + + } else { + var vPadding = labelOpts.padding; + var columnWidths = me.columnWidths = []; + var columnHeights = me.columnHeights = []; + var totalWidth = labelOpts.padding; + var currentColWidth = 0; + var currentColHeight = 0; + + helpers$1.each(me.legendItems, function(legendItem, i) { + var boxWidth = getBoxWidth(labelOpts, fontSize); + var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; + + // If too tall, go to new column + if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) { + totalWidth += currentColWidth + labelOpts.padding; + columnWidths.push(currentColWidth); // previous column width + columnHeights.push(currentColHeight); + currentColWidth = 0; + currentColHeight = 0; + } + + // Get max width + currentColWidth = Math.max(currentColWidth, itemWidth); + currentColHeight += fontSize + vPadding; + + // Store the hitbox width and height here. Final position will be updated in `draw` + hitboxes[i] = { + left: 0, + top: 0, + width: itemWidth, + height: fontSize + }; + }); + + totalWidth += currentColWidth; + columnWidths.push(currentColWidth); + columnHeights.push(currentColHeight); + minSize.width += totalWidth; + } + + me.width = minSize.width; + me.height = minSize.height; + }, + afterFit: noop$1, + + // Shared Methods + isHorizontal: function() { + return this.options.position === 'top' || this.options.position === 'bottom'; + }, + + // Actually draw the legend on the canvas + draw: function() { + var me = this; + var opts = me.options; + var labelOpts = opts.labels; + var globalDefaults = core_defaults.global; + var defaultColor = globalDefaults.defaultColor; + var lineDefault = globalDefaults.elements.line; + var legendHeight = me.height; + var columnHeights = me.columnHeights; + var legendWidth = me.width; + var lineWidths = me.lineWidths; + + if (!opts.display) { + return; + } + + var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width); + var ctx = me.ctx; + var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor); + var labelFont = helpers$1.options._parseFont(labelOpts); + var fontSize = labelFont.size; + var cursor; + + // Canvas setup + ctx.textAlign = rtlHelper.textAlign('left'); + ctx.textBaseline = 'middle'; + ctx.lineWidth = 0.5; + ctx.strokeStyle = fontColor; // for strikethrough effect + ctx.fillStyle = fontColor; // render in correct colour + ctx.font = labelFont.string; + + var boxWidth = getBoxWidth(labelOpts, fontSize); + var hitboxes = me.legendHitBoxes; + + // current position + var drawLegendBox = function(x, y, legendItem) { + if (isNaN(boxWidth) || boxWidth <= 0) { + return; + } + + // Set the ctx for the box + ctx.save(); + + var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth); + ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor); + ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle); + ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset); + ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle); + ctx.lineWidth = lineWidth; + ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor); + + if (ctx.setLineDash) { + // IE 9 and 10 do not support line dash + ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash)); + } + + if (labelOpts && labelOpts.usePointStyle) { + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + var radius = boxWidth * Math.SQRT2 / 2; + var centerX = rtlHelper.xPlus(x, boxWidth / 2); + var centerY = y + fontSize / 2; + + // Draw pointStyle as legend symbol + helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation); + } else { + // Draw box as legend symbol + ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); + if (lineWidth !== 0) { + ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); + } + } + + ctx.restore(); + }; + + var fillText = function(x, y, legendItem, textWidth) { + var halfFontSize = fontSize / 2; + var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize); + var yMiddle = y + halfFontSize; + + ctx.fillText(legendItem.text, xLeft, yMiddle); + + if (legendItem.hidden) { + // Strikethrough the text if hidden + ctx.beginPath(); + ctx.lineWidth = 2; + ctx.moveTo(xLeft, yMiddle); + ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle); + ctx.stroke(); + } + }; + + var alignmentOffset = function(dimension, blockSize) { + switch (opts.align) { + case 'start': + return labelOpts.padding; + case 'end': + return dimension - blockSize; + default: // center + return (dimension - blockSize + labelOpts.padding) / 2; + } + }; + + // Horizontal + var isHorizontal = me.isHorizontal(); + if (isHorizontal) { + cursor = { + x: me.left + alignmentOffset(legendWidth, lineWidths[0]), + y: me.top + labelOpts.padding, + line: 0 + }; + } else { + cursor = { + x: me.left + labelOpts.padding, + y: me.top + alignmentOffset(legendHeight, columnHeights[0]), + line: 0 + }; + } + + helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection); + + var itemHeight = fontSize + labelOpts.padding; + helpers$1.each(me.legendItems, function(legendItem, i) { + var textWidth = ctx.measureText(legendItem.text).width; + var width = boxWidth + (fontSize / 2) + textWidth; + var x = cursor.x; + var y = cursor.y; + + rtlHelper.setWidth(me.minSize.width); + + // Use (me.left + me.minSize.width) and (me.top + me.minSize.height) + // instead of me.right and me.bottom because me.width and me.height + // may have been changed since me.minSize was calculated + if (isHorizontal) { + if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) { + y = cursor.y += itemHeight; + cursor.line++; + x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]); + } + } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) { + x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; + cursor.line++; + y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]); + } + + var realX = rtlHelper.x(x); + + drawLegendBox(realX, y, legendItem); + + hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width); + hitboxes[i].top = y; + + // Fill the actual label + fillText(realX, y, legendItem, textWidth); + + if (isHorizontal) { + cursor.x += width + labelOpts.padding; + } else { + cursor.y += itemHeight; + } + }); + + helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection); + }, + + /** + * @private + */ + _getLegendItemAt: function(x, y) { + var me = this; + var i, hitBox, lh; + + if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { + // See if we are touching one of the dataset boxes + lh = me.legendHitBoxes; + for (i = 0; i < lh.length; ++i) { + hitBox = lh[i]; + + if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { + // Touching an element + return me.legendItems[i]; + } + } + } + + return null; + }, + + /** + * Handle an event + * @private + * @param {IEvent} event - The event to handle + */ + handleEvent: function(e) { + var me = this; + var opts = me.options; + var type = e.type === 'mouseup' ? 'click' : e.type; + var hoveredItem; + + if (type === 'mousemove') { + if (!opts.onHover && !opts.onLeave) { + return; + } + } else if (type === 'click') { + if (!opts.onClick) { + return; + } + } else { + return; + } + + // Chart event already has relative position in it + hoveredItem = me._getLegendItemAt(e.x, e.y); + + if (type === 'click') { + if (hoveredItem && opts.onClick) { + // use e.native for backwards compatibility + opts.onClick.call(me, e.native, hoveredItem); + } + } else { + if (opts.onLeave && hoveredItem !== me._hoveredItem) { + if (me._hoveredItem) { + opts.onLeave.call(me, e.native, me._hoveredItem); + } + me._hoveredItem = hoveredItem; + } + + if (opts.onHover && hoveredItem) { + // use e.native for backwards compatibility + opts.onHover.call(me, e.native, hoveredItem); + } + } + } +}); + +function createNewLegendAndAttach(chart, legendOpts) { + var legend = new Legend({ + ctx: chart.ctx, + options: legendOpts, + chart: chart + }); + + core_layouts.configure(chart, legend, legendOpts); + core_layouts.addBox(chart, legend); + chart.legend = legend; +} + +var plugin_legend = { + id: 'legend', + + /** + * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making + * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of + * the plugin, which one will be re-exposed in the chart.js file. + * https://github.com/chartjs/Chart.js/pull/2640 + * @private + */ + _element: Legend, + + beforeInit: function(chart) { + var legendOpts = chart.options.legend; + + if (legendOpts) { + createNewLegendAndAttach(chart, legendOpts); + } + }, + + beforeUpdate: function(chart) { + var legendOpts = chart.options.legend; + var legend = chart.legend; + + if (legendOpts) { + helpers$1.mergeIf(legendOpts, core_defaults.global.legend); + + if (legend) { + core_layouts.configure(chart, legend, legendOpts); + legend.options = legendOpts; + } else { + createNewLegendAndAttach(chart, legendOpts); + } + } else if (legend) { + core_layouts.removeBox(chart, legend); + delete chart.legend; + } + }, + + afterEvent: function(chart, e) { + var legend = chart.legend; + if (legend) { + legend.handleEvent(e); + } + } +}; + +var noop$2 = helpers$1.noop; + +core_defaults._set('global', { + title: { + display: false, + fontStyle: 'bold', + fullWidth: true, + padding: 10, + position: 'top', + text: '', + weight: 2000 // by default greater than legend (1000) to be above + } +}); + +/** + * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! + */ +var Title = core_element.extend({ + initialize: function(config) { + var me = this; + helpers$1.extend(me, config); + + // Contains hit boxes for each dataset (in dataset order) + me.legendHitBoxes = []; + }, + + // These methods are ordered by lifecycle. Utilities then follow. + + beforeUpdate: noop$2, + update: function(maxWidth, maxHeight, margins) { + var me = this; + + // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) + me.beforeUpdate(); + + // Absorb the master measurements + me.maxWidth = maxWidth; + me.maxHeight = maxHeight; + me.margins = margins; + + // Dimensions + me.beforeSetDimensions(); + me.setDimensions(); + me.afterSetDimensions(); + // Labels + me.beforeBuildLabels(); + me.buildLabels(); + me.afterBuildLabels(); + + // Fit + me.beforeFit(); + me.fit(); + me.afterFit(); + // + me.afterUpdate(); + + return me.minSize; + + }, + afterUpdate: noop$2, + + // + + beforeSetDimensions: noop$2, + setDimensions: function() { + var me = this; + // Set the unconstrained dimension before label rotation + if (me.isHorizontal()) { + // Reset position before calculating rotation + me.width = me.maxWidth; + me.left = 0; + me.right = me.width; + } else { + me.height = me.maxHeight; + + // Reset position before calculating rotation + me.top = 0; + me.bottom = me.height; + } + + // Reset padding + me.paddingLeft = 0; + me.paddingTop = 0; + me.paddingRight = 0; + me.paddingBottom = 0; + + // Reset minSize + me.minSize = { + width: 0, + height: 0 + }; + }, + afterSetDimensions: noop$2, + + // + + beforeBuildLabels: noop$2, + buildLabels: noop$2, + afterBuildLabels: noop$2, + + // + + beforeFit: noop$2, + fit: function() { + var me = this; + var opts = me.options; + var minSize = me.minSize = {}; + var isHorizontal = me.isHorizontal(); + var lineCount, textSize; + + if (!opts.display) { + me.width = minSize.width = me.height = minSize.height = 0; + return; + } + + lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1; + textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2; + + me.width = minSize.width = isHorizontal ? me.maxWidth : textSize; + me.height = minSize.height = isHorizontal ? textSize : me.maxHeight; + }, + afterFit: noop$2, + + // Shared Methods + isHorizontal: function() { + var pos = this.options.position; + return pos === 'top' || pos === 'bottom'; + }, + + // Actually draw the title block on the canvas + draw: function() { + var me = this; + var ctx = me.ctx; + var opts = me.options; + + if (!opts.display) { + return; + } + + var fontOpts = helpers$1.options._parseFont(opts); + var lineHeight = fontOpts.lineHeight; + var offset = lineHeight / 2 + opts.padding; + var rotation = 0; + var top = me.top; + var left = me.left; + var bottom = me.bottom; + var right = me.right; + var maxWidth, titleX, titleY; + + ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour + ctx.font = fontOpts.string; + + // Horizontal + if (me.isHorizontal()) { + titleX = left + ((right - left) / 2); // midpoint of the width + titleY = top + offset; + maxWidth = right - left; + } else { + titleX = opts.position === 'left' ? left + offset : right - offset; + titleY = top + ((bottom - top) / 2); + maxWidth = bottom - top; + rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); + } + + ctx.save(); + ctx.translate(titleX, titleY); + ctx.rotate(rotation); + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + var text = opts.text; + if (helpers$1.isArray(text)) { + var y = 0; + for (var i = 0; i < text.length; ++i) { + ctx.fillText(text[i], 0, y, maxWidth); + y += lineHeight; + } + } else { + ctx.fillText(text, 0, 0, maxWidth); + } + + ctx.restore(); + } +}); + +function createNewTitleBlockAndAttach(chart, titleOpts) { + var title = new Title({ + ctx: chart.ctx, + options: titleOpts, + chart: chart + }); + + core_layouts.configure(chart, title, titleOpts); + core_layouts.addBox(chart, title); + chart.titleBlock = title; +} + +var plugin_title = { + id: 'title', + + /** + * Backward compatibility: since 2.1.5, the title is registered as a plugin, making + * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of + * the plugin, which one will be re-exposed in the chart.js file. + * https://github.com/chartjs/Chart.js/pull/2640 + * @private + */ + _element: Title, + + beforeInit: function(chart) { + var titleOpts = chart.options.title; + + if (titleOpts) { + createNewTitleBlockAndAttach(chart, titleOpts); + } + }, + + beforeUpdate: function(chart) { + var titleOpts = chart.options.title; + var titleBlock = chart.titleBlock; + + if (titleOpts) { + helpers$1.mergeIf(titleOpts, core_defaults.global.title); + + if (titleBlock) { + core_layouts.configure(chart, titleBlock, titleOpts); + titleBlock.options = titleOpts; + } else { + createNewTitleBlockAndAttach(chart, titleOpts); + } + } else if (titleBlock) { + core_layouts.removeBox(chart, titleBlock); + delete chart.titleBlock; + } + } +}; + +var plugins = {}; +var filler = plugin_filler; +var legend = plugin_legend; +var title = plugin_title; +plugins.filler = filler; +plugins.legend = legend; +plugins.title = title; + +/** + * @namespace Chart + */ + + +core_controller.helpers = helpers$1; + +// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! +core_helpers(); + +core_controller._adapters = core_adapters; +core_controller.Animation = core_animation; +core_controller.animationService = core_animations; +core_controller.controllers = controllers; +core_controller.DatasetController = core_datasetController; +core_controller.defaults = core_defaults; +core_controller.Element = core_element; +core_controller.elements = elements; +core_controller.Interaction = core_interaction; +core_controller.layouts = core_layouts; +core_controller.platform = platform; +core_controller.plugins = core_plugins; +core_controller.Scale = core_scale; +core_controller.scaleService = core_scaleService; +core_controller.Ticks = core_ticks; +core_controller.Tooltip = core_tooltip; + +// Register built-in scales + +core_controller.helpers.each(scales, function(scale, type) { + core_controller.scaleService.registerScaleType(type, scale, scale._defaults); +}); + +// Load to register built-in adapters (as side effects) + + +// Loading built-in plugins + +for (var k in plugins) { + if (plugins.hasOwnProperty(k)) { + core_controller.plugins.register(plugins[k]); + } +} + +core_controller.platform.initialize(); + +var src = core_controller; +if (typeof window !== 'undefined') { + window.Chart = core_controller; +} + +// DEPRECATIONS + +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Chart + * @deprecated since version 2.8.0 + * @todo remove at version 3 + * @private + */ +core_controller.Chart = core_controller; + +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Legend + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +core_controller.Legend = plugins.legend._element; + +/** + * Provided for backward compatibility, not available anymore + * @namespace Chart.Title + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +core_controller.Title = plugins.title._element; + +/** + * Provided for backward compatibility, use Chart.plugins instead + * @namespace Chart.pluginService + * @deprecated since version 2.1.5 + * @todo remove at version 3 + * @private + */ +core_controller.pluginService = core_controller.plugins; + +/** + * Provided for backward compatibility, inheriting from Chart.PlugingBase has no + * effect, instead simply create/register plugins via plain JavaScript objects. + * @interface Chart.PluginBase + * @deprecated since version 2.5.0 + * @todo remove at version 3 + * @private + */ +core_controller.PluginBase = core_controller.Element.extend({}); + +/** + * Provided for backward compatibility, use Chart.helpers.canvas instead. + * @namespace Chart.canvasHelpers + * @deprecated since version 2.6.0 + * @todo remove at version 3 + * @private + */ +core_controller.canvasHelpers = core_controller.helpers.canvas; + +/** + * Provided for backward compatibility, use Chart.layouts instead. + * @namespace Chart.layoutService + * @deprecated since version 2.7.3 + * @todo remove at version 3 + * @private + */ +core_controller.layoutService = core_controller.layouts; + +/** + * Provided for backward compatibility, not available anymore. + * @namespace Chart.LinearScaleBase + * @deprecated since version 2.8 + * @todo remove at version 3 + * @private + */ +core_controller.LinearScaleBase = scale_linearbase; + +/** + * Provided for backward compatibility, instead we should create a new Chart + * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`). + * @deprecated since version 2.8.0 + * @todo remove at version 3 + */ +core_controller.helpers.each( + [ + 'Bar', + 'Bubble', + 'Doughnut', + 'Line', + 'PolarArea', + 'Radar', + 'Scatter' + ], + function(klass) { + core_controller[klass] = function(ctx, cfg) { + return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, { + type: klass.charAt(0).toLowerCase() + klass.slice(1) + })); + }; + } +); + +return src; + +}))); diff --git a/lib/web/chartjs/Chart.min.css b/lib/web/chartjs/Chart.min.css new file mode 100644 index 0000000000000..9dc5ac2e5faca --- /dev/null +++ b/lib/web/chartjs/Chart.min.css @@ -0,0 +1 @@ +@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0} \ No newline at end of file diff --git a/lib/web/chartjs/Chart.min.js b/lib/web/chartjs/Chart.min.js new file mode 100644 index 0000000000000..7c16b0d1287d0 --- /dev/null +++ b/lib/web/chartjs/Chart.min.js @@ -0,0 +1,7 @@ +/*! + * Chart.js v2.9.3 + * https://www.chartjs.org + * (c) 2019 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(function(){try{return require("moment")}catch(t){}}()):"function"==typeof define&&define.amd?define(["require"],(function(t){return e(function(){try{return t("moment")}catch(t){}}())})):(t=t||self).Chart=e(t.moment)}(this,(function(t){"use strict";t=t&&t.hasOwnProperty("default")?t.default:t;var e={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},n=function(t,e){return t(e={exports:{}},e.exports),e.exports}((function(t){var n={};for(var i in e)e.hasOwnProperty(i)&&(n[e[i]]=i);var a=t.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var r in a)if(a.hasOwnProperty(r)){if(!("channels"in a[r]))throw new Error("missing channels property: "+r);if(!("labels"in a[r]))throw new Error("missing channel labels property: "+r);if(a[r].labels.length!==a[r].channels)throw new Error("channel and label counts mismatch: "+r);var o=a[r].channels,s=a[r].labels;delete a[r].channels,delete a[r].labels,Object.defineProperty(a[r],"channels",{value:o}),Object.defineProperty(a[r],"labels",{value:s})}a.rgb.hsl=function(t){var e,n,i=t[0]/255,a=t[1]/255,r=t[2]/255,o=Math.min(i,a,r),s=Math.max(i,a,r),l=s-o;return s===o?e=0:i===s?e=(a-r)/l:a===s?e=2+(r-i)/l:r===s&&(e=4+(i-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),n=(o+s)/2,[e,100*(s===o?0:n<=.5?l/(s+o):l/(2-s-o)),100*n]},a.rgb.hsv=function(t){var e,n,i,a,r,o=t[0]/255,s=t[1]/255,l=t[2]/255,u=Math.max(o,s,l),d=u-Math.min(o,s,l),h=function(t){return(u-t)/6/d+.5};return 0===d?a=r=0:(r=d/u,e=h(o),n=h(s),i=h(l),o===u?a=i-n:s===u?a=1/3+e-i:l===u&&(a=2/3+n-e),a<0?a+=1:a>1&&(a-=1)),[360*a,100*r,100*u]},a.rgb.hwb=function(t){var e=t[0],n=t[1],i=t[2];return[a.rgb.hsl(t)[0],100*(1/255*Math.min(e,Math.min(n,i))),100*(i=1-1/255*Math.max(e,Math.max(n,i)))]},a.rgb.cmyk=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255;return[100*((1-n-(e=Math.min(1-n,1-i,1-a)))/(1-e)||0),100*((1-i-e)/(1-e)||0),100*((1-a-e)/(1-e)||0),100*e]},a.rgb.keyword=function(t){var i=n[t];if(i)return i;var a,r,o,s=1/0;for(var l in e)if(e.hasOwnProperty(l)){var u=e[l],d=(r=t,o=u,Math.pow(r[0]-o[0],2)+Math.pow(r[1]-o[1],2)+Math.pow(r[2]-o[2],2));d<s&&(s=d,a=l)}return a},a.keyword.rgb=function(t){return e[t]},a.rgb.xyz=function(t){var e=t[0]/255,n=t[1]/255,i=t[2]/255;return[100*(.4124*(e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]},a.rgb.lab=function(t){var e=a.rgb.xyz(t),n=e[0],i=e[1],r=e[2];return i/=100,r/=108.883,n=(n/=95.047)>.008856?Math.pow(n,1/3):7.787*n+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(n-i),200*(i-(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116))]},a.hsl.rgb=function(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0===s)return[r=255*l,r,r];e=2*l-(n=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(i=o+1/3*-(u-1))<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a},a.hsl.hsv=function(t){var e=t[0],n=t[1]/100,i=t[2]/100,a=n,r=Math.max(i,.01);return n*=(i*=2)<=1?i:2-i,a*=r<=1?r:2-r,[e,100*(0===i?2*a/(r+a):2*n/(i+n)),100*((i+n)/2)]},a.hsv.rgb=function(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r));switch(i*=255,a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}},a.hsv.hsl=function(t){var e,n,i,a=t[0],r=t[1]/100,o=t[2]/100,s=Math.max(o,.01);return i=(2-r)*o,n=r*s,[a,100*(n=(n/=(e=(2-r)*s)<=1?e:2-e)||0),100*(i/=2)]},a.hwb.rgb=function(t){var e,n,i,a,r,o,s,l=t[0]/360,u=t[1]/100,d=t[2]/100,h=u+d;switch(h>1&&(u/=h,d/=h),i=6*l-(e=Math.floor(6*l)),0!=(1&e)&&(i=1-i),a=u+i*((n=1-d)-u),e){default:case 6:case 0:r=n,o=a,s=u;break;case 1:r=a,o=n,s=u;break;case 2:r=u,o=n,s=a;break;case 3:r=u,o=a,s=n;break;case 4:r=a,o=u,s=n;break;case 5:r=n,o=u,s=a}return[255*r,255*o,255*s]},a.cmyk.rgb=function(t){var e=t[0]/100,n=t[1]/100,i=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a))]},a.xyz.rgb=function(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return n=-.9689*a+1.8758*r+.0415*o,i=.0557*a+-.204*r+1.057*o,e=(e=3.2406*a+-1.5372*r+-.4986*o)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:12.92*i,[255*(e=Math.min(Math.max(0,e),1)),255*(n=Math.min(Math.max(0,n),1)),255*(i=Math.min(Math.max(0,i),1))]},a.xyz.lab=function(t){var e=t[0],n=t[1],i=t[2];return n/=100,i/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(e-n),200*(n-(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116))]},a.lab.xyz=function(t){var e,n,i,a=t[0];e=t[1]/500+(n=(a+16)/116),i=n-t[2]/200;var r=Math.pow(n,3),o=Math.pow(e,3),s=Math.pow(i,3);return n=r>.008856?r:(n-16/116)/7.787,e=o>.008856?o:(e-16/116)/7.787,i=s>.008856?s:(i-16/116)/7.787,[e*=95.047,n*=100,i*=108.883]},a.lab.lch=function(t){var e,n=t[0],i=t[1],a=t[2];return(e=360*Math.atan2(a,i)/2/Math.PI)<0&&(e+=360),[n,Math.sqrt(i*i+a*a),e]},a.lch.lab=function(t){var e,n=t[0],i=t[1];return e=t[2]/360*2*Math.PI,[n,i*Math.cos(e),i*Math.sin(e)]},a.rgb.ansi16=function(t){var e=t[0],n=t[1],i=t[2],r=1 in arguments?arguments[1]:a.rgb.hsv(t)[2];if(0===(r=Math.round(r/50)))return 30;var o=30+(Math.round(i/255)<<2|Math.round(n/255)<<1|Math.round(e/255));return 2===r&&(o+=60),o},a.hsv.ansi16=function(t){return a.rgb.ansi16(a.hsv.rgb(t),t[2])},a.rgb.ansi256=function(t){var e=t[0],n=t[1],i=t[2];return e===n&&n===i?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(n/255*5)+Math.round(i/255*5)},a.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var n=.5*(1+~~(t>50));return[(1&e)*n*255,(e>>1&1)*n*255,(e>>2&1)*n*255]},a.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var n;return t-=16,[Math.floor(t/36)/5*255,Math.floor((n=t%36)/6)/5*255,n%6/5*255]},a.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},a.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var n=e[0];3===e[0].length&&(n=n.split("").map((function(t){return t+t})).join(""));var i=parseInt(n,16);return[i>>16&255,i>>8&255,255&i]},a.rgb.hcg=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255,r=Math.max(Math.max(n,i),a),o=Math.min(Math.min(n,i),a),s=r-o;return e=s<=0?0:r===n?(i-a)/s%6:r===i?2+(a-n)/s:4+(n-i)/s+4,e/=6,[360*(e%=1),100*s,100*(s<1?o/(1-s):0)]},a.hsl.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=1,a=0;return(i=n<.5?2*e*n:2*e*(1-n))<1&&(a=(n-.5*i)/(1-i)),[t[0],100*i,100*a]},a.hsv.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=e*n,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.hcg.rgb=function(t){var e=t[0]/360,n=t[1]/100,i=t[2]/100;if(0===n)return[255*i,255*i,255*i];var a,r=[0,0,0],o=e%1*6,s=o%1,l=1-s;switch(Math.floor(o)){case 0:r[0]=1,r[1]=s,r[2]=0;break;case 1:r[0]=l,r[1]=1,r[2]=0;break;case 2:r[0]=0,r[1]=1,r[2]=s;break;case 3:r[0]=0,r[1]=l,r[2]=1;break;case 4:r[0]=s,r[1]=0,r[2]=1;break;default:r[0]=1,r[1]=0,r[2]=l}return a=(1-n)*i,[255*(n*r[0]+a),255*(n*r[1]+a),255*(n*r[2]+a)]},a.hcg.hsv=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e),i=0;return n>0&&(i=e/n),[t[0],100*i,100*n]},a.hcg.hsl=function(t){var e=t[1]/100,n=t[2]/100*(1-e)+.5*e,i=0;return n>0&&n<.5?i=e/(2*n):n>=.5&&n<1&&(i=e/(2*(1-n))),[t[0],100*i,100*n]},a.hcg.hwb=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e);return[t[0],100*(n-e),100*(1-n)]},a.hwb.hcg=function(t){var e=t[1]/100,n=1-t[2]/100,i=n-e,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},a.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},a.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},a.gray.hsl=a.gray.hsv=function(t){return[0,0,t[0]]},a.gray.hwb=function(t){return[0,100,t[0]]},a.gray.cmyk=function(t){return[0,0,0,t[0]]},a.gray.lab=function(t){return[t[0],0,0]},a.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),n=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(n.length)+n},a.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}}));n.rgb,n.hsl,n.hsv,n.hwb,n.cmyk,n.xyz,n.lab,n.lch,n.hex,n.keyword,n.ansi16,n.ansi256,n.hcg,n.apple,n.gray;function i(t){var e=function(){for(var t={},e=Object.keys(n),i=e.length,a=0;a<i;a++)t[e[a]]={distance:-1,parent:null};return t}(),i=[t];for(e[t].distance=0;i.length;)for(var a=i.pop(),r=Object.keys(n[a]),o=r.length,s=0;s<o;s++){var l=r[s],u=e[l];-1===u.distance&&(u.distance=e[a].distance+1,u.parent=a,i.unshift(l))}return e}function a(t,e){return function(n){return e(t(n))}}function r(t,e){for(var i=[e[t].parent,t],r=n[e[t].parent][t],o=e[t].parent;e[o].parent;)i.unshift(e[o].parent),r=a(n[e[o].parent][o],r),o=e[o].parent;return r.conversion=i,r}var o={};Object.keys(n).forEach((function(t){o[t]={},Object.defineProperty(o[t],"channels",{value:n[t].channels}),Object.defineProperty(o[t],"labels",{value:n[t].labels});var e=function(t){for(var e=i(t),n={},a=Object.keys(e),o=a.length,s=0;s<o;s++){var l=a[s];null!==e[l].parent&&(n[l]=r(l,e))}return n}(t);Object.keys(e).forEach((function(n){var i=e[n];o[t][n]=function(t){var e=function(e){if(null==e)return e;arguments.length>1&&(e=Array.prototype.slice.call(arguments));var n=t(e);if("object"==typeof n)for(var i=n.length,a=0;a<i;a++)n[a]=Math.round(n[a]);return n};return"conversion"in t&&(e.conversion=t.conversion),e}(i),o[t][n].raw=function(t){var e=function(e){return null==e?e:(arguments.length>1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}(i)}))}));var s=o,l={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},u={getRgba:d,getHsla:h,getRgb:function(t){var e=d(t);return e&&e.slice(0,3)},getHsl:function(t){var e=h(t);return e&&e.slice(0,3)},getHwb:c,getAlpha:function(t){var e=d(t);if(e)return e[3];if(e=h(t))return e[3];if(e=c(t))return e[3]},hexString:function(t,e){e=void 0!==e&&3===t.length?e:t[3];return"#"+v(t[0])+v(t[1])+v(t[2])+(e>=0&&e<1?v(Math.round(255*e)):"")},rgbString:function(t,e){if(e<1||t[3]&&t[3]<1)return f(t,e);return"rgb("+t[0]+", "+t[1]+", "+t[2]+")"},rgbaString:f,percentString:function(t,e){if(e<1||t[3]&&t[3]<1)return g(t,e);var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+n+"%, "+i+"%, "+a+"%)"},percentaString:g,hslString:function(t,e){if(e<1||t[3]&&t[3]<1)return p(t,e);return"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"},hslaString:p,hwbString:function(t,e){void 0===e&&(e=void 0!==t[3]?t[3]:1);return"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"},keyword:function(t){return b[t.slice(0,3)]}};function d(t){if(t){var e=[0,0,0],n=1,i=t.match(/^#([a-fA-F0-9]{3,4})$/i),a="";if(i){a=(i=i[1])[3];for(var r=0;r<e.length;r++)e[r]=parseInt(i[r]+i[r],16);a&&(n=Math.round(parseInt(a+a,16)/255*100)/100)}else if(i=t.match(/^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i)){a=i[2],i=i[1];for(r=0;r<e.length;r++)e[r]=parseInt(i.slice(2*r,2*r+2),16);a&&(n=Math.round(parseInt(a,16)/255*100)/100)}else if(i=t.match(/^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i)){for(r=0;r<e.length;r++)e[r]=parseInt(i[r+1]);n=parseFloat(i[4])}else if(i=t.match(/^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i)){for(r=0;r<e.length;r++)e[r]=Math.round(2.55*parseFloat(i[r+1]));n=parseFloat(i[4])}else if(i=t.match(/(\w+)/)){if("transparent"==i[1])return[0,0,0,0];if(!(e=l[i[1]]))return}for(r=0;r<e.length;r++)e[r]=m(e[r],0,255);return n=n||0==n?m(n,0,1):1,e[3]=n,e}}function h(t){if(t){var e=t.match(/^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/);if(e){var n=parseFloat(e[4]);return[m(parseInt(e[1]),0,360),m(parseFloat(e[2]),0,100),m(parseFloat(e[3]),0,100),m(isNaN(n)?1:n,0,1)]}}}function c(t){if(t){var e=t.match(/^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/);if(e){var n=parseFloat(e[4]);return[m(parseInt(e[1]),0,360),m(parseFloat(e[2]),0,100),m(parseFloat(e[3]),0,100),m(isNaN(n)?1:n,0,1)]}}}function f(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function g(t,e){return"rgba("+Math.round(t[0]/255*100)+"%, "+Math.round(t[1]/255*100)+"%, "+Math.round(t[2]/255*100)+"%, "+(e||t[3]||1)+")"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function m(t,e,n){return Math.min(Math.max(e,t),n)}function v(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var b={};for(var x in l)b[l[x]]=x;var y=function(t){return t instanceof y?t:this instanceof y?(this.valid=!1,this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},void("string"==typeof t?(e=u.getRgba(t))?this.setValues("rgb",e):(e=u.getHsla(t))?this.setValues("hsl",e):(e=u.getHwb(t))&&this.setValues("hwb",e):"object"==typeof t&&(void 0!==(e=t).r||void 0!==e.red?this.setValues("rgb",e):void 0!==e.l||void 0!==e.lightness?this.setValues("hsl",e):void 0!==e.v||void 0!==e.value?this.setValues("hsv",e):void 0!==e.w||void 0!==e.whiteness?this.setValues("hwb",e):void 0===e.c&&void 0===e.cyan||this.setValues("cmyk",e)))):new y(t);var e};y.prototype={isValid:function(){return this.valid},rgb:function(){return this.setSpace("rgb",arguments)},hsl:function(){return this.setSpace("hsl",arguments)},hsv:function(){return this.setSpace("hsv",arguments)},hwb:function(){return this.setSpace("hwb",arguments)},cmyk:function(){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){var t=this.values;return 1!==t.alpha?t.hwb.concat([t.alpha]):t.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values;return t.rgb.concat([t.alpha])},hslaArray:function(){var t=this.values;return t.hsl.concat([t.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return t&&(t=(t%=360)<0?360+t:t),this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return u.hexString(this.values.rgb)},rgbString:function(){return u.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return u.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return u.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return u.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return u.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return u.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return u.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){var t=this.values.rgb;return t[0]<<16|t[1]<<8|t[2]},luminosity:function(){for(var t=this.values.rgb,e=[],n=0;n<t.length;n++){var i=t[n]/255;e[n]=i<=.03928?i/12.92:Math.pow((i+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),n=t.luminosity();return e>n?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=t,i=void 0===e?.5:e,a=2*i-1,r=this.alpha()-n.alpha(),o=((a*r==-1?a:(a+r)/(1+a*r))+1)/2,s=1-o;return this.rgb(o*this.red()+s*n.red(),o*this.green()+s*n.green(),o*this.blue()+s*n.blue()).alpha(this.alpha()*i+n.alpha()*(1-i))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new y,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},y.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},y.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},y.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i<t.length;i++)n[t.charAt(i)]=e[t][i];return 1!==e.alpha&&(n.a=e.alpha),n},y.prototype.setValues=function(t,e){var n,i,a=this.values,r=this.spaces,o=this.maxes,l=1;if(this.valid=!0,"alpha"===t)l=e;else if(e.length)a[t]=e.slice(0,t.length),l=e[t.length];else if(void 0!==e[t.charAt(0)]){for(n=0;n<t.length;n++)a[t][n]=e[t.charAt(n)];l=e.a}else if(void 0!==e[r[t][0]]){var u=r[t];for(n=0;n<t.length;n++)a[t][n]=e[u[n]];l=e.alpha}if(a.alpha=Math.max(0,Math.min(1,void 0===l?a.alpha:l)),"alpha"===t)return!1;for(n=0;n<t.length;n++)i=Math.max(0,Math.min(o[t][n],a[t][n])),a[t][n]=Math.round(i);for(var d in r)d!==t&&(a[d]=s[t][d](a[t]));return!0},y.prototype.setSpace=function(t,e){var n=e[0];return void 0===n?this.getValues(t):("number"==typeof n&&(n=Array.prototype.slice.call(e)),this.setValues(t,n),this)},y.prototype.setChannel=function(t,e,n){var i=this.values[t];return void 0===n?i[e]:n===i[e]?this:(i[e]=n,this.setValues(t,i),this)},"undefined"!=typeof window&&(window.Color=y);var _,k=y,w={noop:function(){},uid:(_=0,function(){return _++}),isNullOrUndef:function(t){return null==t},isArray:function(t){if(Array.isArray&&Array.isArray(t))return!0;var e=Object.prototype.toString.call(t);return"[object"===e.substr(0,7)&&"Array]"===e.substr(-6)},isObject:function(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)},isFinite:function(t){return("number"==typeof t||t instanceof Number)&&isFinite(t)},valueOrDefault:function(t,e){return void 0===t?e:t},valueAtIndexOrDefault:function(t,e,n){return w.valueOrDefault(w.isArray(t)?t[e]:t,n)},callback:function(t,e,n){if(t&&"function"==typeof t.call)return t.apply(n,e)},each:function(t,e,n,i){var a,r,o;if(w.isArray(t))if(r=t.length,i)for(a=r-1;a>=0;a--)e.call(n,t[a],a);else for(a=0;a<r;a++)e.call(n,t[a],a);else if(w.isObject(t))for(r=(o=Object.keys(t)).length,a=0;a<r;a++)e.call(n,t[o[a]],o[a])},arrayEquals:function(t,e){var n,i,a,r;if(!t||!e||t.length!==e.length)return!1;for(n=0,i=t.length;n<i;++n)if(a=t[n],r=e[n],a instanceof Array&&r instanceof Array){if(!w.arrayEquals(a,r))return!1}else if(a!==r)return!1;return!0},clone:function(t){if(w.isArray(t))return t.map(w.clone);if(w.isObject(t)){for(var e={},n=Object.keys(t),i=n.length,a=0;a<i;++a)e[n[a]]=w.clone(t[n[a]]);return e}return t},_merger:function(t,e,n,i){var a=e[t],r=n[t];w.isObject(a)&&w.isObject(r)?w.merge(a,r,i):e[t]=w.clone(r)},_mergerIf:function(t,e,n){var i=e[t],a=n[t];w.isObject(i)&&w.isObject(a)?w.mergeIf(i,a):e.hasOwnProperty(t)||(e[t]=w.clone(a))},merge:function(t,e,n){var i,a,r,o,s,l=w.isArray(e)?e:[e],u=l.length;if(!w.isObject(t))return t;for(i=(n=n||{}).merger||w._merger,a=0;a<u;++a)if(e=l[a],w.isObject(e))for(s=0,o=(r=Object.keys(e)).length;s<o;++s)i(r[s],t,e,n);return t},mergeIf:function(t,e){return w.merge(t,e,{merger:w._mergerIf})},extend:Object.assign||function(t){return w.merge(t,[].slice.call(arguments,1),{merger:function(t,e,n){e[t]=n[t]}})},inherits:function(t){var e=this,n=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},i=function(){this.constructor=n};return i.prototype=e.prototype,n.prototype=new i,n.extend=w.inherits,t&&w.extend(n.prototype,t),n.__super__=e.prototype,n},_deprecated:function(t,e,n,i){void 0!==e&&console.warn(t+': "'+n+'" is deprecated. Please use "'+i+'" instead')}},M=w;w.callCallback=w.callback,w.indexOf=function(t,e,n){return Array.prototype.indexOf.call(t,e,n)},w.getValueOrDefault=w.valueOrDefault,w.getValueAtIndexOrDefault=w.valueAtIndexOrDefault;var S={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return(t-=1)*t*t+1},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-((t-=1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return t*t*t*t*t},easeOutQuint:function(t){return(t-=1)*t*t*t*t+1},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return 1-Math.cos(t*(Math.PI/2))},easeOutSine:function(t){return Math.sin(t*(Math.PI/2))},easeInOutSine:function(t){return-.5*(Math.cos(Math.PI*t)-1)},easeInExpo:function(t){return 0===t?0:Math.pow(2,10*(t-1))},easeOutExpo:function(t){return 1===t?1:1-Math.pow(2,-10*t)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(2-Math.pow(2,-10*--t))},easeInCirc:function(t){return t>=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-S.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*S.easeInBounce(2*t):.5*S.easeOutBounce(2*t-1)+.5}},C={effects:S};M.easingEffects=S;var P=Math.PI,A=P/180,D=2*P,T=P/2,I=P/4,F=2*P/3,L={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,r){if(r){var o=Math.min(r,a/2,i/2),s=e+o,l=n+o,u=e+i-o,d=n+a-o;t.moveTo(e,l),s<u&&l<d?(t.arc(s,l,o,-P,-T),t.arc(u,l,o,-T,0),t.arc(u,d,o,0,T),t.arc(s,d,o,T,P)):s<u?(t.moveTo(s,n),t.arc(u,l,o,-T,T),t.arc(s,l,o,T,P+T)):l<d?(t.arc(s,l,o,-P,0),t.arc(s,d,o,0,P)):t.arc(s,l,o,-P,P),t.closePath(),t.moveTo(e,n)}else t.rect(e,n,i,a)},drawPoint:function(t,e,n,i,a,r){var o,s,l,u,d,h=(r||0)*A;if(e&&"object"==typeof e&&("[object HTMLImageElement]"===(o=e.toString())||"[object HTMLCanvasElement]"===o))return t.save(),t.translate(i,a),t.rotate(h),t.drawImage(e,-e.width/2,-e.height/2,e.width,e.height),void t.restore();if(!(isNaN(n)||n<=0)){switch(t.beginPath(),e){default:t.arc(i,a,n,0,D),t.closePath();break;case"triangle":t.moveTo(i+Math.sin(h)*n,a-Math.cos(h)*n),h+=F,t.lineTo(i+Math.sin(h)*n,a-Math.cos(h)*n),h+=F,t.lineTo(i+Math.sin(h)*n,a-Math.cos(h)*n),t.closePath();break;case"rectRounded":u=n-(d=.516*n),s=Math.cos(h+I)*u,l=Math.sin(h+I)*u,t.arc(i-s,a-l,d,h-P,h-T),t.arc(i+l,a-s,d,h-T,h),t.arc(i+s,a+l,d,h,h+T),t.arc(i-l,a+s,d,h+T,h+P),t.closePath();break;case"rect":if(!r){u=Math.SQRT1_2*n,t.rect(i-u,a-u,2*u,2*u);break}h+=I;case"rectRot":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+l,a-s),t.lineTo(i+s,a+l),t.lineTo(i-l,a+s),t.closePath();break;case"crossRot":h+=I;case"cross":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s);break;case"star":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s),h+=I,s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s);break;case"line":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l);break;case"dash":t.moveTo(i,a),t.lineTo(i+Math.cos(h)*n,a+Math.sin(h)*n)}t.fill(),t.stroke()}},_isPointInArea:function(t,e){return t.x>e.left-1e-6&&t.x<e.right+1e-6&&t.y>e.top-1e-6&&t.y<e.bottom+1e-6},clipArea:function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},unclipArea:function(t){t.restore()},lineTo:function(t,e,n,i){var a=n.steppedLine;if(a){if("middle"===a){var r=(e.x+n.x)/2;t.lineTo(r,i?n.y:e.y),t.lineTo(r,i?e.y:n.y)}else"after"===a&&!i||"after"!==a&&i?t.lineTo(e.x,n.y):t.lineTo(n.x,e.y);t.lineTo(n.x,n.y)}else n.tension?t.bezierCurveTo(i?e.controlPointPreviousX:e.controlPointNextX,i?e.controlPointPreviousY:e.controlPointNextY,i?n.controlPointNextX:n.controlPointPreviousX,i?n.controlPointNextY:n.controlPointPreviousY,n.x,n.y):t.lineTo(n.x,n.y)}},O=L;M.clear=L.clear,M.drawRoundedRectangle=function(t){t.beginPath(),L.roundedRect.apply(L,arguments)};var R={_set:function(t,e){return M.merge(this[t]||(this[t]={}),e)}};R._set("global",{defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",defaultLineHeight:1.2,showLines:!0});var z=R,N=M.valueOrDefault;var B={toLineHeight:function(t,e){var n=(""+t).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);if(!n||"normal"===n[1])return 1.2*e;switch(t=+n[2],n[3]){case"px":return t;case"%":t/=100}return e*t},toPadding:function(t){var e,n,i,a;return M.isObject(t)?(e=+t.top||0,n=+t.right||0,i=+t.bottom||0,a=+t.left||0):e=n=i=a=+t||0,{top:e,right:n,bottom:i,left:a,height:e+i,width:a+n}},_parseFont:function(t){var e=z.global,n=N(t.fontSize,e.defaultFontSize),i={family:N(t.fontFamily,e.defaultFontFamily),lineHeight:M.options.toLineHeight(N(t.lineHeight,e.defaultLineHeight),n),size:n,style:N(t.fontStyle,e.defaultFontStyle),weight:null,string:""};return i.string=function(t){return!t||M.isNullOrUndef(t.size)||M.isNullOrUndef(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}(i),i},resolve:function(t,e,n,i){var a,r,o,s=!0;for(a=0,r=t.length;a<r;++a)if(void 0!==(o=t[a])&&(void 0!==e&&"function"==typeof o&&(o=o(e),s=!1),void 0!==n&&M.isArray(o)&&(o=o[n],s=!1),void 0!==o))return i&&!s&&(i.cacheable=!1),o}},E={_factorize:function(t){var e,n=[],i=Math.sqrt(t);for(e=1;e<i;e++)t%e==0&&(n.push(e),n.push(t/e));return i===(0|i)&&n.push(i),n.sort((function(t,e){return t-e})).pop(),n},log10:Math.log10||function(t){var e=Math.log(t)*Math.LOG10E,n=Math.round(e);return t===Math.pow(10,n)?n:e}},W=E;M.log10=E.log10;var V=M,H=C,j=O,q=B,U=W,Y={getRtlAdapter:function(t,e,n){return t?function(t,e){return{x:function(n){return t+t+e-n},setWidth:function(t){e=t},textAlign:function(t){return"center"===t?t:"right"===t?"left":"right"},xPlus:function(t,e){return t-e},leftForLtr:function(t,e){return t-e}}}(e,n):{x:function(t){return t},setWidth:function(t){},textAlign:function(t){return t},xPlus:function(t,e){return t+e},leftForLtr:function(t,e){return t}}},overrideTextDirection:function(t,e){var n,i;"ltr"!==e&&"rtl"!==e||(i=[(n=t.canvas.style).getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",e,"important"),t.prevTextDirection=i)},restoreTextDirection:function(t){var e=t.prevTextDirection;void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}};V.easing=H,V.canvas=j,V.options=q,V.math=U,V.rtl=Y;var G=function(t){V.extend(this,t),this.initialize.apply(this,arguments)};V.extend(G.prototype,{_type:void 0,initialize:function(){this.hidden=!1},pivot:function(){var t=this;return t._view||(t._view=V.extend({},t._model)),t._start={},t},transition:function(t){var e=this,n=e._model,i=e._start,a=e._view;return n&&1!==t?(a||(a=e._view={}),i||(i=e._start={}),function(t,e,n,i){var a,r,o,s,l,u,d,h,c,f=Object.keys(n);for(a=0,r=f.length;a<r;++a)if(u=n[o=f[a]],e.hasOwnProperty(o)||(e[o]=u),(s=e[o])!==u&&"_"!==o[0]){if(t.hasOwnProperty(o)||(t[o]=s),(d=typeof u)===typeof(l=t[o]))if("string"===d){if((h=k(l)).valid&&(c=k(u)).valid){e[o]=c.mix(h,i).rgbString();continue}}else if(V.isFinite(l)&&V.isFinite(u)){e[o]=l+(u-l)*i;continue}e[o]=u}}(i,a,n,t),e):(e._view=V.extend({},n),e._start=null,e)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return V.isNumber(this._model.x)&&V.isNumber(this._model.y)}}),G.extend=V.inherits;var X=G,K=X.extend({chart:null,currentStep:0,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),Z=K;Object.defineProperty(K.prototype,"animationObject",{get:function(){return this}}),Object.defineProperty(K.prototype,"chartInstance",{get:function(){return this.chart},set:function(t){this.chart=t}}),z._set("global",{animation:{duration:1e3,easing:"easeOutQuart",onProgress:V.noop,onComplete:V.noop}});var $={animations:[],request:null,addAnimation:function(t,e,n,i){var a,r,o=this.animations;for(e.chart=t,e.startTime=Date.now(),e.duration=n,i||(t.animating=!0),a=0,r=o.length;a<r;++a)if(o[a].chart===t)return void(o[a]=e);o.push(e),1===o.length&&this.requestAnimationFrame()},cancelAnimation:function(t){var e=V.findIndex(this.animations,(function(e){return e.chart===t}));-1!==e&&(this.animations.splice(e,1),t.animating=!1)},requestAnimationFrame:function(){var t=this;null===t.request&&(t.request=V.requestAnimFrame.call(window,(function(){t.request=null,t.startDigest()})))},startDigest:function(){this.advance(),this.animations.length>0&&this.requestAnimationFrame()},advance:function(){for(var t,e,n,i,a=this.animations,r=0;r<a.length;)e=(t=a[r]).chart,n=t.numSteps,i=Math.floor((Date.now()-t.startTime)/t.duration*n)+1,t.currentStep=Math.min(i,n),V.callback(t.render,[e,t],e),V.callback(t.onAnimationProgress,[t],e),t.currentStep>=n?(V.callback(t.onAnimationComplete,[t],e),e.animating=!1,a.splice(r,1)):++r}},J=V.options.resolve,Q=["push","pop","shift","splice","unshift"];function tt(t,e){var n=t._chartjs;if(n){var i=n.listeners,a=i.indexOf(e);-1!==a&&i.splice(a,1),i.length>0||(Q.forEach((function(e){delete t[e]})),delete t._chartjs)}}var et=function(t,e){this.initialize(t,e)};V.extend(et.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements(),n._type=n.getMeta().type},updateIndex:function(t){this.index=t},linkScales:function(){var t=this.getMeta(),e=this.chart,n=e.scales,i=this.getDataset(),a=e.options.scales;null!==t.xAxisID&&t.xAxisID in n&&!i.xAxisID||(t.xAxisID=i.xAxisID||a.xAxes[0].id),null!==t.yAxisID&&t.yAxisID in n&&!i.yAxisID||(t.yAxisID=i.yAxisID||a.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&tt(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,n=this.getMeta(),i=this.getDataset().data||[],a=n.data;for(t=0,e=i.length;t<e;++t)a[t]=a[t]||this.createMetaData(t);n.dataset=n.dataset||this.createMetaDataset()},addElementAndReset:function(t){var e=this.createMetaData(t);this.getMeta().data.splice(t,0,e),this.updateElement(e,t,!0)},buildOrUpdateElements:function(){var t,e,n=this,i=n.getDataset(),a=i.data||(i.data=[]);n._data!==a&&(n._data&&tt(n._data,n),a&&Object.isExtensible(a)&&(e=n,(t=a)._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),Q.forEach((function(e){var n="onData"+e.charAt(0).toUpperCase()+e.slice(1),i=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value:function(){var e=Array.prototype.slice.call(arguments),a=i.apply(this,e);return V.each(t._chartjs.listeners,(function(t){"function"==typeof t[n]&&t[n].apply(t,e)})),a}})})))),n._data=a),n.resyncElements()},_configure:function(){this._config=V.merge({},[this.chart.options.datasets[this._type],this.getDataset()],{merger:function(t,e,n){"_meta"!==t&&"data"!==t&&V._merger(t,e,n)}})},_update:function(t){this._configure(),this._cachedDataOpts=null,this.update(t)},update:V.noop,transition:function(t){for(var e=this.getMeta(),n=e.data||[],i=n.length,a=0;a<i;++a)n[a].transition(t);e.dataset&&e.dataset.transition(t)},draw:function(){var t=this.getMeta(),e=t.data||[],n=e.length,i=0;for(t.dataset&&t.dataset.draw();i<n;++i)e[i].draw()},getStyle:function(t){var e,n=this.getMeta(),i=n.dataset;return this._configure(),i&&void 0===t?e=this._resolveDatasetElementOptions(i||{}):(t=t||0,e=this._resolveDataElementOptions(n.data[t]||{},t)),!1!==e.fill&&null!==e.fill||(e.backgroundColor=e.borderColor),e},_resolveDatasetElementOptions:function(t,e){var n,i,a,r,o=this,s=o.chart,l=o._config,u=t.custom||{},d=s.options.elements[o.datasetElementType.prototype._type]||{},h=o._datasetElementOptions,c={},f={chart:s,dataset:o.getDataset(),datasetIndex:o.index,hover:e};for(n=0,i=h.length;n<i;++n)a=h[n],r=e?"hover"+a.charAt(0).toUpperCase()+a.slice(1):a,c[a]=J([u[r],l[r],d[r]],f);return c},_resolveDataElementOptions:function(t,e){var n=this,i=t&&t.custom,a=n._cachedDataOpts;if(a&&!i)return a;var r,o,s,l,u=n.chart,d=n._config,h=u.options.elements[n.dataElementType.prototype._type]||{},c=n._dataElementOptions,f={},g={chart:u,dataIndex:e,dataset:n.getDataset(),datasetIndex:n.index},p={cacheable:!i};if(i=i||{},V.isArray(c))for(o=0,s=c.length;o<s;++o)f[l=c[o]]=J([i[l],d[l],h[l]],g,e,p);else for(o=0,s=(r=Object.keys(c)).length;o<s;++o)f[l=r[o]]=J([i[l],d[c[l]],d[l],h[l]],g,e,p);return p.cacheable&&(n._cachedDataOpts=Object.freeze(f)),f},removeHoverStyle:function(t){V.merge(t._model,t.$previousStyle||{}),delete t.$previousStyle},setHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],n=t._index,i=t.custom||{},a=t._model,r=V.getHoverColor;t.$previousStyle={backgroundColor:a.backgroundColor,borderColor:a.borderColor,borderWidth:a.borderWidth},a.backgroundColor=J([i.hoverBackgroundColor,e.hoverBackgroundColor,r(a.backgroundColor)],void 0,n),a.borderColor=J([i.hoverBorderColor,e.hoverBorderColor,r(a.borderColor)],void 0,n),a.borderWidth=J([i.hoverBorderWidth,e.hoverBorderWidth,a.borderWidth],void 0,n)},_removeDatasetHoverStyle:function(){var t=this.getMeta().dataset;t&&this.removeHoverStyle(t)},_setDatasetHoverStyle:function(){var t,e,n,i,a,r,o=this.getMeta().dataset,s={};if(o){for(r=o._model,a=this._resolveDatasetElementOptions(o,!0),t=0,e=(i=Object.keys(a)).length;t<e;++t)s[n=i[t]]=r[n],r[n]=a[n];o.$previousStyle=s}},resyncElements:function(){var t=this.getMeta(),e=this.getDataset().data,n=t.data.length,i=e.length;i<n?t.data.splice(i,n-i):i>n&&this.insertElements(n,i-n)},insertElements:function(t,e){for(var n=0;n<e;++n)this.addElementAndReset(t+n)},onDataPush:function(){var t=arguments.length;this.insertElements(this.getDataset().data.length-t,t)},onDataPop:function(){this.getMeta().data.pop()},onDataShift:function(){this.getMeta().data.shift()},onDataSplice:function(t,e){this.getMeta().data.splice(t,e),this.insertElements(t,arguments.length-2)},onDataUnshift:function(){this.insertElements(0,arguments.length)}}),et.extend=V.inherits;var nt=et,it=2*Math.PI;function at(t,e){var n=e.startAngle,i=e.endAngle,a=e.pixelMargin,r=a/e.outerRadius,o=e.x,s=e.y;t.beginPath(),t.arc(o,s,e.outerRadius,n-r,i+r),e.innerRadius>a?(r=a/e.innerRadius,t.arc(o,s,e.innerRadius-a,i+r,n-r,!0)):t.arc(o,s,a,i+Math.PI/2,n-Math.PI/2),t.closePath(),t.clip()}function rt(t,e,n){var i="inner"===e.borderAlign;i?(t.lineWidth=2*e.borderWidth,t.lineJoin="round"):(t.lineWidth=e.borderWidth,t.lineJoin="bevel"),n.fullCircles&&function(t,e,n,i){var a,r=n.endAngle;for(i&&(n.endAngle=n.startAngle+it,at(t,n),n.endAngle=r,n.endAngle===n.startAngle&&n.fullCircles&&(n.endAngle+=it,n.fullCircles--)),t.beginPath(),t.arc(n.x,n.y,n.innerRadius,n.startAngle+it,n.startAngle,!0),a=0;a<n.fullCircles;++a)t.stroke();for(t.beginPath(),t.arc(n.x,n.y,e.outerRadius,n.startAngle,n.startAngle+it),a=0;a<n.fullCircles;++a)t.stroke()}(t,e,n,i),i&&at(t,n),t.beginPath(),t.arc(n.x,n.y,e.outerRadius,n.startAngle,n.endAngle),t.arc(n.x,n.y,n.innerRadius,n.endAngle,n.startAngle,!0),t.closePath(),t.stroke()}z._set("global",{elements:{arc:{backgroundColor:z.global.defaultColor,borderColor:"#fff",borderWidth:2,borderAlign:"center"}}});var ot=X.extend({_type:"arc",inLabelRange:function(t){var e=this._view;return!!e&&Math.pow(t-e.x,2)<Math.pow(e.radius+e.hoverRadius,2)},inRange:function(t,e){var n=this._view;if(n){for(var i=V.getAngleFromPoint(n,{x:t,y:e}),a=i.angle,r=i.distance,o=n.startAngle,s=n.endAngle;s<o;)s+=it;for(;a>s;)a-=it;for(;a<o;)a+=it;var l=a>=o&&a<=s,u=r>=n.innerRadius&&r<=n.outerRadius;return l&&u}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t,e=this._chart.ctx,n=this._view,i="inner"===n.borderAlign?.33:0,a={x:n.x,y:n.y,innerRadius:n.innerRadius,outerRadius:Math.max(n.outerRadius-i,0),pixelMargin:i,startAngle:n.startAngle,endAngle:n.endAngle,fullCircles:Math.floor(n.circumference/it)};if(e.save(),e.fillStyle=n.backgroundColor,e.strokeStyle=n.borderColor,a.fullCircles){for(a.endAngle=a.startAngle+it,e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),t=0;t<a.fullCircles;++t)e.fill();a.endAngle=a.startAngle+n.circumference%it}e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),e.fill(),n.borderWidth&&rt(e,n,a),e.restore()}}),st=V.valueOrDefault,lt=z.global.defaultColor;z._set("global",{elements:{line:{tension:.4,backgroundColor:lt,borderWidth:3,borderColor:lt,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0}}});var ut=X.extend({_type:"line",draw:function(){var t,e,n,i=this,a=i._view,r=i._chart.ctx,o=a.spanGaps,s=i._children.slice(),l=z.global,u=l.elements.line,d=-1,h=i._loop;if(s.length){if(i._loop){for(t=0;t<s.length;++t)if(e=V.previousItem(s,t),!s[t]._view.skip&&e._view.skip){s=s.slice(t).concat(s.slice(0,t)),h=o;break}h&&s.push(s[0])}for(r.save(),r.lineCap=a.borderCapStyle||u.borderCapStyle,r.setLineDash&&r.setLineDash(a.borderDash||u.borderDash),r.lineDashOffset=st(a.borderDashOffset,u.borderDashOffset),r.lineJoin=a.borderJoinStyle||u.borderJoinStyle,r.lineWidth=st(a.borderWidth,u.borderWidth),r.strokeStyle=a.borderColor||l.defaultColor,r.beginPath(),(n=s[0]._view).skip||(r.moveTo(n.x,n.y),d=0),t=1;t<s.length;++t)n=s[t]._view,e=-1===d?V.previousItem(s,t):s[d],n.skip||(d!==t-1&&!o||-1===d?r.moveTo(n.x,n.y):V.canvas.lineTo(r,e._view,n),d=t);h&&r.closePath(),r.stroke(),r.restore()}}}),dt=V.valueOrDefault,ht=z.global.defaultColor;function ct(t){var e=this._view;return!!e&&Math.abs(t-e.x)<e.radius+e.hitRadius}z._set("global",{elements:{point:{radius:3,pointStyle:"circle",backgroundColor:ht,borderColor:ht,borderWidth:1,hitRadius:1,hoverRadius:4,hoverBorderWidth:1}}});var ft=X.extend({_type:"point",inRange:function(t,e){var n=this._view;return!!n&&Math.pow(t-n.x,2)+Math.pow(e-n.y,2)<Math.pow(n.hitRadius+n.radius,2)},inLabelRange:ct,inXRange:ct,inYRange:function(t){var e=this._view;return!!e&&Math.abs(t-e.y)<e.radius+e.hitRadius},getCenterPoint:function(){var t=this._view;return{x:t.x,y:t.y}},getArea:function(){return Math.PI*Math.pow(this._view.radius,2)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y,padding:t.radius+t.borderWidth}},draw:function(t){var e=this._view,n=this._chart.ctx,i=e.pointStyle,a=e.rotation,r=e.radius,o=e.x,s=e.y,l=z.global,u=l.defaultColor;e.skip||(void 0===t||V.canvas._isPointInArea(e,t))&&(n.strokeStyle=e.borderColor||u,n.lineWidth=dt(e.borderWidth,l.elements.point.borderWidth),n.fillStyle=e.backgroundColor||u,V.canvas.drawPoint(n,i,r,o,s,a))}}),gt=z.global.defaultColor;function pt(t){return t&&void 0!==t.width}function mt(t){var e,n,i,a,r;return pt(t)?(r=t.width/2,e=t.x-r,n=t.x+r,i=Math.min(t.y,t.base),a=Math.max(t.y,t.base)):(r=t.height/2,e=Math.min(t.x,t.base),n=Math.max(t.x,t.base),i=t.y-r,a=t.y+r),{left:e,top:i,right:n,bottom:a}}function vt(t,e,n){return t===e?n:t===n?e:t}function bt(t,e,n){var i,a,r,o,s=t.borderWidth,l=function(t){var e=t.borderSkipped,n={};return e?(t.horizontal?t.base>t.x&&(e=vt(e,"left","right")):t.base<t.y&&(e=vt(e,"bottom","top")),n[e]=!0,n):n}(t);return V.isObject(s)?(i=+s.top||0,a=+s.right||0,r=+s.bottom||0,o=+s.left||0):i=a=r=o=+s||0,{t:l.top||i<0?0:i>n?n:i,r:l.right||a<0?0:a>e?e:a,b:l.bottom||r<0?0:r>n?n:r,l:l.left||o<0?0:o>e?e:o}}function xt(t,e,n){var i=null===e,a=null===n,r=!(!t||i&&a)&&mt(t);return r&&(i||e>=r.left&&e<=r.right)&&(a||n>=r.top&&n<=r.bottom)}z._set("global",{elements:{rectangle:{backgroundColor:gt,borderColor:gt,borderSkipped:"bottom",borderWidth:0}}});var yt=X.extend({_type:"rectangle",draw:function(){var t=this._chart.ctx,e=this._view,n=function(t){var e=mt(t),n=e.right-e.left,i=e.bottom-e.top,a=bt(t,n/2,i/2);return{outer:{x:e.left,y:e.top,w:n,h:i},inner:{x:e.left+a.l,y:e.top+a.t,w:n-a.l-a.r,h:i-a.t-a.b}}}(e),i=n.outer,a=n.inner;t.fillStyle=e.backgroundColor,t.fillRect(i.x,i.y,i.w,i.h),i.w===a.w&&i.h===a.h||(t.save(),t.beginPath(),t.rect(i.x,i.y,i.w,i.h),t.clip(),t.fillStyle=e.borderColor,t.rect(a.x,a.y,a.w,a.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return xt(this._view,t,e)},inLabelRange:function(t,e){var n=this._view;return pt(n)?xt(n,t,null):xt(n,null,e)},inXRange:function(t){return xt(this._view,t,null)},inYRange:function(t){return xt(this._view,null,t)},getCenterPoint:function(){var t,e,n=this._view;return pt(n)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return pt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),_t={},kt=ot,wt=ut,Mt=ft,St=yt;_t.Arc=kt,_t.Line=wt,_t.Point=Mt,_t.Rectangle=St;var Ct=V._deprecated,Pt=V.valueOrDefault;function At(t,e,n){var i,a,r=n.barThickness,o=e.stackCount,s=e.pixels[t],l=V.isNullOrUndef(r)?function(t,e){var n,i,a,r,o=t._length;for(a=1,r=e.length;a<r;++a)o=Math.min(o,Math.abs(e[a]-e[a-1]));for(a=0,r=t.getTicks().length;a<r;++a)i=t.getPixelForTick(a),o=a>0?Math.min(o,Math.abs(i-n)):o,n=i;return o}(e.scale,e.pixels):-1;return V.isNullOrUndef(r)?(i=l*n.categoryPercentage,a=n.barPercentage):(i=r*o,a=1),{chunk:i/o,ratio:a,start:s-i/2}}z._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),z._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var Dt=nt.extend({dataElementType:_t.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var t,e,n=this;nt.prototype.initialize.apply(n,arguments),(t=n.getMeta()).stack=n.getDataset().stack,t.bar=!0,e=n._getIndexScale().options,Ct("bar chart",e.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),Ct("bar chart",e.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),Ct("bar chart",e.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),Ct("bar chart",n._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),Ct("bar chart",e.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(t){var e,n,i=this.getMeta().data;for(this._ruler=this.getRuler(),e=0,n=i.length;e<n;++e)this.updateElement(i[e],e,t)},updateElement:function(t,e,n){var i=this,a=i.getMeta(),r=i.getDataset(),o=i._resolveDataElementOptions(t,e);t._xScale=i.getScaleForId(a.xAxisID),t._yScale=i.getScaleForId(a.yAxisID),t._datasetIndex=i.index,t._index=e,t._model={backgroundColor:o.backgroundColor,borderColor:o.borderColor,borderSkipped:o.borderSkipped,borderWidth:o.borderWidth,datasetLabel:r.label,label:i.chart.data.labels[e]},V.isArray(r.data[e])&&(t._model.borderSkipped=null),i._updateElementGeometry(t,e,n,o),t.pivot()},_updateElementGeometry:function(t,e,n,i){var a=this,r=t._model,o=a._getValueScale(),s=o.getBasePixel(),l=o.isHorizontal(),u=a._ruler||a.getRuler(),d=a.calculateBarValuePixels(a.index,e,i),h=a.calculateBarIndexPixels(a.index,e,u,i);r.horizontal=l,r.base=n?s:d.base,r.x=l?n?s:d.head:h.center,r.y=l?h.center:n?s:d.head,r.height=l?h.size:void 0,r.width=l?void 0:h.size},_getStacks:function(t){var e,n,i=this._getIndexScale(),a=i._getMatchingVisibleMetas(this._type),r=i.options.stacked,o=a.length,s=[];for(e=0;e<o&&(n=a[e],(!1===r||-1===s.indexOf(n.stack)||void 0===r&&void 0===n.stack)&&s.push(n.stack),n.index!==t);++e);return s},getStackCount:function(){return this._getStacks().length},getStackIndex:function(t,e){var n=this._getStacks(t),i=void 0!==e?n.indexOf(e):-1;return-1===i?n.length-1:i},getRuler:function(){var t,e,n=this._getIndexScale(),i=[];for(t=0,e=this.getMeta().data.length;t<e;++t)i.push(n.getPixelForValue(null,t,this.index));return{pixels:i,start:n._startPixel,end:n._endPixel,stackCount:this.getStackCount(),scale:n}},calculateBarValuePixels:function(t,e,n){var i,a,r,o,s,l,u,d=this.chart,h=this._getValueScale(),c=h.isHorizontal(),f=d.data.datasets,g=h._getMatchingVisibleMetas(this._type),p=h._parseValue(f[t].data[e]),m=n.minBarLength,v=h.options.stacked,b=this.getMeta().stack,x=void 0===p.start?0:p.max>=0&&p.min>=0?p.min:p.max,y=void 0===p.start?p.end:p.max>=0&&p.min>=0?p.max-p.min:p.min-p.max,_=g.length;if(v||void 0===v&&void 0!==b)for(i=0;i<_&&(a=g[i]).index!==t;++i)a.stack===b&&(r=void 0===(u=h._parseValue(f[a.index].data[e])).start?u.end:u.min>=0&&u.max>=0?u.max:u.min,(p.min<0&&r<0||p.max>=0&&r>0)&&(x+=r));return o=h.getPixelForValue(x),l=(s=h.getPixelForValue(x+y))-o,void 0!==m&&Math.abs(l)<m&&(l=m,s=y>=0&&!c||y<0&&c?o-m:o+m),{size:l,base:o,head:s,center:s+l/2}},calculateBarIndexPixels:function(t,e,n,i){var a="flex"===i.barThickness?function(t,e,n){var i,a=e.pixels,r=a[t],o=t>0?a[t-1]:null,s=t<a.length-1?a[t+1]:null,l=n.categoryPercentage;return null===o&&(o=r-(null===s?e.end-e.start:s-r)),null===s&&(s=r+r-o),i=r-(r-Math.min(o,s))/2*l,{chunk:Math.abs(s-o)/2*l/e.stackCount,ratio:n.barPercentage,start:i}}(e,n,i):At(e,n,i),r=this.getStackIndex(t,this.getMeta().stack),o=a.start+a.chunk*r+a.chunk/2,s=Math.min(Pt(i.maxBarThickness,1/0),a.chunk*a.ratio);return{base:o-s/2,head:o+s/2,center:o,size:s}},draw:function(){var t=this.chart,e=this._getValueScale(),n=this.getMeta().data,i=this.getDataset(),a=n.length,r=0;for(V.canvas.clipArea(t.ctx,t.chartArea);r<a;++r){var o=e._parseValue(i.data[r]);isNaN(o.min)||isNaN(o.max)||n[r].draw()}V.canvas.unclipArea(t.ctx)},_resolveDataElementOptions:function(){var t=this,e=V.extend({},nt.prototype._resolveDataElementOptions.apply(t,arguments)),n=t._getIndexScale().options,i=t._getValueScale().options;return e.barPercentage=Pt(n.barPercentage,e.barPercentage),e.barThickness=Pt(n.barThickness,e.barThickness),e.categoryPercentage=Pt(n.categoryPercentage,e.categoryPercentage),e.maxBarThickness=Pt(n.maxBarThickness,e.maxBarThickness),e.minBarLength=Pt(i.minBarLength,e.minBarLength),e}}),Tt=V.valueOrDefault,It=V.options.resolve;z._set("bubble",{hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(){return""},label:function(t,e){var n=e.datasets[t.datasetIndex].label||"",i=e.datasets[t.datasetIndex].data[t.index];return n+": ("+t.xLabel+", "+t.yLabel+", "+i.r+")"}}}});var Ft=nt.extend({dataElementType:_t.Point,_dataElementOptions:["backgroundColor","borderColor","borderWidth","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth","hoverRadius","hitRadius","pointStyle","rotation"],update:function(t){var e=this,n=e.getMeta().data;V.each(n,(function(n,i){e.updateElement(n,i,t)}))},updateElement:function(t,e,n){var i=this,a=i.getMeta(),r=t.custom||{},o=i.getScaleForId(a.xAxisID),s=i.getScaleForId(a.yAxisID),l=i._resolveDataElementOptions(t,e),u=i.getDataset().data[e],d=i.index,h=n?o.getPixelForDecimal(.5):o.getPixelForValue("object"==typeof u?u:NaN,e,d),c=n?s.getBasePixel():s.getPixelForValue(u,e,d);t._xScale=o,t._yScale=s,t._options=l,t._datasetIndex=d,t._index=e,t._model={backgroundColor:l.backgroundColor,borderColor:l.borderColor,borderWidth:l.borderWidth,hitRadius:l.hitRadius,pointStyle:l.pointStyle,rotation:l.rotation,radius:n?0:l.radius,skip:r.skip||isNaN(h)||isNaN(c),x:h,y:c},t.pivot()},setHoverStyle:function(t){var e=t._model,n=t._options,i=V.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=Tt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Tt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Tt(n.hoverBorderWidth,n.borderWidth),e.radius=n.radius+n.hoverRadius},_resolveDataElementOptions:function(t,e){var n=this,i=n.chart,a=n.getDataset(),r=t.custom||{},o=a.data[e]||{},s=nt.prototype._resolveDataElementOptions.apply(n,arguments),l={chart:i,dataIndex:e,dataset:a,datasetIndex:n.index};return n._cachedDataOpts===s&&(s=V.extend({},s)),s.radius=It([r.radius,o.r,n._config.radius,i.options.elements.point.radius],l,e),s}}),Lt=V.valueOrDefault,Ot=Math.PI,Rt=2*Ot,zt=Ot/2;z._set("doughnut",{animation:{animateRotate:!0,animateScale:!1},hover:{mode:"single"},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data,o=r.datasets,s=r.labels;if(a.setAttribute("class",t.id+"-legend"),o.length)for(e=0,n=o[0].data.length;e<n;++e)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=o[0].backgroundColor[e],s[e]&&i.appendChild(document.createTextNode(s[e]));return a.outerHTML},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((function(n,i){var a=t.getDatasetMeta(0),r=a.controller.getStyle(i);return{text:n,fillStyle:r.backgroundColor,strokeStyle:r.borderColor,lineWidth:r.borderWidth,hidden:isNaN(e.datasets[0].data[i])||a.data[i].hidden,index:i}})):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n<i;++n)(a=o.getDatasetMeta(n)).data[r]&&(a.data[r].hidden=!a.data[r].hidden);o.update()}},cutoutPercentage:50,rotation:-zt,circumference:Rt,tooltips:{callbacks:{title:function(){return""},label:function(t,e){var n=e.labels[t.index],i=": "+e.datasets[t.datasetIndex].data[t.index];return V.isArray(n)?(n=n.slice())[0]+=i:n+=i,n}}}});var Nt=nt.extend({dataElementType:_t.Arc,linkScales:V.noop,_dataElementOptions:["backgroundColor","borderColor","borderWidth","borderAlign","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth"],getRingIndex:function(t){for(var e=0,n=0;n<t;++n)this.chart.isDatasetVisible(n)&&++e;return e},update:function(t){var e,n,i,a,r=this,o=r.chart,s=o.chartArea,l=o.options,u=1,d=1,h=0,c=0,f=r.getMeta(),g=f.data,p=l.cutoutPercentage/100||0,m=l.circumference,v=r._getRingWeight(r.index);if(m<Rt){var b=l.rotation%Rt,x=(b+=b>=Ot?-Rt:b<-Ot?Rt:0)+m,y=Math.cos(b),_=Math.sin(b),k=Math.cos(x),w=Math.sin(x),M=b<=0&&x>=0||x>=Rt,S=b<=zt&&x>=zt||x>=Rt+zt,C=b<=-zt&&x>=-zt||x>=Ot+zt,P=b===-Ot||x>=Ot?-1:Math.min(y,y*p,k,k*p),A=C?-1:Math.min(_,_*p,w,w*p),D=M?1:Math.max(y,y*p,k,k*p),T=S?1:Math.max(_,_*p,w,w*p);u=(D-P)/2,d=(T-A)/2,h=-(D+P)/2,c=-(T+A)/2}for(i=0,a=g.length;i<a;++i)g[i]._options=r._resolveDataElementOptions(g[i],i);for(o.borderWidth=r.getMaxBorderWidth(),e=(s.right-s.left-o.borderWidth)/u,n=(s.bottom-s.top-o.borderWidth)/d,o.outerRadius=Math.max(Math.min(e,n)/2,0),o.innerRadius=Math.max(o.outerRadius*p,0),o.radiusLength=(o.outerRadius-o.innerRadius)/(r._getVisibleDatasetWeightTotal()||1),o.offsetX=h*o.outerRadius,o.offsetY=c*o.outerRadius,f.total=r.calculateTotal(),r.outerRadius=o.outerRadius-o.radiusLength*r._getRingWeightOffset(r.index),r.innerRadius=Math.max(r.outerRadius-o.radiusLength*v,0),i=0,a=g.length;i<a;++i)r.updateElement(g[i],i,t)},updateElement:function(t,e,n){var i=this,a=i.chart,r=a.chartArea,o=a.options,s=o.animation,l=(r.left+r.right)/2,u=(r.top+r.bottom)/2,d=o.rotation,h=o.rotation,c=i.getDataset(),f=n&&s.animateRotate?0:t.hidden?0:i.calculateCircumference(c.data[e])*(o.circumference/Rt),g=n&&s.animateScale?0:i.innerRadius,p=n&&s.animateScale?0:i.outerRadius,m=t._options||{};V.extend(t,{_datasetIndex:i.index,_index:e,_model:{backgroundColor:m.backgroundColor,borderColor:m.borderColor,borderWidth:m.borderWidth,borderAlign:m.borderAlign,x:l+a.offsetX,y:u+a.offsetY,startAngle:d,endAngle:h,circumference:f,outerRadius:p,innerRadius:g,label:V.valueAtIndexOrDefault(c.label,e,a.data.labels[e])}});var v=t._model;n&&s.animateRotate||(v.startAngle=0===e?o.rotation:i.getMeta().data[e-1]._model.endAngle,v.endAngle=v.startAngle+v.circumference),t.pivot()},calculateTotal:function(){var t,e=this.getDataset(),n=this.getMeta(),i=0;return V.each(n.data,(function(n,a){t=e.data[a],isNaN(t)||n.hidden||(i+=Math.abs(t))})),i},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?Rt*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,n,i,a,r,o,s,l,u=0,d=this.chart;if(!t)for(e=0,n=d.data.datasets.length;e<n;++e)if(d.isDatasetVisible(e)){t=(i=d.getDatasetMeta(e)).data,e!==this.index&&(r=i.controller);break}if(!t)return 0;for(e=0,n=t.length;e<n;++e)a=t[e],r?(r._configure(),o=r._resolveDataElementOptions(a,e)):o=a._options,"inner"!==o.borderAlign&&(s=o.borderWidth,u=(l=o.hoverBorderWidth)>(u=s>u?s:u)?l:u);return u},setHoverStyle:function(t){var e=t._model,n=t._options,i=V.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=Lt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Lt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Lt(n.hoverBorderWidth,n.borderWidth)},_getRingWeightOffset:function(t){for(var e=0,n=0;n<t;++n)this.chart.isDatasetVisible(n)&&(e+=this._getRingWeight(n));return e},_getRingWeight:function(t){return Math.max(Lt(this.chart.data.datasets[t].weight,1),0)},_getVisibleDatasetWeightTotal:function(){return this._getRingWeightOffset(this.chart.data.datasets.length)}});z._set("horizontalBar",{hover:{mode:"index",axis:"y"},scales:{xAxes:[{type:"linear",position:"bottom"}],yAxes:[{type:"category",position:"left",offset:!0,gridLines:{offsetGridLines:!0}}]},elements:{rectangle:{borderSkipped:"left"}},tooltips:{mode:"index",axis:"y"}}),z._set("global",{datasets:{horizontalBar:{categoryPercentage:.8,barPercentage:.9}}});var Bt=Dt.extend({_getValueScaleId:function(){return this.getMeta().xAxisID},_getIndexScaleId:function(){return this.getMeta().yAxisID}}),Et=V.valueOrDefault,Wt=V.options.resolve,Vt=V.canvas._isPointInArea;function Ht(t,e){var n=t&&t.options.ticks||{},i=n.reverse,a=void 0===n.min?e:0,r=void 0===n.max?e:0;return{start:i?r:a,end:i?a:r}}function jt(t,e,n){var i=n/2,a=Ht(t,i),r=Ht(e,i);return{top:r.end,right:a.end,bottom:r.start,left:a.start}}function qt(t){var e,n,i,a;return V.isObject(t)?(e=t.top,n=t.right,i=t.bottom,a=t.left):e=n=i=a=t,{top:e,right:n,bottom:i,left:a}}z._set("line",{showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}});var Ut=nt.extend({datasetElementType:_t.Line,dataElementType:_t.Point,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth","cubicInterpolationMode","fill"],_dataElementOptions:{backgroundColor:"pointBackgroundColor",borderColor:"pointBorderColor",borderWidth:"pointBorderWidth",hitRadius:"pointHitRadius",hoverBackgroundColor:"pointHoverBackgroundColor",hoverBorderColor:"pointHoverBorderColor",hoverBorderWidth:"pointHoverBorderWidth",hoverRadius:"pointHoverRadius",pointStyle:"pointStyle",radius:"pointRadius",rotation:"pointRotation"},update:function(t){var e,n,i=this,a=i.getMeta(),r=a.dataset,o=a.data||[],s=i.chart.options,l=i._config,u=i._showLine=Et(l.showLine,s.showLines);for(i._xScale=i.getScaleForId(a.xAxisID),i._yScale=i.getScaleForId(a.yAxisID),u&&(void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),r._scale=i._yScale,r._datasetIndex=i.index,r._children=o,r._model=i._resolveDatasetElementOptions(r),r.pivot()),e=0,n=o.length;e<n;++e)i.updateElement(o[e],e,t);for(u&&0!==r._model.tension&&i.updateBezierControlPoints(),e=0,n=o.length;e<n;++e)o[e].pivot()},updateElement:function(t,e,n){var i,a,r=this,o=r.getMeta(),s=t.custom||{},l=r.getDataset(),u=r.index,d=l.data[e],h=r._xScale,c=r._yScale,f=o.dataset._model,g=r._resolveDataElementOptions(t,e);i=h.getPixelForValue("object"==typeof d?d:NaN,e,u),a=n?c.getBasePixel():r.calculatePointY(d,e,u),t._xScale=h,t._yScale=c,t._options=g,t._datasetIndex=u,t._index=e,t._model={x:i,y:a,skip:s.skip||isNaN(i)||isNaN(a),radius:g.radius,pointStyle:g.pointStyle,rotation:g.rotation,backgroundColor:g.backgroundColor,borderColor:g.borderColor,borderWidth:g.borderWidth,tension:Et(s.tension,f?f.tension:0),steppedLine:!!f&&f.steppedLine,hitRadius:g.hitRadius}},_resolveDatasetElementOptions:function(t){var e=this,n=e._config,i=t.custom||{},a=e.chart.options,r=a.elements.line,o=nt.prototype._resolveDatasetElementOptions.apply(e,arguments);return o.spanGaps=Et(n.spanGaps,a.spanGaps),o.tension=Et(n.lineTension,r.tension),o.steppedLine=Wt([i.steppedLine,n.steppedLine,r.stepped]),o.clip=qt(Et(n.clip,jt(e._xScale,e._yScale,o.borderWidth))),o},calculatePointY:function(t,e,n){var i,a,r,o,s,l,u,d=this.chart,h=this._yScale,c=0,f=0;if(h.options.stacked){for(s=+h.getRightValue(t),u=(l=d._getSortedVisibleDatasetMetas()).length,i=0;i<u&&(r=l[i]).index!==n;++i)a=d.data.datasets[r.index],"line"===r.type&&r.yAxisID===h.id&&((o=+h.getRightValue(a.data[e]))<0?f+=o||0:c+=o||0);return s<0?h.getPixelForValue(f+s):h.getPixelForValue(c+s)}return h.getPixelForValue(t)},updateBezierControlPoints:function(){var t,e,n,i,a=this.chart,r=this.getMeta(),o=r.dataset._model,s=a.chartArea,l=r.data||[];function u(t,e,n){return Math.max(Math.min(t,n),e)}if(o.spanGaps&&(l=l.filter((function(t){return!t._model.skip}))),"monotone"===o.cubicInterpolationMode)V.splineCurveMonotone(l);else for(t=0,e=l.length;t<e;++t)n=l[t]._model,i=V.splineCurve(V.previousItem(l,t)._model,n,V.nextItem(l,t)._model,o.tension),n.controlPointPreviousX=i.previous.x,n.controlPointPreviousY=i.previous.y,n.controlPointNextX=i.next.x,n.controlPointNextY=i.next.y;if(a.options.elements.line.capBezierPoints)for(t=0,e=l.length;t<e;++t)n=l[t]._model,Vt(n,s)&&(t>0&&Vt(l[t-1]._model,s)&&(n.controlPointPreviousX=u(n.controlPointPreviousX,s.left,s.right),n.controlPointPreviousY=u(n.controlPointPreviousY,s.top,s.bottom)),t<l.length-1&&Vt(l[t+1]._model,s)&&(n.controlPointNextX=u(n.controlPointNextX,s.left,s.right),n.controlPointNextY=u(n.controlPointNextY,s.top,s.bottom)))},draw:function(){var t,e=this.chart,n=this.getMeta(),i=n.data||[],a=e.chartArea,r=e.canvas,o=0,s=i.length;for(this._showLine&&(t=n.dataset._model.clip,V.canvas.clipArea(e.ctx,{left:!1===t.left?0:a.left-t.left,right:!1===t.right?r.width:a.right+t.right,top:!1===t.top?0:a.top-t.top,bottom:!1===t.bottom?r.height:a.bottom+t.bottom}),n.dataset.draw(),V.canvas.unclipArea(e.ctx));o<s;++o)i[o].draw(a)},setHoverStyle:function(t){var e=t._model,n=t._options,i=V.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=Et(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Et(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Et(n.hoverBorderWidth,n.borderWidth),e.radius=Et(n.hoverRadius,n.radius)}}),Yt=V.options.resolve;z._set("polarArea",{scale:{type:"radialLinear",angleLines:{display:!1},gridLines:{circular:!0},pointLabels:{display:!1},ticks:{beginAtZero:!0}},animation:{animateRotate:!0,animateScale:!0},startAngle:-.5*Math.PI,legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data,o=r.datasets,s=r.labels;if(a.setAttribute("class",t.id+"-legend"),o.length)for(e=0,n=o[0].data.length;e<n;++e)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=o[0].backgroundColor[e],s[e]&&i.appendChild(document.createTextNode(s[e]));return a.outerHTML},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((function(n,i){var a=t.getDatasetMeta(0),r=a.controller.getStyle(i);return{text:n,fillStyle:r.backgroundColor,strokeStyle:r.borderColor,lineWidth:r.borderWidth,hidden:isNaN(e.datasets[0].data[i])||a.data[i].hidden,index:i}})):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n<i;++n)(a=o.getDatasetMeta(n)).data[r].hidden=!a.data[r].hidden;o.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}});var Gt=nt.extend({dataElementType:_t.Arc,linkScales:V.noop,_dataElementOptions:["backgroundColor","borderColor","borderWidth","borderAlign","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth"],_getIndexScaleId:function(){return this.chart.scale.id},_getValueScaleId:function(){return this.chart.scale.id},update:function(t){var e,n,i,a=this,r=a.getDataset(),o=a.getMeta(),s=a.chart.options.startAngle||0,l=a._starts=[],u=a._angles=[],d=o.data;for(a._updateRadius(),o.count=a.countVisibleElements(),e=0,n=r.data.length;e<n;e++)l[e]=s,i=a._computeAngle(e),u[e]=i,s+=i;for(e=0,n=d.length;e<n;++e)d[e]._options=a._resolveDataElementOptions(d[e],e),a.updateElement(d[e],e,t)},_updateRadius:function(){var t=this,e=t.chart,n=e.chartArea,i=e.options,a=Math.min(n.right-n.left,n.bottom-n.top);e.outerRadius=Math.max(a/2,0),e.innerRadius=Math.max(i.cutoutPercentage?e.outerRadius/100*i.cutoutPercentage:1,0),e.radiusLength=(e.outerRadius-e.innerRadius)/e.getVisibleDatasetCount(),t.outerRadius=e.outerRadius-e.radiusLength*t.index,t.innerRadius=t.outerRadius-e.radiusLength},updateElement:function(t,e,n){var i=this,a=i.chart,r=i.getDataset(),o=a.options,s=o.animation,l=a.scale,u=a.data.labels,d=l.xCenter,h=l.yCenter,c=o.startAngle,f=t.hidden?0:l.getDistanceFromCenterForValue(r.data[e]),g=i._starts[e],p=g+(t.hidden?0:i._angles[e]),m=s.animateScale?0:l.getDistanceFromCenterForValue(r.data[e]),v=t._options||{};V.extend(t,{_datasetIndex:i.index,_index:e,_scale:l,_model:{backgroundColor:v.backgroundColor,borderColor:v.borderColor,borderWidth:v.borderWidth,borderAlign:v.borderAlign,x:d,y:h,innerRadius:0,outerRadius:n?m:f,startAngle:n&&s.animateRotate?c:g,endAngle:n&&s.animateRotate?c:p,label:V.valueAtIndexOrDefault(u,e,u[e])}}),t.pivot()},countVisibleElements:function(){var t=this.getDataset(),e=this.getMeta(),n=0;return V.each(e.data,(function(e,i){isNaN(t.data[i])||e.hidden||n++})),n},setHoverStyle:function(t){var e=t._model,n=t._options,i=V.getHoverColor,a=V.valueOrDefault;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=a(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=a(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=a(n.hoverBorderWidth,n.borderWidth)},_computeAngle:function(t){var e=this,n=this.getMeta().count,i=e.getDataset(),a=e.getMeta();if(isNaN(i.data[t])||a.data[t].hidden)return 0;var r={chart:e.chart,dataIndex:t,dataset:i,datasetIndex:e.index};return Yt([e.chart.options.elements.arc.angle,2*Math.PI/n],r,t)}});z._set("pie",V.clone(z.doughnut)),z._set("pie",{cutoutPercentage:0});var Xt=Nt,Kt=V.valueOrDefault;z._set("radar",{spanGaps:!1,scale:{type:"radialLinear"},elements:{line:{fill:"start",tension:0}}});var Zt=nt.extend({datasetElementType:_t.Line,dataElementType:_t.Point,linkScales:V.noop,_datasetElementOptions:["backgroundColor","borderWidth","borderColor","borderCapStyle","borderDash","borderDashOffset","borderJoinStyle","fill"],_dataElementOptions:{backgroundColor:"pointBackgroundColor",borderColor:"pointBorderColor",borderWidth:"pointBorderWidth",hitRadius:"pointHitRadius",hoverBackgroundColor:"pointHoverBackgroundColor",hoverBorderColor:"pointHoverBorderColor",hoverBorderWidth:"pointHoverBorderWidth",hoverRadius:"pointHoverRadius",pointStyle:"pointStyle",radius:"pointRadius",rotation:"pointRotation"},_getIndexScaleId:function(){return this.chart.scale.id},_getValueScaleId:function(){return this.chart.scale.id},update:function(t){var e,n,i=this,a=i.getMeta(),r=a.dataset,o=a.data||[],s=i.chart.scale,l=i._config;for(void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),r._scale=s,r._datasetIndex=i.index,r._children=o,r._loop=!0,r._model=i._resolveDatasetElementOptions(r),r.pivot(),e=0,n=o.length;e<n;++e)i.updateElement(o[e],e,t);for(i.updateBezierControlPoints(),e=0,n=o.length;e<n;++e)o[e].pivot()},updateElement:function(t,e,n){var i=this,a=t.custom||{},r=i.getDataset(),o=i.chart.scale,s=o.getPointPositionForValue(e,r.data[e]),l=i._resolveDataElementOptions(t,e),u=i.getMeta().dataset._model,d=n?o.xCenter:s.x,h=n?o.yCenter:s.y;t._scale=o,t._options=l,t._datasetIndex=i.index,t._index=e,t._model={x:d,y:h,skip:a.skip||isNaN(d)||isNaN(h),radius:l.radius,pointStyle:l.pointStyle,rotation:l.rotation,backgroundColor:l.backgroundColor,borderColor:l.borderColor,borderWidth:l.borderWidth,tension:Kt(a.tension,u?u.tension:0),hitRadius:l.hitRadius}},_resolveDatasetElementOptions:function(){var t=this,e=t._config,n=t.chart.options,i=nt.prototype._resolveDatasetElementOptions.apply(t,arguments);return i.spanGaps=Kt(e.spanGaps,n.spanGaps),i.tension=Kt(e.lineTension,n.elements.line.tension),i},updateBezierControlPoints:function(){var t,e,n,i,a=this.getMeta(),r=this.chart.chartArea,o=a.data||[];function s(t,e,n){return Math.max(Math.min(t,n),e)}for(a.dataset._model.spanGaps&&(o=o.filter((function(t){return!t._model.skip}))),t=0,e=o.length;t<e;++t)n=o[t]._model,i=V.splineCurve(V.previousItem(o,t,!0)._model,n,V.nextItem(o,t,!0)._model,n.tension),n.controlPointPreviousX=s(i.previous.x,r.left,r.right),n.controlPointPreviousY=s(i.previous.y,r.top,r.bottom),n.controlPointNextX=s(i.next.x,r.left,r.right),n.controlPointNextY=s(i.next.y,r.top,r.bottom)},setHoverStyle:function(t){var e=t._model,n=t._options,i=V.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=Kt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Kt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Kt(n.hoverBorderWidth,n.borderWidth),e.radius=Kt(n.hoverRadius,n.radius)}});z._set("scatter",{hover:{mode:"single"},scales:{xAxes:[{id:"x-axis-1",type:"linear",position:"bottom"}],yAxes:[{id:"y-axis-1",type:"linear",position:"left"}]},tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}}),z._set("global",{datasets:{scatter:{showLine:!1}}});var $t={bar:Dt,bubble:Ft,doughnut:Nt,horizontalBar:Bt,line:Ut,polarArea:Gt,pie:Xt,radar:Zt,scatter:Ut};function Jt(t,e){return t.native?{x:t.x,y:t.y}:V.getRelativePosition(t,e)}function Qt(t,e){var n,i,a,r,o,s,l=t._getSortedVisibleDatasetMetas();for(i=0,r=l.length;i<r;++i)for(a=0,o=(n=l[i].data).length;a<o;++a)(s=n[a])._view.skip||e(s)}function te(t,e){var n=[];return Qt(t,(function(t){t.inRange(e.x,e.y)&&n.push(t)})),n}function ee(t,e,n,i){var a=Number.POSITIVE_INFINITY,r=[];return Qt(t,(function(t){if(!n||t.inRange(e.x,e.y)){var o=t.getCenterPoint(),s=i(e,o);s<a?(r=[t],a=s):s===a&&r.push(t)}})),r}function ne(t){var e=-1!==t.indexOf("x"),n=-1!==t.indexOf("y");return function(t,i){var a=e?Math.abs(t.x-i.x):0,r=n?Math.abs(t.y-i.y):0;return Math.sqrt(Math.pow(a,2)+Math.pow(r,2))}}function ie(t,e,n){var i=Jt(e,t);n.axis=n.axis||"x";var a=ne(n.axis),r=n.intersect?te(t,i):ee(t,i,!1,a),o=[];return r.length?(t._getSortedVisibleDatasetMetas().forEach((function(t){var e=t.data[r[0]._index];e&&!e._view.skip&&o.push(e)})),o):[]}var ae={modes:{single:function(t,e){var n=Jt(e,t),i=[];return Qt(t,(function(t){if(t.inRange(n.x,n.y))return i.push(t),i})),i.slice(0,1)},label:ie,index:ie,dataset:function(t,e,n){var i=Jt(e,t);n.axis=n.axis||"xy";var a=ne(n.axis),r=n.intersect?te(t,i):ee(t,i,!1,a);return r.length>0&&(r=t.getDatasetMeta(r[0]._datasetIndex).data),r},"x-axis":function(t,e){return ie(t,e,{intersect:!1})},point:function(t,e){return te(t,Jt(e,t))},nearest:function(t,e,n){var i=Jt(e,t);n.axis=n.axis||"xy";var a=ne(n.axis);return ee(t,i,n.intersect,a)},x:function(t,e,n){var i=Jt(e,t),a=[],r=!1;return Qt(t,(function(t){t.inXRange(i.x)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a},y:function(t,e,n){var i=Jt(e,t),a=[],r=!1;return Qt(t,(function(t){t.inYRange(i.y)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a}}},re=V.extend;function oe(t,e){return V.where(t,(function(t){return t.pos===e}))}function se(t,e){return t.sort((function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i.index-a.index:i.weight-a.weight}))}function le(t,e,n,i){return Math.max(t[n],e[n])+Math.max(t[i],e[i])}function ue(t,e,n){var i,a,r=n.box,o=t.maxPadding;if(n.size&&(t[n.pos]-=n.size),n.size=n.horizontal?r.height:r.width,t[n.pos]+=n.size,r.getPadding){var s=r.getPadding();o.top=Math.max(o.top,s.top),o.left=Math.max(o.left,s.left),o.bottom=Math.max(o.bottom,s.bottom),o.right=Math.max(o.right,s.right)}if(i=e.outerWidth-le(o,t,"left","right"),a=e.outerHeight-le(o,t,"top","bottom"),i!==t.w||a!==t.h)return t.w=i,t.h=a,n.horizontal?i!==t.w:a!==t.h}function de(t,e){var n=e.maxPadding;function i(t){var i={left:0,top:0,right:0,bottom:0};return t.forEach((function(t){i[t]=Math.max(e[t],n[t])})),i}return i(t?["left","right"]:["top","bottom"])}function he(t,e,n){var i,a,r,o,s,l,u=[];for(i=0,a=t.length;i<a;++i)(o=(r=t[i]).box).update(r.width||e.w,r.height||e.h,de(r.horizontal,e)),ue(e,n,r)&&(l=!0,u.length&&(s=!0)),o.fullWidth||u.push(r);return s&&he(u,e,n)||l}function ce(t,e,n){var i,a,r,o,s=n.padding,l=e.x,u=e.y;for(i=0,a=t.length;i<a;++i)o=(r=t[i]).box,r.horizontal?(o.left=o.fullWidth?s.left:e.left,o.right=o.fullWidth?n.outerWidth-s.right:e.left+e.w,o.top=u,o.bottom=u+o.height,o.width=o.right-o.left,u=o.bottom):(o.left=l,o.right=l+o.width,o.top=e.top,o.bottom=e.top+e.h,o.height=o.bottom-o.top,l=o.right);e.x=l,e.y=u}z._set("global",{layout:{padding:{top:0,right:0,bottom:0,left:0}}});var fe,ge={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),e.fullWidth=e.fullWidth||!1,e.position=e.position||"top",e.weight=e.weight||0,e._layers=e._layers||function(){return[{z:0,draw:function(){e.draw.apply(e,arguments)}}]},t.boxes.push(e)},removeBox:function(t,e){var n=t.boxes?t.boxes.indexOf(e):-1;-1!==n&&t.boxes.splice(n,1)},configure:function(t,e,n){for(var i,a=["fullWidth","position","weight"],r=a.length,o=0;o<r;++o)i=a[o],n.hasOwnProperty(i)&&(e[i]=n[i])},update:function(t,e,n){if(t){var i=t.options.layout||{},a=V.options.toPadding(i.padding),r=e-a.width,o=n-a.height,s=function(t){var e=function(t){var e,n,i,a=[];for(e=0,n=(t||[]).length;e<n;++e)i=t[e],a.push({index:e,box:i,pos:i.position,horizontal:i.isHorizontal(),weight:i.weight});return a}(t),n=se(oe(e,"left"),!0),i=se(oe(e,"right")),a=se(oe(e,"top"),!0),r=se(oe(e,"bottom"));return{leftAndTop:n.concat(a),rightAndBottom:i.concat(r),chartArea:oe(e,"chartArea"),vertical:n.concat(i),horizontal:a.concat(r)}}(t.boxes),l=s.vertical,u=s.horizontal,d=Object.freeze({outerWidth:e,outerHeight:n,padding:a,availableWidth:r,vBoxMaxWidth:r/2/l.length,hBoxMaxHeight:o/2}),h=re({maxPadding:re({},a),w:r,h:o,x:a.left,y:a.top},a);!function(t,e){var n,i,a;for(n=0,i=t.length;n<i;++n)(a=t[n]).width=a.horizontal?a.box.fullWidth&&e.availableWidth:e.vBoxMaxWidth,a.height=a.horizontal&&e.hBoxMaxHeight}(l.concat(u),d),he(l,h,d),he(u,h,d)&&he(l,h,d),function(t){var e=t.maxPadding;function n(n){var i=Math.max(e[n]-t[n],0);return t[n]+=i,i}t.y+=n("top"),t.x+=n("left"),n("right"),n("bottom")}(h),ce(s.leftAndTop,h,d),h.x+=h.w,h.y+=h.h,ce(s.rightAndBottom,h,d),t.chartArea={left:h.left,top:h.top,right:h.left+h.w,bottom:h.top+h.h},V.each(s.chartArea,(function(e){var n=e.box;re(n,t.chartArea),n.update(h.w,h.h)}))}}},pe=(fe=Object.freeze({__proto__:null,default:"@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}"}))&&fe.default||fe,me="$chartjs",ve="chartjs-size-monitor",be="chartjs-render-monitor",xe="chartjs-render-animation",ye=["animationstart","webkitAnimationStart"],_e={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function ke(t,e){var n=V.getStyle(t,e),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?Number(i[1]):void 0}var we=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};function Me(t,e,n){t.addEventListener(e,n,we)}function Se(t,e,n){t.removeEventListener(e,n,we)}function Ce(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function Pe(t){var e=document.createElement("div");return e.className=t||"",e}function Ae(t,e,n){var i,a,r,o,s=t[me]||(t[me]={}),l=s.resizer=function(t){var e=Pe(ve),n=Pe(ve+"-expand"),i=Pe(ve+"-shrink");n.appendChild(Pe()),i.appendChild(Pe()),e.appendChild(n),e.appendChild(i),e._reset=function(){n.scrollLeft=1e6,n.scrollTop=1e6,i.scrollLeft=1e6,i.scrollTop=1e6};var a=function(){e._reset(),t()};return Me(n,"scroll",a.bind(n,"expand")),Me(i,"scroll",a.bind(i,"shrink")),e}((i=function(){if(s.resizer){var i=n.options.maintainAspectRatio&&t.parentNode,a=i?i.clientWidth:0;e(Ce("resize",n)),i&&i.clientWidth<a&&n.canvas&&e(Ce("resize",n))}},r=!1,o=[],function(){o=Array.prototype.slice.call(arguments),a=a||this,r||(r=!0,V.requestAnimFrame.call(window,(function(){r=!1,i.apply(a,o)})))}));!function(t,e){var n=t[me]||(t[me]={}),i=n.renderProxy=function(t){t.animationName===xe&&e()};V.each(ye,(function(e){Me(t,e,i)})),n.reflow=!!t.offsetParent,t.classList.add(be)}(t,(function(){if(s.resizer){var e=t.parentNode;e&&e!==l.parentNode&&e.insertBefore(l,e.firstChild),l._reset()}}))}function De(t){var e=t[me]||{},n=e.resizer;delete e.resizer,function(t){var e=t[me]||{},n=e.renderProxy;n&&(V.each(ye,(function(e){Se(t,e,n)})),delete e.renderProxy),t.classList.remove(be)}(t),n&&n.parentNode&&n.parentNode.removeChild(n)}var Te={disableCSSInjection:!1,_enabled:"undefined"!=typeof window&&"undefined"!=typeof document,_ensureLoaded:function(t){if(!this.disableCSSInjection){var e=t.getRootNode?t.getRootNode():document;!function(t,e){var n=t[me]||(t[me]={});if(!n.containsStyles){n.containsStyles=!0,e="/* Chart.js */\n"+e;var i=document.createElement("style");i.setAttribute("type","text/css"),i.appendChild(document.createTextNode(e)),t.appendChild(i)}}(e.host?e:document.head,pe)}},acquireContext:function(t,e){"string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas);var n=t&&t.getContext&&t.getContext("2d");return n&&n.canvas===t?(this._ensureLoaded(t),function(t,e){var n=t.style,i=t.getAttribute("height"),a=t.getAttribute("width");if(t[me]={initial:{height:i,width:a,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",null===a||""===a){var r=ke(t,"width");void 0!==r&&(t.width=r)}if(null===i||""===i)if(""===t.style.height)t.height=t.width/(e.options.aspectRatio||2);else{var o=ke(t,"height");void 0!==r&&(t.height=o)}}(t,e),n):null},releaseContext:function(t){var e=t.canvas;if(e[me]){var n=e[me].initial;["height","width"].forEach((function(t){var i=n[t];V.isNullOrUndef(i)?e.removeAttribute(t):e.setAttribute(t,i)})),V.each(n.style||{},(function(t,n){e.style[n]=t})),e.width=e.width,delete e[me]}},addEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=n[me]||(n[me]={});Me(i,e,(a.proxies||(a.proxies={}))[t.id+"_"+e]=function(e){n(function(t,e){var n=_e[t.type]||t.type,i=V.getRelativePosition(t,e);return Ce(n,e,i.x,i.y,t)}(e,t))})}else Ae(i,n,t)},removeEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=((n[me]||{}).proxies||{})[t.id+"_"+e];a&&Se(i,e,a)}else De(i)}};V.addEvent=Me,V.removeEvent=Se;var Ie=Te._enabled?Te:{acquireContext:function(t){return t&&t.canvas&&(t=t.canvas),t&&t.getContext("2d")||null}},Fe=V.extend({initialize:function(){},acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},Ie);z._set("global",{plugins:{}});var Le={_plugins:[],_cacheId:0,register:function(t){var e=this._plugins;[].concat(t).forEach((function(t){-1===e.indexOf(t)&&e.push(t)})),this._cacheId++},unregister:function(t){var e=this._plugins;[].concat(t).forEach((function(t){var n=e.indexOf(t);-1!==n&&e.splice(n,1)})),this._cacheId++},clear:function(){this._plugins=[],this._cacheId++},count:function(){return this._plugins.length},getAll:function(){return this._plugins},notify:function(t,e,n){var i,a,r,o,s,l=this.descriptors(t),u=l.length;for(i=0;i<u;++i)if("function"==typeof(s=(r=(a=l[i]).plugin)[e])&&((o=[t].concat(n||[])).push(a.options),!1===s.apply(r,o)))return!1;return!0},descriptors:function(t){var e=t.$plugins||(t.$plugins={});if(e.id===this._cacheId)return e.descriptors;var n=[],i=[],a=t&&t.config||{},r=a.options&&a.options.plugins||{};return this._plugins.concat(a.plugins||[]).forEach((function(t){if(-1===n.indexOf(t)){var e=t.id,a=r[e];!1!==a&&(!0===a&&(a=V.clone(z.global.plugins[e])),n.push(t),i.push({plugin:t,options:a||{}}))}})),e.descriptors=i,e.id=this._cacheId,i},_invalidate:function(t){delete t.$plugins}},Oe={constructors:{},defaults:{},registerScaleType:function(t,e,n){this.constructors[t]=e,this.defaults[t]=V.clone(n)},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0},getScaleDefaults:function(t){return this.defaults.hasOwnProperty(t)?V.merge({},[z.scale,this.defaults[t]]):{}},updateScaleDefaults:function(t,e){this.defaults.hasOwnProperty(t)&&(this.defaults[t]=V.extend(this.defaults[t],e))},addScalesToLayout:function(t){V.each(t.scales,(function(e){e.fullWidth=e.options.fullWidth,e.position=e.options.position,e.weight=e.options.weight,ge.addBox(t,e)}))}},Re=V.valueOrDefault,ze=V.rtl.getRtlAdapter;z._set("global",{tooltips:{enabled:!0,custom:null,mode:"nearest",position:"average",intersect:!0,backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,caretPadding:2,caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,callbacks:{beforeTitle:V.noop,title:function(t,e){var n="",i=e.labels,a=i?i.length:0;if(t.length>0){var r=t[0];r.label?n=r.label:r.xLabel?n=r.xLabel:a>0&&r.index<a&&(n=i[r.index])}return n},afterTitle:V.noop,beforeBody:V.noop,beforeLabel:V.noop,label:function(t,e){var n=e.datasets[t.datasetIndex].label||"";return n&&(n+=": "),V.isNullOrUndef(t.value)?n+=t.yLabel:n+=t.value,n},labelColor:function(t,e){var n=e.getDatasetMeta(t.datasetIndex).data[t.index]._view;return{borderColor:n.borderColor,backgroundColor:n.backgroundColor}},labelTextColor:function(){return this._options.bodyFontColor},afterLabel:V.noop,afterBody:V.noop,beforeFooter:V.noop,footer:V.noop,afterFooter:V.noop}}});var Ne={average:function(t){if(!t.length)return!1;var e,n,i=0,a=0,r=0;for(e=0,n=t.length;e<n;++e){var o=t[e];if(o&&o.hasValue()){var s=o.tooltipPosition();i+=s.x,a+=s.y,++r}}return{x:i/r,y:a/r}},nearest:function(t,e){var n,i,a,r=e.x,o=e.y,s=Number.POSITIVE_INFINITY;for(n=0,i=t.length;n<i;++n){var l=t[n];if(l&&l.hasValue()){var u=l.getCenterPoint(),d=V.distanceBetweenPoints(e,u);d<s&&(s=d,a=l)}}if(a){var h=a.tooltipPosition();r=h.x,o=h.y}return{x:r,y:o}}};function Be(t,e){return e&&(V.isArray(e)?Array.prototype.push.apply(t,e):t.push(e)),t}function Ee(t){return("string"==typeof t||t instanceof String)&&t.indexOf("\n")>-1?t.split("\n"):t}function We(t){var e=z.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,rtl:t.rtl,textDirection:t.textDirection,bodyFontColor:t.bodyFontColor,_bodyFontFamily:Re(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:Re(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:Re(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:Re(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:Re(t.titleFontStyle,e.defaultFontStyle),titleFontSize:Re(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:Re(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:Re(t.footerFontStyle,e.defaultFontStyle),footerFontSize:Re(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function Ve(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function He(t){return Be([],Ee(t))}var je=X.extend({initialize:function(){this._model=We(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options,n=e.callbacks,i=n.beforeTitle.apply(t,arguments),a=n.title.apply(t,arguments),r=n.afterTitle.apply(t,arguments),o=[];return o=Be(o,Ee(i)),o=Be(o,Ee(a)),o=Be(o,Ee(r))},getBeforeBody:function(){return He(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var n=this,i=n._options.callbacks,a=[];return V.each(t,(function(t){var r={before:[],lines:[],after:[]};Be(r.before,Ee(i.beforeLabel.call(n,t,e))),Be(r.lines,i.label.call(n,t,e)),Be(r.after,Ee(i.afterLabel.call(n,t,e))),a.push(r)})),a},getAfterBody:function(){return He(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this,e=t._options.callbacks,n=e.beforeFooter.apply(t,arguments),i=e.footer.apply(t,arguments),a=e.afterFooter.apply(t,arguments),r=[];return r=Be(r,Ee(n)),r=Be(r,Ee(i)),r=Be(r,Ee(a))},update:function(t){var e,n,i,a,r,o,s,l,u,d,h=this,c=h._options,f=h._model,g=h._model=We(c),p=h._active,m=h._data,v={xAlign:f.xAlign,yAlign:f.yAlign},b={x:f.x,y:f.y},x={width:f.width,height:f.height},y={x:f.caretX,y:f.caretY};if(p.length){g.opacity=1;var _=[],k=[];y=Ne[c.position].call(h,p,h._eventPosition);var w=[];for(e=0,n=p.length;e<n;++e)w.push((i=p[e],a=void 0,r=void 0,o=void 0,s=void 0,l=void 0,u=void 0,d=void 0,a=i._xScale,r=i._yScale||i._scale,o=i._index,s=i._datasetIndex,l=i._chart.getDatasetMeta(s).controller,u=l._getIndexScale(),d=l._getValueScale(),{xLabel:a?a.getLabelForIndex(o,s):"",yLabel:r?r.getLabelForIndex(o,s):"",label:u?""+u.getLabelForIndex(o,s):"",value:d?""+d.getLabelForIndex(o,s):"",index:o,datasetIndex:s,x:i._model.x,y:i._model.y}));c.filter&&(w=w.filter((function(t){return c.filter(t,m)}))),c.itemSort&&(w=w.sort((function(t,e){return c.itemSort(t,e,m)}))),V.each(w,(function(t){_.push(c.callbacks.labelColor.call(h,t,h._chart)),k.push(c.callbacks.labelTextColor.call(h,t,h._chart))})),g.title=h.getTitle(w,m),g.beforeBody=h.getBeforeBody(w,m),g.body=h.getBody(w,m),g.afterBody=h.getAfterBody(w,m),g.footer=h.getFooter(w,m),g.x=y.x,g.y=y.y,g.caretPadding=c.caretPadding,g.labelColors=_,g.labelTextColors=k,g.dataPoints=w,x=function(t,e){var n=t._chart.ctx,i=2*e.yPadding,a=0,r=e.body,o=r.reduce((function(t,e){return t+e.before.length+e.lines.length+e.after.length}),0);o+=e.beforeBody.length+e.afterBody.length;var s=e.title.length,l=e.footer.length,u=e.titleFontSize,d=e.bodyFontSize,h=e.footerFontSize;i+=s*u,i+=s?(s-1)*e.titleSpacing:0,i+=s?e.titleMarginBottom:0,i+=o*d,i+=o?(o-1)*e.bodySpacing:0,i+=l?e.footerMarginTop:0,i+=l*h,i+=l?(l-1)*e.footerSpacing:0;var c=0,f=function(t){a=Math.max(a,n.measureText(t).width+c)};return n.font=V.fontString(u,e._titleFontStyle,e._titleFontFamily),V.each(e.title,f),n.font=V.fontString(d,e._bodyFontStyle,e._bodyFontFamily),V.each(e.beforeBody.concat(e.afterBody),f),c=e.displayColors?d+2:0,V.each(r,(function(t){V.each(t.before,f),V.each(t.lines,f),V.each(t.after,f)})),c=0,n.font=V.fontString(h,e._footerFontStyle,e._footerFontFamily),V.each(e.footer,f),{width:a+=2*e.xPadding,height:i}}(this,g),b=function(t,e,n,i){var a=t.x,r=t.y,o=t.caretSize,s=t.caretPadding,l=t.cornerRadius,u=n.xAlign,d=n.yAlign,h=o+s,c=l+s;return"right"===u?a-=e.width:"center"===u&&((a-=e.width/2)+e.width>i.width&&(a=i.width-e.width),a<0&&(a=0)),"top"===d?r+=h:r-="bottom"===d?e.height+h:e.height/2,"center"===d?"left"===u?a+=h:"right"===u&&(a-=h):"left"===u?a-=c:"right"===u&&(a+=c),{x:a,y:r}}(g,x,v=function(t,e){var n,i,a,r,o,s=t._model,l=t._chart,u=t._chart.chartArea,d="center",h="center";s.y<e.height?h="top":s.y>l.height-e.height&&(h="bottom");var c=(u.left+u.right)/2,f=(u.top+u.bottom)/2;"center"===h?(n=function(t){return t<=c},i=function(t){return t>c}):(n=function(t){return t<=e.width/2},i=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},r=function(t){return t-e.width-s.caretSize-s.caretPadding<0},o=function(t){return t<=f?"top":"bottom"},n(s.x)?(d="left",a(s.x)&&(d="center",h=o(s.y))):i(s.x)&&(d="right",r(s.x)&&(d="center",h=o(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:h}}(this,x),h._chart)}else g.opacity=0;return g.xAlign=v.xAlign,g.yAlign=v.yAlign,g.x=b.x,g.y=b.y,g.width=x.width,g.height=x.height,g.caretX=y.x,g.caretY=y.y,h._model=g,t&&c.custom&&c.custom.call(h,g),h},drawCaret:function(t,e){var n=this._chart.ctx,i=this._view,a=this.getCaretPosition(t,e,i);n.lineTo(a.x1,a.y1),n.lineTo(a.x2,a.y2),n.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,n){var i,a,r,o,s,l,u=n.caretSize,d=n.cornerRadius,h=n.xAlign,c=n.yAlign,f=t.x,g=t.y,p=e.width,m=e.height;if("center"===c)s=g+m/2,"left"===h?(a=(i=f)-u,r=i,o=s+u,l=s-u):(a=(i=f+p)+u,r=i,o=s-u,l=s+u);else if("left"===h?(i=(a=f+d+u)-u,r=a+u):"right"===h?(i=(a=f+p-d-u)-u,r=a+u):(i=(a=n.caretX)-u,r=a+u),"top"===c)s=(o=g)-u,l=o;else{s=(o=g+m)+u,l=o;var v=r;r=i,i=v}return{x1:i,x2:a,x3:r,y1:o,y2:s,y3:l}},drawTitle:function(t,e,n){var i,a,r,o=e.title,s=o.length;if(s){var l=ze(e.rtl,e.x,e.width);for(t.x=Ve(e,e._titleAlign),n.textAlign=l.textAlign(e._titleAlign),n.textBaseline="middle",i=e.titleFontSize,a=e.titleSpacing,n.fillStyle=e.titleFontColor,n.font=V.fontString(i,e._titleFontStyle,e._titleFontFamily),r=0;r<s;++r)n.fillText(o[r],l.x(t.x),t.y+i/2),t.y+=i+a,r+1===s&&(t.y+=e.titleMarginBottom-a)}},drawBody:function(t,e,n){var i,a,r,o,s,l,u,d,h=e.bodyFontSize,c=e.bodySpacing,f=e._bodyAlign,g=e.body,p=e.displayColors,m=0,v=p?Ve(e,"left"):0,b=ze(e.rtl,e.x,e.width),x=function(e){n.fillText(e,b.x(t.x+m),t.y+h/2),t.y+=h+c},y=b.textAlign(f);for(n.textAlign=f,n.textBaseline="middle",n.font=V.fontString(h,e._bodyFontStyle,e._bodyFontFamily),t.x=Ve(e,y),n.fillStyle=e.bodyFontColor,V.each(e.beforeBody,x),m=p&&"right"!==y?"center"===f?h/2+1:h+2:0,s=0,u=g.length;s<u;++s){for(i=g[s],a=e.labelTextColors[s],r=e.labelColors[s],n.fillStyle=a,V.each(i.before,x),l=0,d=(o=i.lines).length;l<d;++l){if(p){var _=b.x(v);n.fillStyle=e.legendColorBackground,n.fillRect(b.leftForLtr(_,h),t.y,h,h),n.lineWidth=1,n.strokeStyle=r.borderColor,n.strokeRect(b.leftForLtr(_,h),t.y,h,h),n.fillStyle=r.backgroundColor,n.fillRect(b.leftForLtr(b.xPlus(_,1),h-2),t.y+1,h-2,h-2),n.fillStyle=a}x(o[l])}V.each(i.after,x)}m=0,V.each(e.afterBody,x),t.y-=c},drawFooter:function(t,e,n){var i,a,r=e.footer,o=r.length;if(o){var s=ze(e.rtl,e.x,e.width);for(t.x=Ve(e,e._footerAlign),t.y+=e.footerMarginTop,n.textAlign=s.textAlign(e._footerAlign),n.textBaseline="middle",i=e.footerFontSize,n.fillStyle=e.footerFontColor,n.font=V.fontString(i,e._footerFontStyle,e._footerFontFamily),a=0;a<o;++a)n.fillText(r[a],s.x(t.x),t.y+i/2),t.y+=i+e.footerSpacing}},drawBackground:function(t,e,n,i){n.fillStyle=e.backgroundColor,n.strokeStyle=e.borderColor,n.lineWidth=e.borderWidth;var a=e.xAlign,r=e.yAlign,o=t.x,s=t.y,l=i.width,u=i.height,d=e.cornerRadius;n.beginPath(),n.moveTo(o+d,s),"top"===r&&this.drawCaret(t,i),n.lineTo(o+l-d,s),n.quadraticCurveTo(o+l,s,o+l,s+d),"center"===r&&"right"===a&&this.drawCaret(t,i),n.lineTo(o+l,s+u-d),n.quadraticCurveTo(o+l,s+u,o+l-d,s+u),"bottom"===r&&this.drawCaret(t,i),n.lineTo(o+d,s+u),n.quadraticCurveTo(o,s+u,o,s+u-d),"center"===r&&"left"===a&&this.drawCaret(t,i),n.lineTo(o,s+d),n.quadraticCurveTo(o,s,o+d,s),n.closePath(),n.fill(),e.borderWidth>0&&n.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&r&&(t.save(),t.globalAlpha=a,this.drawBackground(i,e,t,n),i.y+=e.yPadding,V.rtl.overrideTextDirection(t,e.textDirection),this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),V.rtl.restoreTextDirection(t,e.textDirection),t.restore())}},handleEvent:function(t){var e,n=this,i=n._options;return n._lastActive=n._lastActive||[],"mouseout"===t.type?n._active=[]:(n._active=n._chart.getElementsAtEventForMode(t,i.mode,i),i.reverse&&n._active.reverse()),(e=!V.arrayEquals(n._active,n._lastActive))&&(n._lastActive=n._active,(i.enabled||i.custom)&&(n._eventPosition={x:t.x,y:t.y},n.update(!0),n.pivot())),e}}),qe=Ne,Ue=je;Ue.positioners=qe;var Ye=V.valueOrDefault;function Ge(){return V.merge({},[].slice.call(arguments),{merger:function(t,e,n,i){if("xAxes"===t||"yAxes"===t){var a,r,o,s=n[t].length;for(e[t]||(e[t]=[]),a=0;a<s;++a)o=n[t][a],r=Ye(o.type,"xAxes"===t?"category":"linear"),a>=e[t].length&&e[t].push({}),!e[t][a].type||o.type&&o.type!==e[t][a].type?V.merge(e[t][a],[Oe.getScaleDefaults(r),o]):V.merge(e[t][a],o)}else V._merger(t,e,n,i)}})}function Xe(){return V.merge({},[].slice.call(arguments),{merger:function(t,e,n,i){var a=e[t]||{},r=n[t];"scales"===t?e[t]=Ge(a,r):"scale"===t?e[t]=V.merge(a,[Oe.getScaleDefaults(r.type),r]):V._merger(t,e,n,i)}})}function Ke(t){var e=t.options;V.each(t.scales,(function(e){ge.removeBox(t,e)})),e=Xe(z.global,z[t.config.type],e),t.options=t.config.options=e,t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.tooltip._options=e.tooltips,t.tooltip.initialize()}function Ze(t,e,n){var i,a=function(t){return t.id===i};do{i=e+n++}while(V.findIndex(t,a)>=0);return i}function $e(t){return"top"===t||"bottom"===t}function Je(t,e){return function(n,i){return n[t]===i[t]?n[e]-i[e]:n[t]-i[t]}}z._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var Qe=function(t,e){return this.construct(t,e),this};V.extend(Qe.prototype,{construct:function(t,e){var n=this;e=function(t){var e=(t=t||{}).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=Xe(z.global,z[t.type],t.options||{}),t}(e);var i=Fe.acquireContext(t,e),a=i&&i.canvas,r=a&&a.height,o=a&&a.width;n.id=V.uid(),n.ctx=i,n.canvas=a,n.config=e,n.width=o,n.height=r,n.aspectRatio=r?o/r:null,n.options=e.options,n._bufferedRender=!1,n._layers=[],n.chart=n,n.controller=n,Qe.instances[n.id]=n,Object.defineProperty(n,"data",{get:function(){return n.config.data},set:function(t){n.config.data=t}}),i&&a?(n.initialize(),n.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return Le.notify(t,"beforeInit"),V.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.initToolTip(),Le.notify(t,"afterInit"),t},clear:function(){return V.canvas.clear(this),this},stop:function(){return $.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,a=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(V.getMaximumWidth(i))),o=Math.max(0,Math.floor(a?r/a:V.getMaximumHeight(i)));if((e.width!==r||e.height!==o)&&(i.width=e.width=r,i.height=e.height=o,i.style.width=r+"px",i.style.height=o+"px",V.retinaScale(e,n.devicePixelRatio),!t)){var s={width:r,height:o};Le.notify(e,"resize",[s]),n.onResize&&n.onResize(e,s),e.stop(),e.update({duration:n.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;V.each(e.xAxes,(function(t,n){t.id||(t.id=Ze(e.xAxes,"x-axis-",n))})),V.each(e.yAxes,(function(t,n){t.id||(t.id=Ze(e.yAxes,"y-axis-",n))})),n&&(n.id=n.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,n=t.scales||{},i=[],a=Object.keys(n).reduce((function(t,e){return t[e]=!1,t}),{});e.scales&&(i=i.concat((e.scales.xAxes||[]).map((function(t){return{options:t,dtype:"category",dposition:"bottom"}})),(e.scales.yAxes||[]).map((function(t){return{options:t,dtype:"linear",dposition:"left"}})))),e.scale&&i.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),V.each(i,(function(e){var i=e.options,r=i.id,o=Ye(i.type,e.dtype);$e(i.position)!==$e(e.dposition)&&(i.position=e.dposition),a[r]=!0;var s=null;if(r in n&&n[r].type===o)(s=n[r]).options=i,s.ctx=t.ctx,s.chart=t;else{var l=Oe.getScaleConstructor(o);if(!l)return;s=new l({id:r,type:o,options:i,ctx:t.ctx,chart:t}),n[s.id]=s}s.mergeTicksOptions(),e.isDefault&&(t.scale=s)})),V.each(a,(function(t,e){t||delete n[e]})),t.scales=n,Oe.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t,e,n=this,i=[],a=n.data.datasets;for(t=0,e=a.length;t<e;t++){var r=a[t],o=n.getDatasetMeta(t),s=r.type||n.config.type;if(o.type&&o.type!==s&&(n.destroyDatasetMeta(t),o=n.getDatasetMeta(t)),o.type=s,o.order=r.order||0,o.index=t,o.controller)o.controller.updateIndex(t),o.controller.linkScales();else{var l=$t[o.type];if(void 0===l)throw new Error('"'+o.type+'" is not a chart type.');o.controller=new l(n,t),i.push(o.controller)}}return i},resetElements:function(){var t=this;V.each(t.data.datasets,(function(e,n){t.getDatasetMeta(n).controller.reset()}),t)},reset:function(){this.resetElements(),this.tooltip.initialize()},update:function(t){var e,n,i=this;if(t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]}),Ke(i),Le._invalidate(i),!1!==Le.notify(i,"beforeUpdate")){i.tooltip._data=i.data;var a=i.buildOrUpdateControllers();for(e=0,n=i.data.datasets.length;e<n;e++)i.getDatasetMeta(e).controller.buildOrUpdateElements();i.updateLayout(),i.options.animation&&i.options.animation.duration&&V.each(a,(function(t){t.reset()})),i.updateDatasets(),i.tooltip.initialize(),i.lastActive=[],Le.notify(i,"afterUpdate"),i._layers.sort(Je("z","_idx")),i._bufferedRender?i._bufferedRequest={duration:t.duration,easing:t.easing,lazy:t.lazy}:i.render(t)}},updateLayout:function(){var t=this;!1!==Le.notify(t,"beforeLayout")&&(ge.update(this,this.width,this.height),t._layers=[],V.each(t.boxes,(function(e){e._configure&&e._configure(),t._layers.push.apply(t._layers,e._layers())}),t),t._layers.forEach((function(t,e){t._idx=e})),Le.notify(t,"afterScaleUpdate"),Le.notify(t,"afterLayout"))},updateDatasets:function(){if(!1!==Le.notify(this,"beforeDatasetsUpdate")){for(var t=0,e=this.data.datasets.length;t<e;++t)this.updateDataset(t);Le.notify(this,"afterDatasetsUpdate")}},updateDataset:function(t){var e=this.getDatasetMeta(t),n={meta:e,index:t};!1!==Le.notify(this,"beforeDatasetUpdate",[n])&&(e.controller._update(),Le.notify(this,"afterDatasetUpdate",[n]))},render:function(t){var e=this;t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]});var n=e.options.animation,i=Ye(t.duration,n&&n.duration),a=t.lazy;if(!1!==Le.notify(e,"beforeRender")){var r=function(t){Le.notify(e,"afterRender"),V.callback(n&&n.onComplete,[t],e)};if(n&&i){var o=new Z({numSteps:i/16.66,easing:t.easing||n.easing,render:function(t,e){var n=V.easing.effects[e.easing],i=e.currentStep,a=i/e.numSteps;t.draw(n(a),a,i)},onAnimationProgress:n.onProgress,onAnimationComplete:r});$.addAnimation(e,o,i,a)}else e.draw(),r(new Z({numSteps:0,chart:e}));return e}},draw:function(t){var e,n,i=this;if(i.clear(),V.isNullOrUndef(t)&&(t=1),i.transition(t),!(i.width<=0||i.height<=0)&&!1!==Le.notify(i,"beforeDraw",[t])){for(n=i._layers,e=0;e<n.length&&n[e].z<=0;++e)n[e].draw(i.chartArea);for(i.drawDatasets(t);e<n.length;++e)n[e].draw(i.chartArea);i._drawTooltip(t),Le.notify(i,"afterDraw",[t])}},transition:function(t){for(var e=0,n=(this.data.datasets||[]).length;e<n;++e)this.isDatasetVisible(e)&&this.getDatasetMeta(e).controller.transition(t);this.tooltip.transition(t)},_getSortedDatasetMetas:function(t){var e,n,i=[];for(e=0,n=(this.data.datasets||[]).length;e<n;++e)t&&!this.isDatasetVisible(e)||i.push(this.getDatasetMeta(e));return i.sort(Je("order","index")),i},_getSortedVisibleDatasetMetas:function(){return this._getSortedDatasetMetas(!0)},drawDatasets:function(t){var e,n;if(!1!==Le.notify(this,"beforeDatasetsDraw",[t])){for(n=(e=this._getSortedVisibleDatasetMetas()).length-1;n>=0;--n)this.drawDataset(e[n],t);Le.notify(this,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n={meta:t,index:t.index,easingValue:e};!1!==Le.notify(this,"beforeDatasetDraw",[n])&&(t.controller.draw(e),Le.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,n={tooltip:e,easingValue:t};!1!==Le.notify(this,"beforeTooltipDraw",[n])&&(e.draw(),Le.notify(this,"afterTooltipDraw",[n]))},getElementAtEvent:function(t){return ae.modes.single(this,t)},getElementsAtEvent:function(t){return ae.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return ae.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=ae.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return ae.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var n=e._meta[this.id];return n||(n=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e.order||0,index:t}),n},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e<n;++e)this.isDatasetVisible(e)&&t++;return t},isDatasetVisible:function(t){var e=this.getDatasetMeta(t);return"boolean"==typeof e.hidden?!e.hidden:!this.data.datasets[t].hidden},generateLegend:function(){return this.options.legendCallback(this)},destroyDatasetMeta:function(t){var e=this.id,n=this.data.datasets[t],i=n._meta&&n._meta[e];i&&(i.controller.destroy(),delete n._meta[e])},destroy:function(){var t,e,n=this,i=n.canvas;for(n.stop(),t=0,e=n.data.datasets.length;t<e;++t)n.destroyDatasetMeta(t);i&&(n.unbindEvents(),V.canvas.clear(n),Fe.releaseContext(n.ctx),n.canvas=null,n.ctx=null),Le.notify(n,"destroy"),delete Qe.instances[n.id]},toBase64Image:function(){return this.canvas.toDataURL.apply(this.canvas,arguments)},initToolTip:function(){var t=this;t.tooltip=new Ue({_chart:t,_chartInstance:t,_data:t.data,_options:t.options.tooltips},t)},bindEvents:function(){var t=this,e=t._listeners={},n=function(){t.eventHandler.apply(t,arguments)};V.each(t.options.events,(function(i){Fe.addEventListener(t,i,n),e[i]=n})),t.options.responsive&&(n=function(){t.resize()},Fe.addEventListener(t,"resize",n),e.resize=n)},unbindEvents:function(){var t=this,e=t._listeners;e&&(delete t._listeners,V.each(e,(function(e,n){Fe.removeEventListener(t,n,e)})))},updateHoverStyle:function(t,e,n){var i,a,r,o=n?"set":"remove";for(a=0,r=t.length;a<r;++a)(i=t[a])&&this.getDatasetMeta(i._datasetIndex).controller[o+"HoverStyle"](i);"dataset"===e&&this.getDatasetMeta(t[0]._datasetIndex).controller["_"+o+"DatasetHoverStyle"]()},eventHandler:function(t){var e=this,n=e.tooltip;if(!1!==Le.notify(e,"beforeEvent",[t])){e._bufferedRender=!0,e._bufferedRequest=null;var i=e.handleEvent(t);n&&(i=n._start?n.handleEvent(t):i|n.handleEvent(t)),Le.notify(e,"afterEvent",[t]);var a=e._bufferedRequest;return a?e.render(a):i&&!e.animating&&(e.stop(),e.render({duration:e.options.hover.animationDuration,lazy:!0})),e._bufferedRender=!1,e._bufferedRequest=null,e}},handleEvent:function(t){var e,n=this,i=n.options||{},a=i.hover;return n.lastActive=n.lastActive||[],"mouseout"===t.type?n.active=[]:n.active=n.getElementsAtEventForMode(t,a.mode,a),V.callback(i.onHover||i.hover.onHover,[t.native,n.active],n),"mouseup"!==t.type&&"click"!==t.type||i.onClick&&i.onClick.call(n,t.native,n.active),n.lastActive.length&&n.updateHoverStyle(n.lastActive,a.mode,!1),n.active.length&&a.mode&&n.updateHoverStyle(n.active,a.mode,!0),e=!V.arrayEquals(n.active,n.lastActive),n.lastActive=n.active,e}}),Qe.instances={};var tn=Qe;Qe.Controller=Qe,Qe.types={},V.configMerge=Xe,V.scaleMerge=Ge;function en(){throw new Error("This method is not implemented: either no adapter can be found or an incomplete integration was provided.")}function nn(t){this.options=t||{}}V.extend(nn.prototype,{formats:en,parse:en,format:en,add:en,diff:en,startOf:en,endOf:en,_create:function(t){return t}}),nn.override=function(t){V.extend(nn.prototype,t)};var an={_date:nn},rn={formatters:{values:function(t){return V.isArray(t)?t:""+t},linear:function(t,e,n){var i=n.length>3?n[2]-n[1]:n[1]-n[0];Math.abs(i)>1&&t!==Math.floor(t)&&(i=t-Math.floor(t));var a=V.log10(Math.abs(i)),r="";if(0!==t)if(Math.max(Math.abs(n[0]),Math.abs(n[n.length-1]))<1e-4){var o=V.log10(Math.abs(t)),s=Math.floor(o)-Math.floor(a);s=Math.max(Math.min(s,20),0),r=t.toExponential(s)}else{var l=-1*Math.floor(a);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var i=t/Math.pow(10,Math.floor(V.log10(t)));return 0===t?"0":1===i||2===i||5===i||0===e||e===n.length-1?t.toExponential():""}}},on=V.isArray,sn=V.isNullOrUndef,ln=V.valueOrDefault,un=V.valueAtIndexOrDefault;function dn(t,e,n){var i,a=t.getTicks().length,r=Math.min(e,a-1),o=t.getPixelForTick(r),s=t._startPixel,l=t._endPixel;if(!(n&&(i=1===a?Math.max(o-s,l-o):0===e?(t.getPixelForTick(1)-o)/2:(o-t.getPixelForTick(r-1))/2,(o+=r<e?i:-i)<s-1e-6||o>l+1e-6)))return o}function hn(t,e,n,i){var a,r,o,s,l,u,d,h,c,f,g,p,m,v=n.length,b=[],x=[],y=[];for(a=0;a<v;++a){if(s=n[a].label,l=n[a].major?e.major:e.minor,t.font=u=l.string,d=i[u]=i[u]||{data:{},gc:[]},h=l.lineHeight,c=f=0,sn(s)||on(s)){if(on(s))for(r=0,o=s.length;r<o;++r)g=s[r],sn(g)||on(g)||(c=V.measureText(t,d.data,d.gc,c,g),f+=h)}else c=V.measureText(t,d.data,d.gc,c,s),f=h;b.push(c),x.push(f),y.push(h/2)}function _(t){return{width:b[t]||0,height:x[t]||0,offset:y[t]||0}}return function(t,e){V.each(t,(function(t){var n,i=t.gc,a=i.length/2;if(a>e){for(n=0;n<a;++n)delete t.data[i[n]];i.splice(0,a)}}))}(i,v),p=b.indexOf(Math.max.apply(null,b)),m=x.indexOf(Math.max.apply(null,x)),{first:_(0),last:_(v-1),widest:_(p),highest:_(m)}}function cn(t){return t.drawTicks?t.tickMarkLength:0}function fn(t){var e,n;return t.display?(e=V.options._parseFont(t),n=V.options.toPadding(t.padding),e.lineHeight+n.height):0}function gn(t,e){return V.extend(V.options._parseFont({fontFamily:ln(e.fontFamily,t.fontFamily),fontSize:ln(e.fontSize,t.fontSize),fontStyle:ln(e.fontStyle,t.fontStyle),lineHeight:ln(e.lineHeight,t.lineHeight)}),{color:V.options.resolve([e.fontColor,t.fontColor,z.global.defaultFontColor])})}function pn(t){var e=gn(t,t.minor);return{minor:e,major:t.major.enabled?gn(t,t.major):e}}function mn(t){var e,n,i,a=[];for(n=0,i=t.length;n<i;++n)void 0!==(e=t[n])._index&&a.push(e);return a}function vn(t,e,n,i){var a,r,o,s,l=ln(n,0),u=Math.min(ln(i,t.length),t.length),d=0;for(e=Math.ceil(e),i&&(e=(a=i-n)/Math.floor(a/e)),s=l;s<0;)d++,s=Math.round(l+d*e);for(r=Math.max(l,0);r<u;r++)o=t[r],r===s?(o._index=r,d++,s=Math.round(l+d*e)):delete o.label}z._set("scale",{display:!0,position:"left",offset:!1,gridLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,drawBorder:!0,drawOnChartArea:!0,drawTicks:!0,tickMarkLength:10,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",zeroLineBorderDash:[],zeroLineBorderDashOffset:0,offsetGridLines:!1,borderDash:[],borderDashOffset:0},scaleLabel:{display:!1,labelString:"",padding:{top:4,bottom:4}},ticks:{beginAtZero:!1,minRotation:0,maxRotation:50,mirror:!1,padding:0,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,labelOffset:0,callback:rn.formatters.values,minor:{},major:{}}});var bn=X.extend({zeroLineIndex:0,getPadding:function(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}},getTicks:function(){return this._ticks},_getLabels:function(){var t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]},mergeTicksOptions:function(){},beforeUpdate:function(){V.callback(this.options.beforeUpdate,[this])},update:function(t,e,n){var i,a,r,o,s,l=this,u=l.options.ticks,d=u.sampleSize;if(l.beforeUpdate(),l.maxWidth=t,l.maxHeight=e,l.margins=V.extend({left:0,right:0,top:0,bottom:0},n),l._ticks=null,l.ticks=null,l._labelSizes=null,l._maxLabelLines=0,l.longestLabelWidth=0,l.longestTextCache=l.longestTextCache||{},l._gridLineItems=null,l._labelItems=null,l.beforeSetDimensions(),l.setDimensions(),l.afterSetDimensions(),l.beforeDataLimits(),l.determineDataLimits(),l.afterDataLimits(),l.beforeBuildTicks(),o=l.buildTicks()||[],(!(o=l.afterBuildTicks(o)||o)||!o.length)&&l.ticks)for(o=[],i=0,a=l.ticks.length;i<a;++i)o.push({value:l.ticks[i],major:!1});return l._ticks=o,s=d<o.length,r=l._convertTicksToLabels(s?function(t,e){for(var n=[],i=t.length/e,a=0,r=t.length;a<r;a+=i)n.push(t[Math.floor(a)]);return n}(o,d):o),l._configure(),l.beforeCalculateTickRotation(),l.calculateTickRotation(),l.afterCalculateTickRotation(),l.beforeFit(),l.fit(),l.afterFit(),l._ticksToDraw=u.display&&(u.autoSkip||"auto"===u.source)?l._autoSkip(o):o,s&&(r=l._convertTicksToLabels(l._ticksToDraw)),l.ticks=r,l.afterUpdate(),l.minSize},_configure:function(){var t,e,n=this,i=n.options.ticks.reverse;n.isHorizontal()?(t=n.left,e=n.right):(t=n.top,e=n.bottom,i=!i),n._startPixel=t,n._endPixel=e,n._reversePixels=i,n._length=e-t},afterUpdate:function(){V.callback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){V.callback(this.options.beforeSetDimensions,[this])},setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0},afterSetDimensions:function(){V.callback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){V.callback(this.options.beforeDataLimits,[this])},determineDataLimits:V.noop,afterDataLimits:function(){V.callback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){V.callback(this.options.beforeBuildTicks,[this])},buildTicks:V.noop,afterBuildTicks:function(t){var e=this;return on(t)&&t.length?V.callback(e.options.afterBuildTicks,[e,t]):(e.ticks=V.callback(e.options.afterBuildTicks,[e,e.ticks])||e.ticks,t)},beforeTickToLabelConversion:function(){V.callback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){var t=this.options.ticks;this.ticks=this.ticks.map(t.userCallback||t.callback,this)},afterTickToLabelConversion:function(){V.callback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){V.callback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var t,e,n,i,a,r,o,s=this,l=s.options,u=l.ticks,d=s.getTicks().length,h=u.minRotation||0,c=u.maxRotation,f=h;!s._isVisible()||!u.display||h>=c||d<=1||!s.isHorizontal()?s.labelRotation=h:(e=(t=s._getLabelSizes()).widest.width,n=t.highest.height-t.highest.offset,i=Math.min(s.maxWidth,s.chart.width-e),e+6>(a=l.offset?s.maxWidth/d:i/(d-1))&&(a=i/(d-(l.offset?.5:1)),r=s.maxHeight-cn(l.gridLines)-u.padding-fn(l.scaleLabel),o=Math.sqrt(e*e+n*n),f=V.toDegrees(Math.min(Math.asin(Math.min((t.highest.height+6)/a,1)),Math.asin(Math.min(r/o,1))-Math.asin(n/o))),f=Math.max(h,Math.min(c,f))),s.labelRotation=f)},afterCalculateTickRotation:function(){V.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){V.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},n=t.chart,i=t.options,a=i.ticks,r=i.scaleLabel,o=i.gridLines,s=t._isVisible(),l="bottom"===i.position,u=t.isHorizontal();if(u?e.width=t.maxWidth:s&&(e.width=cn(o)+fn(r)),u?s&&(e.height=cn(o)+fn(r)):e.height=t.maxHeight,a.display&&s){var d=pn(a),h=t._getLabelSizes(),c=h.first,f=h.last,g=h.widest,p=h.highest,m=.4*d.minor.lineHeight,v=a.padding;if(u){var b=0!==t.labelRotation,x=V.toRadians(t.labelRotation),y=Math.cos(x),_=Math.sin(x),k=_*g.width+y*(p.height-(b?p.offset:0))+(b?0:m);e.height=Math.min(t.maxHeight,e.height+k+v);var w,M,S=t.getPixelForTick(0)-t.left,C=t.right-t.getPixelForTick(t.getTicks().length-1);b?(w=l?y*c.width+_*c.offset:_*(c.height-c.offset),M=l?_*(f.height-f.offset):y*f.width+_*f.offset):(w=c.width/2,M=f.width/2),t.paddingLeft=Math.max((w-S)*t.width/(t.width-S),0)+3,t.paddingRight=Math.max((M-C)*t.width/(t.width-C),0)+3}else{var P=a.mirror?0:g.width+v+m;e.width=Math.min(t.maxWidth,e.width+P),t.paddingTop=c.height/2,t.paddingBottom=f.height/2}}t.handleMargins(),u?(t.width=t._length=n.width-t.margins.left-t.margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=n.height-t.margins.top-t.margins.bottom)},handleMargins:function(){var t=this;t.margins&&(t.margins.left=Math.max(t.paddingLeft,t.margins.left),t.margins.top=Math.max(t.paddingTop,t.margins.top),t.margins.right=Math.max(t.paddingRight,t.margins.right),t.margins.bottom=Math.max(t.paddingBottom,t.margins.bottom))},afterFit:function(){V.callback(this.options.afterFit,[this])},isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(sn(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},_convertTicksToLabels:function(t){var e,n,i,a=this;for(a.ticks=t.map((function(t){return t.value})),a.beforeTickToLabelConversion(),e=a.convertTicksToLabels(t)||a.ticks,a.afterTickToLabelConversion(),n=0,i=t.length;n<i;++n)t[n].label=e[n];return e},_getLabelSizes:function(){var t=this,e=t._labelSizes;return e||(t._labelSizes=e=hn(t.ctx,pn(t.options.ticks),t.getTicks(),t.longestTextCache),t.longestLabelWidth=e.widest.width),e},_parseValue:function(t){var e,n,i,a;return on(t)?(e=+this.getRightValue(t[0]),n=+this.getRightValue(t[1]),i=Math.min(e,n),a=Math.max(e,n)):(e=void 0,n=t=+this.getRightValue(t),i=t,a=t),{min:i,max:a,start:e,end:n}},_getScaleLabel:function(t){var e=this._parseValue(t);return void 0!==e.start?"["+e.start+", "+e.end+"]":+this.getRightValue(t)},getLabelForIndex:V.noop,getPixelForValue:V.noop,getValueForPixel:V.noop,getPixelForTick:function(t){var e=this.options.offset,n=this._ticks.length,i=1/Math.max(n-(e?0:1),1);return t<0||t>n-1?null:this.getPixelForDecimal(t*i+(e?i/2:0))},getPixelForDecimal:function(t){return this._reversePixels&&(t=1-t),this._startPixel+t*this._length},getDecimalForPixel:function(t){var e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,n,i,a,r=this.options.ticks,o=this._length,s=r.maxTicksLimit||o/this._tickSize()+1,l=r.major.enabled?function(t){var e,n,i=[];for(e=0,n=t.length;e<n;e++)t[e].major&&i.push(e);return i}(t):[],u=l.length,d=l[0],h=l[u-1];if(u>s)return function(t,e,n){var i,a,r=0,o=e[0];for(n=Math.ceil(n),i=0;i<t.length;i++)a=t[i],i===o?(a._index=i,o=e[++r*n]):delete a.label}(t,l,u/s),mn(t);if(i=function(t,e,n,i){var a,r,o,s,l=function(t){var e,n,i=t.length;if(i<2)return!1;for(n=t[0],e=1;e<i;++e)if(t[e]-t[e-1]!==n)return!1;return n}(t),u=(e.length-1)/i;if(!l)return Math.max(u,1);for(o=0,s=(a=V.math._factorize(l)).length-1;o<s;o++)if((r=a[o])>u)return r;return Math.max(u,1)}(l,t,0,s),u>0){for(e=0,n=u-1;e<n;e++)vn(t,i,l[e],l[e+1]);return a=u>1?(h-d)/(u-1):null,vn(t,i,V.isNullOrUndef(a)?0:d-a,d),vn(t,i,h,V.isNullOrUndef(a)?t.length:h+a),mn(t)}return vn(t,i),mn(t)},_tickSize:function(){var t=this.options.ticks,e=V.toRadians(this.labelRotation),n=Math.abs(Math.cos(e)),i=Math.abs(Math.sin(e)),a=this._getLabelSizes(),r=t.autoSkipPadding||0,o=a?a.widest.width+r:0,s=a?a.highest.height+r:0;return this.isHorizontal()?s*n>o*i?o/n:s/i:s*i<o*n?s/n:o/i},_isVisible:function(){var t,e,n,i=this.chart,a=this.options.display;if("auto"!==a)return!!a;for(t=0,e=i.data.datasets.length;t<e;++t)if(i.isDatasetVisible(t)&&((n=i.getDatasetMeta(t)).xAxisID===this.id||n.yAxisID===this.id))return!0;return!1},_computeGridLineItems:function(t){var e,n,i,a,r,o,s,l,u,d,h,c,f,g,p,m,v,b=this,x=b.chart,y=b.options,_=y.gridLines,k=y.position,w=_.offsetGridLines,M=b.isHorizontal(),S=b._ticksToDraw,C=S.length+(w?1:0),P=cn(_),A=[],D=_.drawBorder?un(_.lineWidth,0,0):0,T=D/2,I=V._alignPixel,F=function(t){return I(x,t,D)};for("top"===k?(e=F(b.bottom),s=b.bottom-P,u=e-T,h=F(t.top)+T,f=t.bottom):"bottom"===k?(e=F(b.top),h=t.top,f=F(t.bottom)-T,s=e+T,u=b.top+P):"left"===k?(e=F(b.right),o=b.right-P,l=e-T,d=F(t.left)+T,c=t.right):(e=F(b.left),d=t.left,c=F(t.right)-T,o=e+T,l=b.left+P),n=0;n<C;++n)i=S[n]||{},sn(i.label)&&n<S.length||(n===b.zeroLineIndex&&y.offset===w?(g=_.zeroLineWidth,p=_.zeroLineColor,m=_.zeroLineBorderDash||[],v=_.zeroLineBorderDashOffset||0):(g=un(_.lineWidth,n,1),p=un(_.color,n,"rgba(0,0,0,0.1)"),m=_.borderDash||[],v=_.borderDashOffset||0),void 0!==(a=dn(b,i._index||n,w))&&(r=I(x,a,g),M?o=l=d=c=r:s=u=h=f=r,A.push({tx1:o,ty1:s,tx2:l,ty2:u,x1:d,y1:h,x2:c,y2:f,width:g,color:p,borderDash:m,borderDashOffset:v})));return A.ticksLength=C,A.borderValue=e,A},_computeLabelItems:function(){var t,e,n,i,a,r,o,s,l,u,d,h,c=this,f=c.options,g=f.ticks,p=f.position,m=g.mirror,v=c.isHorizontal(),b=c._ticksToDraw,x=pn(g),y=g.padding,_=cn(f.gridLines),k=-V.toRadians(c.labelRotation),w=[];for("top"===p?(r=c.bottom-_-y,o=k?"left":"center"):"bottom"===p?(r=c.top+_+y,o=k?"right":"center"):"left"===p?(a=c.right-(m?0:_)-y,o=m?"left":"right"):(a=c.left+(m?0:_)+y,o=m?"right":"left"),t=0,e=b.length;t<e;++t)i=(n=b[t]).label,sn(i)||(s=c.getPixelForTick(n._index||t)+g.labelOffset,u=(l=n.major?x.major:x.minor).lineHeight,d=on(i)?i.length:1,v?(a=s,h="top"===p?((k?1:.5)-d)*u:(k?0:.5)*u):(r=s,h=(1-d)*u/2),w.push({x:a,y:r,rotation:k,label:i,font:l,textOffset:h,textAlign:o}));return w},_drawGrid:function(t){var e=this,n=e.options.gridLines;if(n.display){var i,a,r,o,s,l=e.ctx,u=e.chart,d=V._alignPixel,h=n.drawBorder?un(n.lineWidth,0,0):0,c=e._gridLineItems||(e._gridLineItems=e._computeGridLineItems(t));for(r=0,o=c.length;r<o;++r)i=(s=c[r]).width,a=s.color,i&&a&&(l.save(),l.lineWidth=i,l.strokeStyle=a,l.setLineDash&&(l.setLineDash(s.borderDash),l.lineDashOffset=s.borderDashOffset),l.beginPath(),n.drawTicks&&(l.moveTo(s.tx1,s.ty1),l.lineTo(s.tx2,s.ty2)),n.drawOnChartArea&&(l.moveTo(s.x1,s.y1),l.lineTo(s.x2,s.y2)),l.stroke(),l.restore());if(h){var f,g,p,m,v=h,b=un(n.lineWidth,c.ticksLength-1,1),x=c.borderValue;e.isHorizontal()?(f=d(u,e.left,v)-v/2,g=d(u,e.right,b)+b/2,p=m=x):(p=d(u,e.top,v)-v/2,m=d(u,e.bottom,b)+b/2,f=g=x),l.lineWidth=h,l.strokeStyle=un(n.color,0),l.beginPath(),l.moveTo(f,p),l.lineTo(g,m),l.stroke()}}},_drawLabels:function(){var t=this;if(t.options.ticks.display){var e,n,i,a,r,o,s,l,u=t.ctx,d=t._labelItems||(t._labelItems=t._computeLabelItems());for(e=0,i=d.length;e<i;++e){if(o=(r=d[e]).font,u.save(),u.translate(r.x,r.y),u.rotate(r.rotation),u.font=o.string,u.fillStyle=o.color,u.textBaseline="middle",u.textAlign=r.textAlign,s=r.label,l=r.textOffset,on(s))for(n=0,a=s.length;n<a;++n)u.fillText(""+s[n],0,l),l+=o.lineHeight;else u.fillText(s,0,l);u.restore()}}},_drawTitle:function(){var t=this,e=t.ctx,n=t.options,i=n.scaleLabel;if(i.display){var a,r,o=ln(i.fontColor,z.global.defaultFontColor),s=V.options._parseFont(i),l=V.options.toPadding(i.padding),u=s.lineHeight/2,d=n.position,h=0;if(t.isHorizontal())a=t.left+t.width/2,r="bottom"===d?t.bottom-u-l.bottom:t.top+u+l.top;else{var c="left"===d;a=c?t.left+u+l.top:t.right-u-l.top,r=t.top+t.height/2,h=c?-.5*Math.PI:.5*Math.PI}e.save(),e.translate(a,r),e.rotate(h),e.textAlign="center",e.textBaseline="middle",e.fillStyle=o,e.font=s.string,e.fillText(i.labelString,0,0),e.restore()}},draw:function(t){this._isVisible()&&(this._drawGrid(t),this._drawTitle(),this._drawLabels())},_layers:function(){var t=this,e=t.options,n=e.ticks&&e.ticks.z||0,i=e.gridLines&&e.gridLines.z||0;return t._isVisible()&&n!==i&&t.draw===t._draw?[{z:i,draw:function(){t._drawGrid.apply(t,arguments),t._drawTitle.apply(t,arguments)}},{z:n,draw:function(){t._drawLabels.apply(t,arguments)}}]:[{z:n,draw:function(){t.draw.apply(t,arguments)}}]},_getMatchingVisibleMetas:function(t){var e=this,n=e.isHorizontal();return e.chart._getSortedVisibleDatasetMetas().filter((function(i){return(!t||i.type===t)&&(n?i.xAxisID===e.id:i.yAxisID===e.id)}))}});bn.prototype._draw=bn.prototype.draw;var xn=bn,yn=V.isNullOrUndef,_n=xn.extend({determineDataLimits:function(){var t,e=this,n=e._getLabels(),i=e.options.ticks,a=i.min,r=i.max,o=0,s=n.length-1;void 0!==a&&(t=n.indexOf(a))>=0&&(o=t),void 0!==r&&(t=n.indexOf(r))>=0&&(s=t),e.minIndex=o,e.maxIndex=s,e.min=n[o],e.max=n[s]},buildTicks:function(){var t=this._getLabels(),e=this.minIndex,n=this.maxIndex;this.ticks=0===e&&n===t.length-1?t:t.slice(e,n+1)},getLabelForIndex:function(t,e){var n=this.chart;return n.getDatasetMeta(e).controller._getValueScaleId()===this.id?this.getRightValue(n.data.datasets[e].data[t]):this._getLabels()[t]},_configure:function(){var t=this,e=t.options.offset,n=t.ticks;xn.prototype._configure.call(t),t.isHorizontal()||(t._reversePixels=!t._reversePixels),n&&(t._startValue=t.minIndex-(e?.5:0),t._valueRange=Math.max(n.length-(e?0:1),1))},getPixelForValue:function(t,e,n){var i,a,r,o=this;return yn(e)||yn(n)||(t=o.chart.data.datasets[n].data[e]),yn(t)||(i=o.isHorizontal()?t.x:t.y),(void 0!==i||void 0!==t&&isNaN(e))&&(a=o._getLabels(),t=V.valueOrDefault(i,t),e=-1!==(r=a.indexOf(t))?r:e,isNaN(e)&&(e=t)),o.getPixelForDecimal((e-o._startValue)/o._valueRange)},getPixelForTick:function(t){var e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t],t+this.minIndex)},getValueForPixel:function(t){var e=Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange);return Math.min(Math.max(e,0),this.ticks.length-1)},getBasePixel:function(){return this.bottom}}),kn={position:"bottom"};_n._defaults=kn;var wn=V.noop,Mn=V.isNullOrUndef;var Sn=xn.extend({getRightValue:function(t){return"string"==typeof t?+t:xn.prototype.getRightValue.call(this,t)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=V.sign(t.min),i=V.sign(t.max);n<0&&i<0?t.max=0:n>0&&i>0&&(t.min=0)}var a=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),a!==r&&t.min>=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this.options.ticks,n=e.stepSize,i=e.maxTicksLimit;return n?t=Math.ceil(this.max/n)-Math.floor(this.min/n)+1:(t=this._computeTickLimit(),i=i||11),i&&(t=Math.min(i,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:wn,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),i={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,precision:e.precision,stepSize:V.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var n,i,a,r,o=[],s=t.stepSize,l=s||1,u=t.maxTicks-1,d=t.min,h=t.max,c=t.precision,f=e.min,g=e.max,p=V.niceNum((g-f)/u/l)*l;if(p<1e-14&&Mn(d)&&Mn(h))return[f,g];(r=Math.ceil(g/p)-Math.floor(f/p))>u&&(p=V.niceNum(r*p/u/l)*l),s||Mn(c)?n=Math.pow(10,V._decimalPlaces(p)):(n=Math.pow(10,c),p=Math.ceil(p*n)/n),i=Math.floor(f/p)*p,a=Math.ceil(g/p)*p,s&&(!Mn(d)&&V.almostWhole(d/p,p/1e3)&&(i=d),!Mn(h)&&V.almostWhole(h/p,p/1e3)&&(a=h)),r=(a-i)/p,r=V.almostEquals(r,Math.round(r),p/1e3)?Math.round(r):Math.ceil(r),i=Math.round(i*n)/n,a=Math.round(a*n)/n,o.push(Mn(d)?i:d);for(var m=1;m<r;++m)o.push(Math.round((i+m*p)*n)/n);return o.push(Mn(h)?a:h),o}(i,t);t.handleDirectionalChanges(),t.max=V.max(a),t.min=V.min(a),e.reverse?(a.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max)},convertTicksToLabels:function(){var t=this;t.ticksAsNumbers=t.ticks.slice(),t.zeroLineIndex=t.ticks.indexOf(0),xn.prototype.convertTicksToLabels.call(t)},_configure:function(){var t,e=this,n=e.getTicks(),i=e.min,a=e.max;xn.prototype._configure.call(e),e.options.offset&&n.length&&(i-=t=(a-i)/Math.max(n.length-1,1)/2,a+=t),e._startValue=i,e._endValue=a,e._valueRange=a-i}}),Cn={position:"left",ticks:{callback:rn.formatters.linear}};function Pn(t,e,n,i){var a,r,o=t.options,s=function(t,e,n){var i=[n.type,void 0===e&&void 0===n.stack?n.index:"",n.stack].join(".");return void 0===t[i]&&(t[i]={pos:[],neg:[]}),t[i]}(e,o.stacked,n),l=s.pos,u=s.neg,d=i.length;for(a=0;a<d;++a)r=t._parseValue(i[a]),isNaN(r.min)||isNaN(r.max)||n.data[a].hidden||(l[a]=l[a]||0,u[a]=u[a]||0,o.relativePoints?l[a]=100:r.min<0||r.max<0?u[a]+=r.min:l[a]+=r.max)}function An(t,e,n){var i,a,r=n.length;for(i=0;i<r;++i)a=t._parseValue(n[i]),isNaN(a.min)||isNaN(a.max)||e.data[i].hidden||(t.min=Math.min(t.min,a.min),t.max=Math.max(t.max,a.max))}var Dn=Sn.extend({determineDataLimits:function(){var t,e,n,i,a=this,r=a.options,o=a.chart.data.datasets,s=a._getMatchingVisibleMetas(),l=r.stacked,u={},d=s.length;if(a.min=Number.POSITIVE_INFINITY,a.max=Number.NEGATIVE_INFINITY,void 0===l)for(t=0;!l&&t<d;++t)l=void 0!==(e=s[t]).stack;for(t=0;t<d;++t)n=o[(e=s[t]).index].data,l?Pn(a,u,e,n):An(a,e,n);V.each(u,(function(t){i=t.pos.concat(t.neg),a.min=Math.min(a.min,V.min(i)),a.max=Math.max(a.max,V.max(i))})),a.min=V.isFinite(a.min)&&!isNaN(a.min)?a.min:0,a.max=V.isFinite(a.max)&&!isNaN(a.max)?a.max:1,a.handleTickRangeOptions()},_computeTickLimit:function(){var t;return this.isHorizontal()?Math.ceil(this.width/40):(t=V.options._parseFont(this.options.ticks),Math.ceil(this.height/t.lineHeight))},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return this._getScaleLabel(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){return this.getPixelForDecimal((+this.getRightValue(t)-this._startValue)/this._valueRange)},getValueForPixel:function(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange},getPixelForTick:function(t){var e=this.ticksAsNumbers;return t<0||t>e.length-1?null:this.getPixelForValue(e[t])}}),Tn=Cn;Dn._defaults=Tn;var In=V.valueOrDefault,Fn=V.math.log10;var Ln={position:"left",ticks:{callback:rn.formatters.logarithmic}};function On(t,e){return V.isFinite(t)&&t>=0?t:e}var Rn=xn.extend({determineDataLimits:function(){var t,e,n,i,a,r,o=this,s=o.options,l=o.chart,u=l.data.datasets,d=o.isHorizontal();function h(t){return d?t.xAxisID===o.id:t.yAxisID===o.id}o.min=Number.POSITIVE_INFINITY,o.max=Number.NEGATIVE_INFINITY,o.minNotZero=Number.POSITIVE_INFINITY;var c=s.stacked;if(void 0===c)for(t=0;t<u.length;t++)if(e=l.getDatasetMeta(t),l.isDatasetVisible(t)&&h(e)&&void 0!==e.stack){c=!0;break}if(s.stacked||c){var f={};for(t=0;t<u.length;t++){var g=[(e=l.getDatasetMeta(t)).type,void 0===s.stacked&&void 0===e.stack?t:"",e.stack].join(".");if(l.isDatasetVisible(t)&&h(e))for(void 0===f[g]&&(f[g]=[]),a=0,r=(i=u[t].data).length;a<r;a++){var p=f[g];n=o._parseValue(i[a]),isNaN(n.min)||isNaN(n.max)||e.data[a].hidden||n.min<0||n.max<0||(p[a]=p[a]||0,p[a]+=n.max)}}V.each(f,(function(t){if(t.length>0){var e=V.min(t),n=V.max(t);o.min=Math.min(o.min,e),o.max=Math.max(o.max,n)}}))}else for(t=0;t<u.length;t++)if(e=l.getDatasetMeta(t),l.isDatasetVisible(t)&&h(e))for(a=0,r=(i=u[t].data).length;a<r;a++)n=o._parseValue(i[a]),isNaN(n.min)||isNaN(n.max)||e.data[a].hidden||n.min<0||n.max<0||(o.min=Math.min(n.min,o.min),o.max=Math.max(n.max,o.max),0!==n.min&&(o.minNotZero=Math.min(n.min,o.minNotZero)));o.min=V.isFinite(o.min)?o.min:null,o.max=V.isFinite(o.max)?o.max:null,o.minNotZero=V.isFinite(o.minNotZero)?o.minNotZero:null,this.handleTickRangeOptions()},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;t.min=On(e.min,t.min),t.max=On(e.max,t.max),t.min===t.max&&(0!==t.min&&null!==t.min?(t.min=Math.pow(10,Math.floor(Fn(t.min))-1),t.max=Math.pow(10,Math.floor(Fn(t.max))+1)):(t.min=1,t.max=10)),null===t.min&&(t.min=Math.pow(10,Math.floor(Fn(t.max))-1)),null===t.max&&(t.max=0!==t.min?Math.pow(10,Math.floor(Fn(t.min))+1):10),null===t.minNotZero&&(t.min>0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(Fn(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,n=!t.isHorizontal(),i={min:On(e.min),max:On(e.max)},a=t.ticks=function(t,e){var n,i,a=[],r=In(t.min,Math.pow(10,Math.floor(Fn(e.min)))),o=Math.floor(Fn(e.max)),s=Math.ceil(e.max/Math.pow(10,o));0===r?(n=Math.floor(Fn(e.minNotZero)),i=Math.floor(e.minNotZero/Math.pow(10,n)),a.push(r),r=i*Math.pow(10,n)):(n=Math.floor(Fn(r)),i=Math.floor(r/Math.pow(10,n)));var l=n<0?Math.pow(10,Math.abs(n)):1;do{a.push(r),10===++i&&(i=1,l=++n>=0?1:l),r=Math.round(i*Math.pow(10,n)*l)/l}while(n<o||n===o&&i<s);var u=In(t.max,r);return a.push(u),a}(i,t);t.max=V.max(a),t.min=V.min(a),e.reverse?(n=!n,t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max),n&&a.reverse()},convertTicksToLabels:function(){this.tickValues=this.ticks.slice(),xn.prototype.convertTicksToLabels.call(this)},getLabelForIndex:function(t,e){return this._getScaleLabel(this.chart.data.datasets[e].data[t])},getPixelForTick:function(t){var e=this.tickValues;return t<0||t>e.length-1?null:this.getPixelForValue(e[t])},_getFirstTickValue:function(t){var e=Math.floor(Fn(t));return Math.floor(t/Math.pow(10,e))*Math.pow(10,e)},_configure:function(){var t=this,e=t.min,n=0;xn.prototype._configure.call(t),0===e&&(e=t._getFirstTickValue(t.minNotZero),n=In(t.options.ticks.fontSize,z.global.defaultFontSize)/t._length),t._startValue=Fn(e),t._valueOffset=n,t._valueRange=(Fn(t.max)-Fn(e))/(1-n)},getPixelForValue:function(t){var e=this,n=0;return(t=+e.getRightValue(t))>e.min&&t>0&&(n=(Fn(t)-e._startValue)/e._valueRange+e._valueOffset),e.getPixelForDecimal(n)},getValueForPixel:function(t){var e=this,n=e.getDecimalForPixel(t);return 0===n&&0===e.min?0:Math.pow(10,e._startValue+(n-e._valueOffset)*e._valueRange)}}),zn=Ln;Rn._defaults=zn;var Nn=V.valueOrDefault,Bn=V.valueAtIndexOrDefault,En=V.options.resolve,Wn={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:rn.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(t){return t}}};function Vn(t){var e=t.ticks;return e.display&&t.display?Nn(e.fontSize,z.global.defaultFontSize)+2*e.backdropPaddingY:0}function Hn(t,e,n,i,a){return t===i||t===a?{start:e-n/2,end:e+n/2}:t<i||t>a?{start:e-n,end:e}:{start:e,end:e+n}}function jn(t){return 0===t||180===t?"center":t<180?"left":"right"}function qn(t,e,n,i){var a,r,o=n.y+i/2;if(V.isArray(e))for(a=0,r=e.length;a<r;++a)t.fillText(e[a],n.x,o),o+=i;else t.fillText(e,n.x,o)}function Un(t,e,n){90===t||270===t?n.y-=e.h/2:(t>270||t<90)&&(n.y-=e.h)}function Yn(t){return V.isNumber(t)?t:0}var Gn=Sn.extend({setDimensions:function(){var t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=Vn(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2},determineDataLimits:function(){var t=this,e=t.chart,n=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;V.each(e.data.datasets,(function(a,r){if(e.isDatasetVisible(r)){var o=e.getDatasetMeta(r);V.each(a.data,(function(e,a){var r=+t.getRightValue(e);isNaN(r)||o.data[a].hidden||(n=Math.min(r,n),i=Math.max(r,i))}))}})),t.min=n===Number.POSITIVE_INFINITY?0:n,t.max=i===Number.NEGATIVE_INFINITY?0:i,t.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Vn(this.options))},convertTicksToLabels:function(){var t=this;Sn.prototype.convertTicksToLabels.call(t),t.pointLabels=t.chart.data.labels.map((function(){var e=V.callback(t.options.pointLabels.callback,arguments,t);return e||0===e?e:""}))},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t=this.options;t.display&&t.pointLabels.display?function(t){var e,n,i,a=V.options._parseFont(t.options.pointLabels),r={l:0,r:t.width,t:0,b:t.height-t.paddingTop},o={};t.ctx.font=a.string,t._pointLabelSizes=[];var s,l,u,d=t.chart.data.labels.length;for(e=0;e<d;e++){i=t.getPointPosition(e,t.drawingArea+5),s=t.ctx,l=a.lineHeight,u=t.pointLabels[e],n=V.isArray(u)?{w:V.longestText(s,s.font,u),h:u.length*l}:{w:s.measureText(u).width,h:l},t._pointLabelSizes[e]=n;var h=t.getIndexAngle(e),c=V.toDegrees(h)%360,f=Hn(c,i.x,n.w,0,180),g=Hn(c,i.y,n.h,90,270);f.start<r.l&&(r.l=f.start,o.l=h),f.end>r.r&&(r.r=f.end,o.r=h),g.start<r.t&&(r.t=g.start,o.t=h),g.end>r.b&&(r.b=g.end,o.b=h)}t.setReductions(t.drawingArea,r,o)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(t,e,n){var i=this,a=e.l/Math.sin(n.l),r=Math.max(e.r-i.width,0)/Math.sin(n.r),o=-e.t/Math.cos(n.t),s=-Math.max(e.b-(i.height-i.paddingTop),0)/Math.cos(n.b);a=Yn(a),r=Yn(r),o=Yn(o),s=Yn(s),i.drawingArea=Math.min(Math.floor(t-(a+r)/2),Math.floor(t-(o+s)/2)),i.setCenterPoint(a,r,o,s)},setCenterPoint:function(t,e,n,i){var a=this,r=a.width-e-a.drawingArea,o=t+a.drawingArea,s=n+a.drawingArea,l=a.height-a.paddingTop-i-a.drawingArea;a.xCenter=Math.floor((o+r)/2+a.left),a.yCenter=Math.floor((s+l)/2+a.top+a.paddingTop)},getIndexAngle:function(t){var e=this.chart,n=(t*(360/e.data.labels.length)+((e.options||{}).startAngle||0))%360;return(n<0?n+360:n)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(V.isNullOrUndef(t))return NaN;var n=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*n:(t-e.min)*n},getPointPosition:function(t,e){var n=this.getIndexAngle(t)-Math.PI/2;return{x:Math.cos(n)*e+this.xCenter,y:Math.sin(n)*e+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(t){var e=this.min,n=this.max;return this.getPointPositionForValue(t||0,this.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0)},_drawGrid:function(){var t,e,n,i=this,a=i.ctx,r=i.options,o=r.gridLines,s=r.angleLines,l=Nn(s.lineWidth,o.lineWidth),u=Nn(s.color,o.color);if(r.pointLabels.display&&function(t){var e=t.ctx,n=t.options,i=n.pointLabels,a=Vn(n),r=t.getDistanceFromCenterForValue(n.ticks.reverse?t.min:t.max),o=V.options._parseFont(i);e.save(),e.font=o.string,e.textBaseline="middle";for(var s=t.chart.data.labels.length-1;s>=0;s--){var l=0===s?a/2:0,u=t.getPointPosition(s,r+l+5),d=Bn(i.fontColor,s,z.global.defaultFontColor);e.fillStyle=d;var h=t.getIndexAngle(s),c=V.toDegrees(h);e.textAlign=jn(c),Un(c,t._pointLabelSizes[s],u),qn(e,t.pointLabels[s],u,o.lineHeight)}e.restore()}(i),o.display&&V.each(i.ticks,(function(t,n){0!==n&&(e=i.getDistanceFromCenterForValue(i.ticksAsNumbers[n]),function(t,e,n,i){var a,r=t.ctx,o=e.circular,s=t.chart.data.labels.length,l=Bn(e.color,i-1),u=Bn(e.lineWidth,i-1);if((o||s)&&l&&u){if(r.save(),r.strokeStyle=l,r.lineWidth=u,r.setLineDash&&(r.setLineDash(e.borderDash||[]),r.lineDashOffset=e.borderDashOffset||0),r.beginPath(),o)r.arc(t.xCenter,t.yCenter,n,0,2*Math.PI);else{a=t.getPointPosition(0,n),r.moveTo(a.x,a.y);for(var d=1;d<s;d++)a=t.getPointPosition(d,n),r.lineTo(a.x,a.y)}r.closePath(),r.stroke(),r.restore()}}(i,o,e,n))})),s.display&&l&&u){for(a.save(),a.lineWidth=l,a.strokeStyle=u,a.setLineDash&&(a.setLineDash(En([s.borderDash,o.borderDash,[]])),a.lineDashOffset=En([s.borderDashOffset,o.borderDashOffset,0])),t=i.chart.data.labels.length-1;t>=0;t--)e=i.getDistanceFromCenterForValue(r.ticks.reverse?i.min:i.max),n=i.getPointPosition(t,e),a.beginPath(),a.moveTo(i.xCenter,i.yCenter),a.lineTo(n.x,n.y),a.stroke();a.restore()}},_drawLabels:function(){var t=this,e=t.ctx,n=t.options.ticks;if(n.display){var i,a,r=t.getIndexAngle(0),o=V.options._parseFont(n),s=Nn(n.fontColor,z.global.defaultFontColor);e.save(),e.font=o.string,e.translate(t.xCenter,t.yCenter),e.rotate(r),e.textAlign="center",e.textBaseline="middle",V.each(t.ticks,(function(r,l){(0!==l||n.reverse)&&(i=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),n.showLabelBackdrop&&(a=e.measureText(r).width,e.fillStyle=n.backdropColor,e.fillRect(-a/2-n.backdropPaddingX,-i-o.size/2-n.backdropPaddingY,a+2*n.backdropPaddingX,o.size+2*n.backdropPaddingY)),e.fillStyle=s,e.fillText(r,0,-i))})),e.restore()}},_drawTitle:V.noop}),Xn=Wn;Gn._defaults=Xn;var Kn=V._deprecated,Zn=V.options.resolve,$n=V.valueOrDefault,Jn=Number.MIN_SAFE_INTEGER||-9007199254740991,Qn=Number.MAX_SAFE_INTEGER||9007199254740991,ti={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ei=Object.keys(ti);function ni(t,e){return t-e}function ii(t){return V.valueOrDefault(t.time.min,t.ticks.min)}function ai(t){return V.valueOrDefault(t.time.max,t.ticks.max)}function ri(t,e,n,i){var a=function(t,e,n){for(var i,a,r,o=0,s=t.length-1;o>=0&&o<=s;){if(a=t[(i=o+s>>1)-1]||null,r=t[i],!a)return{lo:null,hi:r};if(r[e]<n)o=i+1;else{if(!(a[e]>n))return{lo:a,hi:r};s=i-1}}return{lo:r,hi:null}}(t,e,n),r=a.lo?a.hi?a.lo:t[t.length-2]:t[0],o=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=o[e]-r[e],l=s?(n-r[e])/s:0,u=(o[i]-r[i])*l;return r[i]+u}function oi(t,e){var n=t._adapter,i=t.options.time,a=i.parser,r=a||i.format,o=e;return"function"==typeof a&&(o=a(o)),V.isFinite(o)||(o="string"==typeof r?n.parse(o,r):n.parse(o)),null!==o?+o:(a||"function"!=typeof r||(o=r(e),V.isFinite(o)||(o=n.parse(o))),o)}function si(t,e){if(V.isNullOrUndef(e))return null;var n=t.options.time,i=oi(t,t.getRightValue(e));return null===i?i:(n.round&&(i=+t._adapter.startOf(i,n.round)),i)}function li(t,e,n,i){var a,r,o,s=ei.length;for(a=ei.indexOf(t);a<s-1;++a)if(o=(r=ti[ei[a]]).steps?r.steps:Qn,r.common&&Math.ceil((n-e)/(o*r.size))<=i)return ei[a];return ei[s-1]}function ui(t,e,n){var i,a,r=[],o={},s=e.length;for(i=0;i<s;++i)o[a=e[i]]=i,r.push({value:a,major:!1});return 0!==s&&n?function(t,e,n,i){var a,r,o=t._adapter,s=+o.startOf(e[0].value,i),l=e[e.length-1].value;for(a=s;a<=l;a=+o.add(a,1,i))(r=n[a])>=0&&(e[r].major=!0);return e}(t,r,o,n):r}var di=xn.extend({initialize:function(){this.mergeTicksOptions(),xn.prototype.initialize.call(this)},update:function(){var t=this,e=t.options,n=e.time||(e.time={}),i=t._adapter=new an._date(e.adapters.date);return Kn("time scale",n.format,"time.format","time.parser"),Kn("time scale",n.min,"time.min","ticks.min"),Kn("time scale",n.max,"time.max","ticks.max"),V.mergeIf(n.displayFormats,i.formats()),xn.prototype.update.apply(t,arguments)},getRightValue:function(t){return t&&void 0!==t.t&&(t=t.t),xn.prototype.getRightValue.call(this,t)},determineDataLimits:function(){var t,e,n,i,a,r,o,s=this,l=s.chart,u=s._adapter,d=s.options,h=d.time.unit||"day",c=Qn,f=Jn,g=[],p=[],m=[],v=s._getLabels();for(t=0,n=v.length;t<n;++t)m.push(si(s,v[t]));for(t=0,n=(l.data.datasets||[]).length;t<n;++t)if(l.isDatasetVisible(t))if(a=l.data.datasets[t].data,V.isObject(a[0]))for(p[t]=[],e=0,i=a.length;e<i;++e)r=si(s,a[e]),g.push(r),p[t][e]=r;else p[t]=m.slice(0),o||(g=g.concat(m),o=!0);else p[t]=[];m.length&&(c=Math.min(c,m[0]),f=Math.max(f,m[m.length-1])),g.length&&(g=n>1?function(t){var e,n,i,a={},r=[];for(e=0,n=t.length;e<n;++e)a[i=t[e]]||(a[i]=!0,r.push(i));return r}(g).sort(ni):g.sort(ni),c=Math.min(c,g[0]),f=Math.max(f,g[g.length-1])),c=si(s,ii(d))||c,f=si(s,ai(d))||f,c=c===Qn?+u.startOf(Date.now(),h):c,f=f===Jn?+u.endOf(Date.now(),h)+1:f,s.min=Math.min(c,f),s.max=Math.max(c+1,f),s._table=[],s._timestamps={data:g,datasets:p,labels:m}},buildTicks:function(){var t,e,n,i=this,a=i.min,r=i.max,o=i.options,s=o.ticks,l=o.time,u=i._timestamps,d=[],h=i.getLabelCapacity(a),c=s.source,f=o.distribution;for(u="data"===c||"auto"===c&&"series"===f?u.data:"labels"===c?u.labels:function(t,e,n,i){var a,r=t._adapter,o=t.options,s=o.time,l=s.unit||li(s.minUnit,e,n,i),u=Zn([s.stepSize,s.unitStepSize,1]),d="week"===l&&s.isoWeekday,h=e,c=[];if(d&&(h=+r.startOf(h,"isoWeek",d)),h=+r.startOf(h,d?"day":l),r.diff(n,e,l)>1e5*u)throw e+" and "+n+" are too far apart with stepSize of "+u+" "+l;for(a=h;a<n;a=+r.add(a,u,l))c.push(a);return a!==n&&"ticks"!==o.bounds||c.push(a),c}(i,a,r,h),"ticks"===o.bounds&&u.length&&(a=u[0],r=u[u.length-1]),a=si(i,ii(o))||a,r=si(i,ai(o))||r,t=0,e=u.length;t<e;++t)(n=u[t])>=a&&n<=r&&d.push(n);return i.min=a,i.max=r,i._unit=l.unit||(s.autoSkip?li(l.minUnit,i.min,i.max,h):function(t,e,n,i,a){var r,o;for(r=ei.length-1;r>=ei.indexOf(n);r--)if(o=ei[r],ti[o].common&&t._adapter.diff(a,i,o)>=e-1)return o;return ei[n?ei.indexOf(n):0]}(i,d.length,l.minUnit,i.min,i.max)),i._majorUnit=s.major.enabled&&"year"!==i._unit?function(t){for(var e=ei.indexOf(t)+1,n=ei.length;e<n;++e)if(ti[ei[e]].common)return ei[e]}(i._unit):void 0,i._table=function(t,e,n,i){if("linear"===i||!t.length)return[{time:e,pos:0},{time:n,pos:1}];var a,r,o,s,l,u=[],d=[e];for(a=0,r=t.length;a<r;++a)(s=t[a])>e&&s<n&&d.push(s);for(d.push(n),a=0,r=d.length;a<r;++a)l=d[a+1],o=d[a-1],s=d[a],void 0!==o&&void 0!==l&&Math.round((l+o)/2)===s||u.push({time:s,pos:a/(r-1)});return u}(i._timestamps.data,a,r,f),i._offsets=function(t,e,n,i,a){var r,o,s=0,l=0;return a.offset&&e.length&&(r=ri(t,"time",e[0],"pos"),s=1===e.length?1-r:(ri(t,"time",e[1],"pos")-r)/2,o=ri(t,"time",e[e.length-1],"pos"),l=1===e.length?o:(o-ri(t,"time",e[e.length-2],"pos"))/2),{start:s,end:l,factor:1/(s+1+l)}}(i._table,d,0,0,o),s.reverse&&d.reverse(),ui(i,d,i._majorUnit)},getLabelForIndex:function(t,e){var n=this,i=n._adapter,a=n.chart.data,r=n.options.time,o=a.labels&&t<a.labels.length?a.labels[t]:"",s=a.datasets[e].data[t];return V.isObject(s)&&(o=n.getRightValue(s)),r.tooltipFormat?i.format(oi(n,o),r.tooltipFormat):"string"==typeof o?o:i.format(oi(n,o),r.displayFormats.datetime)},tickFormatFunction:function(t,e,n,i){var a=this._adapter,r=this.options,o=r.time.displayFormats,s=o[this._unit],l=this._majorUnit,u=o[l],d=n[e],h=r.ticks,c=l&&u&&d&&d.major,f=a.format(t,i||(c?u:s)),g=c?h.major:h.minor,p=Zn([g.callback,g.userCallback,h.callback,h.userCallback]);return p?p(f,e,n):f},convertTicksToLabels:function(t){var e,n,i=[];for(e=0,n=t.length;e<n;++e)i.push(this.tickFormatFunction(t[e].value,e,t));return i},getPixelForOffset:function(t){var e=this._offsets,n=ri(this._table,"time",t,"pos");return this.getPixelForDecimal((e.start+n)*e.factor)},getPixelForValue:function(t,e,n){var i=null;if(void 0!==e&&void 0!==n&&(i=this._timestamps.datasets[n][e]),null===i&&(i=si(this,t)),null!==i)return this.getPixelForOffset(i)},getPixelForTick:function(t){var e=this.getTicks();return t>=0&&t<e.length?this.getPixelForOffset(e[t].value):null},getValueForPixel:function(t){var e=this._offsets,n=this.getDecimalForPixel(t)/e.factor-e.end,i=ri(this._table,"pos",n,"time");return this._adapter._create(i)},_getLabelSize:function(t){var e=this.options.ticks,n=this.ctx.measureText(t).width,i=V.toRadians(this.isHorizontal()?e.maxRotation:e.minRotation),a=Math.cos(i),r=Math.sin(i),o=$n(e.fontSize,z.global.defaultFontSize);return{w:n*a+o*r,h:n*r+o*a}},getLabelWidth:function(t){return this._getLabelSize(t).w},getLabelCapacity:function(t){var e=this,n=e.options.time,i=n.displayFormats,a=i[n.unit]||i.millisecond,r=e.tickFormatFunction(t,0,ui(e,[t],e._majorUnit),a),o=e._getLabelSize(r),s=Math.floor(e.isHorizontal()?e.width/o.w:e.height/o.h);return e.options.offset&&s--,s>0?s:1}}),hi={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};di._defaults=hi;var ci={category:_n,linear:Dn,logarithmic:Rn,radialLinear:Gn,time:di},fi={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};an._date.override("function"==typeof t?{_id:"moment",formats:function(){return fi},parse:function(e,n){return"string"==typeof e&&"string"==typeof n?e=t(e,n):e instanceof t||(e=t(e)),e.isValid()?e.valueOf():null},format:function(e,n){return t(e).format(n)},add:function(e,n,i){return t(e).add(n,i).valueOf()},diff:function(e,n,i){return t(e).diff(t(n),i)},startOf:function(e,n,i){return e=t(e),"isoWeek"===n?e.isoWeekday(i).valueOf():e.startOf(n).valueOf()},endOf:function(e,n){return t(e).endOf(n).valueOf()},_create:function(e){return t(e)}}:{}),z._set("global",{plugins:{filler:{propagate:!0}}});var gi={dataset:function(t){var e=t.fill,n=t.chart,i=n.getDatasetMeta(e),a=i&&n.isDatasetVisible(e)&&i.dataset._children||[],r=a.length||0;return r?function(t,e){return e<r&&a[e]._view||null}:null},boundary:function(t){var e=t.boundary,n=e?e.x:null,i=e?e.y:null;return V.isArray(e)?function(t,n){return e[n]}:function(t){return{x:null===n?t.x:n,y:null===i?t.y:i}}}};function pi(t,e,n){var i,a=t._model||{},r=a.fill;if(void 0===r&&(r=!!a.backgroundColor),!1===r||null===r)return!1;if(!0===r)return"origin";if(i=parseFloat(r,10),isFinite(i)&&Math.floor(i)===i)return"-"!==r[0]&&"+"!==r[0]||(i=e+i),!(i===e||i<0||i>=n)&&i;switch(r){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return r;default:return!1}}function mi(t){return(t.el._scale||{}).getPointPositionForValue?function(t){var e,n,i,a,r,o=t.el._scale,s=o.options,l=o.chart.data.labels.length,u=t.fill,d=[];if(!l)return null;for(e=s.ticks.reverse?o.max:o.min,n=s.ticks.reverse?o.min:o.max,i=o.getPointPositionForValue(0,e),a=0;a<l;++a)r="start"===u||"end"===u?o.getPointPositionForValue(a,"start"===u?e:n):o.getBasePosition(a),s.gridLines.circular&&(r.cx=i.x,r.cy=i.y,r.angle=o.getIndexAngle(a)-Math.PI/2),d.push(r);return d}(t):function(t){var e,n=t.el._model||{},i=t.el._scale||{},a=t.fill,r=null;if(isFinite(a))return null;if("start"===a?r=void 0===n.scaleBottom?i.bottom:n.scaleBottom:"end"===a?r=void 0===n.scaleTop?i.top:n.scaleTop:void 0!==n.scaleZero?r=n.scaleZero:i.getBasePixel&&(r=i.getBasePixel()),null!=r){if(void 0!==r.x&&void 0!==r.y)return r;if(V.isFinite(r))return{x:(e=i.isHorizontal())?r:null,y:e?null:r}}return null}(t)}function vi(t,e,n){var i,a=t[e].fill,r=[e];if(!n)return a;for(;!1!==a&&-1===r.indexOf(a);){if(!isFinite(a))return a;if(!(i=t[a]))return!1;if(i.visible)return a;r.push(a),a=i.fill}return!1}function bi(t){var e=t.fill,n="dataset";return!1===e?null:(isFinite(e)||(n="boundary"),gi[n](t))}function xi(t){return t&&!t.skip}function yi(t,e,n,i,a){var r,o,s,l;if(i&&a){for(t.moveTo(e[0].x,e[0].y),r=1;r<i;++r)V.canvas.lineTo(t,e[r-1],e[r]);if(void 0===n[0].angle)for(t.lineTo(n[a-1].x,n[a-1].y),r=a-1;r>0;--r)V.canvas.lineTo(t,n[r],n[r-1],!0);else for(o=n[0].cx,s=n[0].cy,l=Math.sqrt(Math.pow(n[0].x-o,2)+Math.pow(n[0].y-s,2)),r=a-1;r>0;--r)t.arc(o,s,l,n[r].angle,n[r-1].angle,!0)}}function _i(t,e,n,i,a,r){var o,s,l,u,d,h,c,f,g=e.length,p=i.spanGaps,m=[],v=[],b=0,x=0;for(t.beginPath(),o=0,s=g;o<s;++o)d=n(u=e[l=o%g]._view,l,i),h=xi(u),c=xi(d),r&&void 0===f&&h&&(s=g+(f=o+1)),h&&c?(b=m.push(u),x=v.push(d)):b&&x&&(p?(h&&m.push(u),c&&v.push(d)):(yi(t,m,v,b,x),b=x=0,m=[],v=[]));yi(t,m,v,b,x),t.closePath(),t.fillStyle=a,t.fill()}var ki={id:"filler",afterDatasetsUpdate:function(t,e){var n,i,a,r,o=(t.data.datasets||[]).length,s=e.propagate,l=[];for(i=0;i<o;++i)r=null,(a=(n=t.getDatasetMeta(i)).dataset)&&a._model&&a instanceof _t.Line&&(r={visible:t.isDatasetVisible(i),fill:pi(a,i,o),chart:t,el:a}),n.$filler=r,l.push(r);for(i=0;i<o;++i)(r=l[i])&&(r.fill=vi(l,i,s),r.boundary=mi(r),r.mapper=bi(r))},beforeDatasetsDraw:function(t){var e,n,i,a,r,o,s,l=t._getSortedVisibleDatasetMetas(),u=t.ctx;for(n=l.length-1;n>=0;--n)(e=l[n].$filler)&&e.visible&&(a=(i=e.el)._view,r=i._children||[],o=e.mapper,s=a.backgroundColor||z.global.defaultColor,o&&s&&r.length&&(V.canvas.clipArea(u,t.chartArea),_i(u,r,o,a,s,i._loop),V.canvas.unclipArea(u)))}},wi=V.rtl.getRtlAdapter,Mi=V.noop,Si=V.valueOrDefault;function Ci(t,e){return t.usePointStyle&&t.boxWidth>e?e:t.boxWidth}z._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data.datasets,n=t.options.legend||{},i=n.labels&&n.labels.usePointStyle;return t._getSortedDatasetMetas().map((function(n){var a=n.controller.getStyle(i?0:void 0);return{text:e[n.index].label,fillStyle:a.backgroundColor,hidden:!t.isDatasetVisible(n.index),lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,pointStyle:a.pointStyle,rotation:a.rotation,datasetIndex:n.index}}),this)}}},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data.datasets;for(a.setAttribute("class",t.id+"-legend"),e=0,n=r.length;e<n;e++)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=r[e].backgroundColor,r[e].label&&i.appendChild(document.createTextNode(r[e].label));return a.outerHTML}});var Pi=X.extend({initialize:function(t){V.extend(this,t),this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1},beforeUpdate:Mi,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Mi,beforeSetDimensions:Mi,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Mi,beforeBuildLabels:Mi,buildLabels:function(){var t=this,e=t.options.labels||{},n=V.callback(e.generateLabels,[t.chart],t)||[];e.filter&&(n=n.filter((function(n){return e.filter(n,t.chart.data)}))),t.options.reverse&&n.reverse(),t.legendItems=n},afterBuildLabels:Mi,beforeFit:Mi,fit:function(){var t=this,e=t.options,n=e.labels,i=e.display,a=t.ctx,r=V.options._parseFont(n),o=r.size,s=t.legendHitBoxes=[],l=t.minSize,u=t.isHorizontal();if(u?(l.width=t.maxWidth,l.height=i?10:0):(l.width=i?10:0,l.height=t.maxHeight),i){if(a.font=r.string,u){var d=t.lineWidths=[0],h=0;a.textAlign="left",a.textBaseline="middle",V.each(t.legendItems,(function(t,e){var i=Ci(n,o)+o/2+a.measureText(t.text).width;(0===e||d[d.length-1]+i+2*n.padding>l.width)&&(h+=o+n.padding,d[d.length-(e>0?0:1)]=0),s[e]={left:0,top:0,width:i,height:o},d[d.length-1]+=i+n.padding})),l.height+=h}else{var c=n.padding,f=t.columnWidths=[],g=t.columnHeights=[],p=n.padding,m=0,v=0;V.each(t.legendItems,(function(t,e){var i=Ci(n,o)+o/2+a.measureText(t.text).width;e>0&&v+o+2*c>l.height&&(p+=m+n.padding,f.push(m),g.push(v),m=0,v=0),m=Math.max(m,i),v+=o+c,s[e]={left:0,top:0,width:i,height:o}})),p+=m,f.push(m),g.push(v),l.width+=p}t.width=l.width,t.height=l.height}else t.width=l.width=t.height=l.height=0},afterFit:Mi,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,n=e.labels,i=z.global,a=i.defaultColor,r=i.elements.line,o=t.height,s=t.columnHeights,l=t.width,u=t.lineWidths;if(e.display){var d,h=wi(e.rtl,t.left,t.minSize.width),c=t.ctx,f=Si(n.fontColor,i.defaultFontColor),g=V.options._parseFont(n),p=g.size;c.textAlign=h.textAlign("left"),c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=g.string;var m=Ci(n,p),v=t.legendHitBoxes,b=function(t,i){switch(e.align){case"start":return n.padding;case"end":return t-i;default:return(t-i+n.padding)/2}},x=t.isHorizontal();d=x?{x:t.left+b(l,u[0]),y:t.top+n.padding,line:0}:{x:t.left+n.padding,y:t.top+b(o,s[0]),line:0},V.rtl.overrideTextDirection(t.ctx,e.textDirection);var y=p+n.padding;V.each(t.legendItems,(function(e,i){var f=c.measureText(e.text).width,g=m+p/2+f,_=d.x,k=d.y;h.setWidth(t.minSize.width),x?i>0&&_+g+n.padding>t.left+t.minSize.width&&(k=d.y+=y,d.line++,_=d.x=t.left+b(l,u[d.line])):i>0&&k+y>t.top+t.minSize.height&&(_=d.x=_+t.columnWidths[d.line]+n.padding,d.line++,k=d.y=t.top+b(o,s[d.line]));var w=h.x(_);!function(t,e,i){if(!(isNaN(m)||m<=0)){c.save();var o=Si(i.lineWidth,r.borderWidth);if(c.fillStyle=Si(i.fillStyle,a),c.lineCap=Si(i.lineCap,r.borderCapStyle),c.lineDashOffset=Si(i.lineDashOffset,r.borderDashOffset),c.lineJoin=Si(i.lineJoin,r.borderJoinStyle),c.lineWidth=o,c.strokeStyle=Si(i.strokeStyle,a),c.setLineDash&&c.setLineDash(Si(i.lineDash,r.borderDash)),n&&n.usePointStyle){var s=m*Math.SQRT2/2,l=h.xPlus(t,m/2),u=e+p/2;V.canvas.drawPoint(c,i.pointStyle,s,l,u,i.rotation)}else c.fillRect(h.leftForLtr(t,m),e,m,p),0!==o&&c.strokeRect(h.leftForLtr(t,m),e,m,p);c.restore()}}(w,k,e),v[i].left=h.leftForLtr(w,v[i].width),v[i].top=k,function(t,e,n,i){var a=p/2,r=h.xPlus(t,m+a),o=e+a;c.fillText(n.text,r,o),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(r,o),c.lineTo(h.xPlus(r,i),o),c.stroke())}(w,k,e,f),x?d.x+=g+n.padding:d.y+=y})),V.rtl.restoreTextDirection(t.ctx,e.textDirection)}},_getLegendItemAt:function(t,e){var n,i,a,r=this;if(t>=r.left&&t<=r.right&&e>=r.top&&e<=r.bottom)for(a=r.legendHitBoxes,n=0;n<a.length;++n)if(t>=(i=a[n]).left&&t<=i.left+i.width&&e>=i.top&&e<=i.top+i.height)return r.legendItems[n];return null},handleEvent:function(t){var e,n=this,i=n.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!i.onHover&&!i.onLeave)return}else{if("click"!==a)return;if(!i.onClick)return}e=n._getLegendItemAt(t.x,t.y),"click"===a?e&&i.onClick&&i.onClick.call(n,t.native,e):(i.onLeave&&e!==n._hoveredItem&&(n._hoveredItem&&i.onLeave.call(n,t.native,n._hoveredItem),n._hoveredItem=e),i.onHover&&e&&i.onHover.call(n,t.native,e))}});function Ai(t,e){var n=new Pi({ctx:t.ctx,options:e,chart:t});ge.configure(t,n,e),ge.addBox(t,n),t.legend=n}var Di={id:"legend",_element:Pi,beforeInit:function(t){var e=t.options.legend;e&&Ai(t,e)},beforeUpdate:function(t){var e=t.options.legend,n=t.legend;e?(V.mergeIf(e,z.global.legend),n?(ge.configure(t,n,e),n.options=e):Ai(t,e)):n&&(ge.removeBox(t,n),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}},Ti=V.noop;z._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Ii=X.extend({initialize:function(t){V.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:Ti,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Ti,beforeSetDimensions:Ti,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Ti,beforeBuildLabels:Ti,buildLabels:Ti,afterBuildLabels:Ti,beforeFit:Ti,fit:function(){var t,e=this,n=e.options,i=e.minSize={},a=e.isHorizontal();n.display?(t=(V.isArray(n.text)?n.text.length:1)*V.options._parseFont(n).lineHeight+2*n.padding,e.width=i.width=a?e.maxWidth:t,e.height=i.height=a?t:e.maxHeight):e.width=i.width=e.height=i.height=0},afterFit:Ti,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=t.options;if(n.display){var i,a,r,o=V.options._parseFont(n),s=o.lineHeight,l=s/2+n.padding,u=0,d=t.top,h=t.left,c=t.bottom,f=t.right;e.fillStyle=V.valueOrDefault(n.fontColor,z.global.defaultFontColor),e.font=o.string,t.isHorizontal()?(a=h+(f-h)/2,r=d+l,i=f-h):(a="left"===n.position?h+l:f-l,r=d+(c-d)/2,i=c-d,u=Math.PI*("left"===n.position?-.5:.5)),e.save(),e.translate(a,r),e.rotate(u),e.textAlign="center",e.textBaseline="middle";var g=n.text;if(V.isArray(g))for(var p=0,m=0;m<g.length;++m)e.fillText(g[m],0,p,i),p+=s;else e.fillText(g,0,0,i);e.restore()}}});function Fi(t,e){var n=new Ii({ctx:t.ctx,options:e,chart:t});ge.configure(t,n,e),ge.addBox(t,n),t.titleBlock=n}var Li={},Oi=ki,Ri=Di,zi={id:"title",_element:Ii,beforeInit:function(t){var e=t.options.title;e&&Fi(t,e)},beforeUpdate:function(t){var e=t.options.title,n=t.titleBlock;e?(V.mergeIf(e,z.global.title),n?(ge.configure(t,n,e),n.options=e):Fi(t,e)):n&&(ge.removeBox(t,n),delete t.titleBlock)}};for(var Ni in Li.filler=Oi,Li.legend=Ri,Li.title=zi,tn.helpers=V,function(){function t(t,e,n){var i;return"string"==typeof t?(i=parseInt(t,10),-1!==t.indexOf("%")&&(i=i/100*e.parentNode[n])):i=t,i}function e(t){return null!=t&&"none"!==t}function n(n,i,a){var r=document.defaultView,o=V._getParentNode(n),s=r.getComputedStyle(n)[i],l=r.getComputedStyle(o)[i],u=e(s),d=e(l),h=Number.POSITIVE_INFINITY;return u||d?Math.min(u?t(s,n,a):h,d?t(l,o,a):h):"none"}V.where=function(t,e){if(V.isArray(t)&&Array.prototype.filter)return t.filter(e);var n=[];return V.each(t,(function(t){e(t)&&n.push(t)})),n},V.findIndex=Array.prototype.findIndex?function(t,e,n){return t.findIndex(e,n)}:function(t,e,n){n=void 0===n?t:n;for(var i=0,a=t.length;i<a;++i)if(e.call(n,t[i],i,t))return i;return-1},V.findNextWhere=function(t,e,n){V.isNullOrUndef(n)&&(n=-1);for(var i=n+1;i<t.length;i++){var a=t[i];if(e(a))return a}},V.findPreviousWhere=function(t,e,n){V.isNullOrUndef(n)&&(n=t.length);for(var i=n-1;i>=0;i--){var a=t[i];if(e(a))return a}},V.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},V.almostEquals=function(t,e,n){return Math.abs(t-e)<n},V.almostWhole=function(t,e){var n=Math.round(t);return n-e<=t&&n+e>=t},V.max=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.max(t,e)}),Number.NEGATIVE_INFINITY)},V.min=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.min(t,e)}),Number.POSITIVE_INFINITY)},V.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0===(t=+t)||isNaN(t)?t:t>0?1:-1},V.toRadians=function(t){return t*(Math.PI/180)},V.toDegrees=function(t){return t*(180/Math.PI)},V._decimalPlaces=function(t){if(V.isFinite(t)){for(var e=1,n=0;Math.round(t*e)/e!==t;)e*=10,n++;return n}},V.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},V.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},V.aliasPixel=function(t){return t%2==0?0:.5},V._alignPixel=function(t,e,n){var i=t.currentDevicePixelRatio,a=n/2;return Math.round((e-a)*i)/i+a},V.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l),h=i*(u=isNaN(u)?0:u),c=i*(d=isNaN(d)?0:d);return{previous:{x:r.x-h*(o.x-a.x),y:r.y-h*(o.y-a.y)},next:{x:r.x+c*(o.x-a.x),y:r.y+c*(o.y-a.y)}}},V.EPSILON=Number.EPSILON||1e-14,V.splineCurveMonotone=function(t){var e,n,i,a,r,o,s,l,u,d=(t||[]).map((function(t){return{model:t._model,deltaK:0,mK:0}})),h=d.length;for(e=0;e<h;++e)if(!(i=d[e]).model.skip){if(n=e>0?d[e-1]:null,(a=e<h-1?d[e+1]:null)&&!a.model.skip){var c=a.model.x-i.model.x;i.deltaK=0!==c?(a.model.y-i.model.y)/c:0}!n||n.model.skip?i.mK=i.deltaK:!a||a.model.skip?i.mK=n.deltaK:this.sign(n.deltaK)!==this.sign(i.deltaK)?i.mK=0:i.mK=(n.deltaK+i.deltaK)/2}for(e=0;e<h-1;++e)i=d[e],a=d[e+1],i.model.skip||a.model.skip||(V.almostEquals(i.deltaK,0,this.EPSILON)?i.mK=a.mK=0:(r=i.mK/i.deltaK,o=a.mK/i.deltaK,(l=Math.pow(r,2)+Math.pow(o,2))<=9||(s=3/Math.sqrt(l),i.mK=r*s*i.deltaK,a.mK=o*s*i.deltaK)));for(e=0;e<h;++e)(i=d[e]).model.skip||(n=e>0?d[e-1]:null,a=e<h-1?d[e+1]:null,n&&!n.model.skip&&(u=(i.model.x-n.model.x)/3,i.model.controlPointPreviousX=i.model.x-u,i.model.controlPointPreviousY=i.model.y-u*i.mK),a&&!a.model.skip&&(u=(a.model.x-i.model.x)/3,i.model.controlPointNextX=i.model.x+u,i.model.controlPointNextY=i.model.y+u*i.mK))},V.nextItem=function(t,e,n){return n?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},V.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},V.niceNum=function(t,e){var n=Math.floor(V.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},V.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},V.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.target||t.srcElement,o=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var l=parseFloat(V.getStyle(r,"padding-left")),u=parseFloat(V.getStyle(r,"padding-top")),d=parseFloat(V.getStyle(r,"padding-right")),h=parseFloat(V.getStyle(r,"padding-bottom")),c=o.right-o.left-l-d,f=o.bottom-o.top-u-h;return{x:n=Math.round((n-o.left-l)/c*r.width/e.currentDevicePixelRatio),y:i=Math.round((i-o.top-u)/f*r.height/e.currentDevicePixelRatio)}},V.getConstraintWidth=function(t){return n(t,"max-width","clientWidth")},V.getConstraintHeight=function(t){return n(t,"max-height","clientHeight")},V._calculatePadding=function(t,e,n){return(e=V.getStyle(t,e)).indexOf("%")>-1?n*parseInt(e,10)/100:parseInt(e,10)},V._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},V.getMaximumWidth=function(t){var e=V._getParentNode(t);if(!e)return t.clientWidth;var n=e.clientWidth,i=n-V._calculatePadding(e,"padding-left",n)-V._calculatePadding(e,"padding-right",n),a=V.getConstraintWidth(t);return isNaN(a)?i:Math.min(i,a)},V.getMaximumHeight=function(t){var e=V._getParentNode(t);if(!e)return t.clientHeight;var n=e.clientHeight,i=n-V._calculatePadding(e,"padding-top",n)-V._calculatePadding(e,"padding-bottom",n),a=V.getConstraintHeight(t);return isNaN(a)?i:Math.min(i,a)},V.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},V.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,r=t.width;i.height=a*n,i.width=r*n,t.ctx.scale(n,n),i.style.height||i.style.width||(i.style.height=a+"px",i.style.width=r+"px")}},V.fontString=function(t,e,n){return e+" "+t+"px "+n},V.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var o,s,l,u,d,h=0,c=n.length;for(o=0;o<c;o++)if(null!=(u=n[o])&&!0!==V.isArray(u))h=V.measureText(t,a,r,h,u);else if(V.isArray(u))for(s=0,l=u.length;s<l;s++)null==(d=u[s])||V.isArray(d)||(h=V.measureText(t,a,r,h,d));var f=r.length/2;if(f>n.length){for(o=0;o<f;o++)delete a[r[o]];r.splice(0,f)}return h},V.measureText=function(t,e,n,i,a){var r=e[a];return r||(r=e[a]=t.measureText(a).width,n.push(a)),r>i&&(i=r),i},V.numberOfLabelLines=function(t){var e=1;return V.each(t,(function(t){V.isArray(t)&&t.length>e&&(e=t.length)})),e},V.color=k?function(t){return t instanceof CanvasGradient&&(t=z.global.defaultColor),k(t)}:function(t){return console.error("Color.js not found!"),t},V.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:V.color(t).saturate(.5).darken(.1).rgbString()}}(),tn._adapters=an,tn.Animation=Z,tn.animationService=$,tn.controllers=$t,tn.DatasetController=nt,tn.defaults=z,tn.Element=X,tn.elements=_t,tn.Interaction=ae,tn.layouts=ge,tn.platform=Fe,tn.plugins=Le,tn.Scale=xn,tn.scaleService=Oe,tn.Ticks=rn,tn.Tooltip=Ue,tn.helpers.each(ci,(function(t,e){tn.scaleService.registerScaleType(e,t,t._defaults)})),Li)Li.hasOwnProperty(Ni)&&tn.plugins.register(Li[Ni]);tn.platform.initialize();var Bi=tn;return"undefined"!=typeof window&&(window.Chart=tn),tn.Chart=tn,tn.Legend=Li.legend._element,tn.Title=Li.title._element,tn.pluginService=tn.plugins,tn.PluginBase=tn.Element.extend({}),tn.canvasHelpers=tn.helpers.canvas,tn.layoutService=tn.layouts,tn.LinearScaleBase=Sn,tn.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],(function(t){tn[t]=function(e,n){return new tn(e,tn.helpers.merge(n||{},{type:t.charAt(0).toLowerCase()+t.slice(1)}))}})),Bi})); From 5c94848bccf5625dce16376be325252c40a6a598 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Mon, 24 Feb 2020 10:26:03 +0100 Subject: [PATCH 114/369] Refresh file list after a directory has been removed --- lib/web/mage/adminhtml/browser.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/web/mage/adminhtml/browser.js b/lib/web/mage/adminhtml/browser.js index 7780ac524fa49..d6502af9ab74b 100644 --- a/lib/web/mage/adminhtml/browser.js +++ b/lib/web/mage/adminhtml/browser.js @@ -417,6 +417,8 @@ define([ showLoader: true }).done($.proxy(function () { self.tree.jstree('refresh', self.activeNode.id); + self.reload(); + $(window).trigger('fileDeleted.mediabrowser'); }, this)); }, From bf514b0b435a75f5688f56f8e9465af9bb3284c1 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Tue, 18 Feb 2020 13:20:32 +0200 Subject: [PATCH 115/369] Refactoring test and add rollback for fixture --- ...SubCategoryWithoutRedirectActionGroup.xml} | 2 +- ...oductRewriteUrlSubCategoryActionGroup.xml} | 2 +- .../AdminRewriteProductWithTwoStoreTest.xml | 15 ++++--- .../Product/AnchorUrlRewriteGeneratorTest.php | 41 ++++++++++++------- .../_files/categories_with_stores.php | 26 ++++++------ .../categories_with_stores_rollback.php | 23 +++++++++++ .../_files/product_with_stores.php | 3 +- .../_files/product_with_stores_rollback.php | 29 +++++++++++++ 8 files changed, 101 insertions(+), 40 deletions(-) rename app/code/Magento/Catalog/Test/Mftf/ActionGroup/{ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml => AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml} (85%) rename app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/{StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml => AssertStorefrontProductRewriteUrlSubCategoryActionGroup.xml} (93%) create mode 100644 dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores_rollback.php diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml similarity index 85% rename from app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml rename to app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml index 4f0b87937baa9..fc010cec4cb65 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" extends="ChangeSeoUrlKeyForSubCategoryActionGroup"> + <actionGroup name="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" extends="ChangeSeoUrlKeyForSubCategoryActionGroup"> <annotations> <description>Requires navigation to subcategory creation/edit. Updates the Search Engine Optimization with uncheck Redirect Checkbox .</description> </annotations> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/AssertStorefrontProductRewriteUrlSubCategoryActionGroup.xml similarity index 93% rename from app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml rename to app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/AssertStorefrontProductRewriteUrlSubCategoryActionGroup.xml index 4e72c7f704866..4675d3b2669a4 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/StorefrontAssertProductRewriteUrlSubCategoryActionGroup.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/ActionGroup/AssertStorefrontProductRewriteUrlSubCategoryActionGroup.xml @@ -6,7 +6,7 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="StorefrontAssertProductRewriteUrlSubCategoryActionGroup"> + <actionGroup name="AssertStorefrontProductRewriteUrlSubCategoryActionGroup"> <annotations> <description>Validates that the provided Product Title is present on the Rewrite URL with a subcategory page.</description> </annotations> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml index ac9b3f573deba..db4811273a5cc 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminRewriteProductWithTwoStoreTest.xml @@ -43,13 +43,13 @@ <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchDefaultStoreViewForDefaultCategory"> <argument name="storeView" value="_defaultStore.name"/> </actionGroup> - <actionGroup ref="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForDefaultCategoryDefaultStore"> + <actionGroup ref="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForDefaultCategoryDefaultStore"> <argument name="value" value="{{_defaultCategoryDifferentUrlStore.url_key_default_store}}"/> </actionGroup> <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchCustomStoreViewForDefaultCategory"> <argument name="storeView" value="customStore.name"/> </actionGroup> - <actionGroup ref="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForDefaultCategoryCustomStore"> + <actionGroup ref="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForDefaultCategoryCustomStore"> <argument name="value" value="{{_defaultCategoryDifferentUrlStore.url_key_custom_store}}"/> </actionGroup> @@ -59,17 +59,17 @@ <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchDefaultStoreViewForSubCategory"> <argument name="storeView" value="_defaultStore.name"/> </actionGroup> - <actionGroup ref="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForSubCategoryDefaultStore"> + <actionGroup ref="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForSubCategoryDefaultStore"> <argument name="value" value="{{SimpleSubCategoryDifferentUrlStore.url_key_default_store}}"/> </actionGroup> <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="AdminSwitchCustomStoreViewForSubCategory"> <argument name="storeView" value="customStore.name"/> </actionGroup> - <actionGroup ref="ChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForSubCategoryCustomStore"> + + <actionGroup ref="AdminChangeSeoUrlKeyForSubCategoryWithoutRedirectActionGroup" stepKey="changeSeoUrlKeyForSubCategoryCustomStore"> <argument name="value" value="{{SimpleSubCategoryDifferentUrlStore.url_key_custom_store}}"/> </actionGroup> - - <actionGroup ref="StorefrontAssertProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrlDefaultStore"> + <actionGroup ref="AssertStorefrontProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrlDefaultStore"> <argument name="category" value="{{_defaultCategoryDifferentUrlStore.url_key_default_store}}"/> <argument name="product" value="SimpleProduct" /> </actionGroup> @@ -77,8 +77,7 @@ <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStore"> <argument name="storeView" value="customStore" /> </actionGroup> - - <actionGroup ref="StorefrontAssertProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrlCustomStore"> + <actionGroup ref="AssertStorefrontProductRewriteUrlSubCategoryActionGroup" stepKey="validatesRewriteUrlCustomStore"> <argument name="category" value="{{_defaultCategoryDifferentUrlStore.url_key_custom_store}}"/> <argument name="product" value="SimpleProduct" /> </actionGroup> diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php index 32eee8dd78250..446b423e17187 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGeneratorTest.php @@ -8,7 +8,6 @@ namespace Magento\CatalogUrlRewrite\Model\Product; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Catalog\Model\Category; use Magento\CatalogUrlRewrite\Model\ObjectRegistryFactory; use Magento\Framework\ObjectManagerInterface; use Magento\Store\Model\Store; @@ -31,11 +30,6 @@ class AnchorUrlRewriteGeneratorTest extends TestCase */ private $productRepository; - /** - * @var Category - */ - private $collectionCategory; - /** * @var ObjectRegistryFactory */ @@ -50,32 +44,49 @@ public function setUp() $this->objectManager = Bootstrap::getObjectManager(); $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); - $this->collectionCategory = $this->objectManager->create(Category::class); $this->objectRegistryFactory = $this->objectManager->create(ObjectRegistryFactory::class); } /** * Verify correct generate of the relative "StoreId" * + * @param string $expect + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_with_stores.php * @magentoDbIsolation disabled - * - * @return void + * @dataProvider getConfigGenerate */ - public function testGenerate(): void + public function testGenerate(string $expect): void { $product = $this->productRepository->get('simple'); $categories = $product->getCategoryCollection(); $productCategories = $this->objectRegistryFactory->create(['entities' => $categories]); - /** @var Store $store */ + /** @var AnchorUrlRewriteGenerator $generator */ + $generator = $this->objectManager->get(AnchorUrlRewriteGenerator::class); + + /** @var $store Store */ $store = Bootstrap::getObjectManager()->get(Store::class); $store->load('fixture_second_store', 'code'); - /** @var AnchorUrlRewriteGenerator $generator */ - $generator = $this->objectManager->get(AnchorUrlRewriteGenerator::class); + $urls = $generator->generate($store->getId(), $product, $productCategories); + + $this->assertEquals($expect, $urls[0]->getRequestPath()); + } - $this->assertEquals([], $generator->generate(1, $product, $productCategories)); - $this->assertNotEquals([], $generator->generate($store->getId(), $product, $productCategories)); + /** + * Data provider for testGenerate + * + * @return array + */ + public function getConfigGenerate(): array + { + return [ + [ + 'expect' => 'category-1-custom/simple-product.html' + ] + ]; } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php index 6794316b4bb93..5fc9d75598da6 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores.php @@ -13,14 +13,6 @@ require __DIR__ . '/../../../Magento/Store/_files/second_store.php'; Bootstrap::getInstance()->loadArea(FrontNameResolver::AREA_CODE); -/** - * After installation system has categories: - * - * root one with ID:1 and Default category with ID:3 both with StoreId:1, - * - * root one with ID:1 and Default category with ID:2 both with StoreId:2 - */ - $store = Bootstrap::getObjectManager()->get(Store::class); $store->load('fixture_second_store', 'code'); @@ -29,11 +21,13 @@ $category->isObjectNew(true); $category->setId(3) ->setName('Category 1') - ->setParentId(1) - ->setPath('1/2') - ->setLevel(1) + ->setParentId(2) + ->setPath('1/2/3') + ->setLevel(2) ->setAvailableSortBy('name') ->setDefaultSortBy('name') + ->setUrlPath('category-1-default') + ->setUrlKey('category-1-default') ->setIsActive(true) ->setPosition(1) ->save(); @@ -43,10 +37,12 @@ $category->setId(4) ->setName('Category 1.1') ->setParentId(3) - ->setPath('1/2/3') - ->setLevel(2) + ->setPath('1/2/3/4') + ->setLevel(3) ->setAvailableSortBy('name') ->setDefaultSortBy('name') + ->setUrlPath('category-1-1-default') + ->setUrlKey('category-1-1-default') ->setIsActive(true) ->setPosition(1) ->save(); @@ -61,6 +57,8 @@ ->setAvailableSortBy('name') ->setDefaultSortBy('name') ->setStoreId($store->getId()) + ->setUrlPath('category-1-custom') + ->setUrlKey('category-1-custom') ->setIsActive(true) ->setPosition(1) ->save(); @@ -75,6 +73,8 @@ ->setAvailableSortBy('name') ->setDefaultSortBy('name') ->setStoreId($store->getId()) + ->setUrlPath('category-1-1-custom') + ->setUrlKey('category-1-1-custom') ->setIsActive(true) ->setPosition(1) ->save(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores_rollback.php new file mode 100644 index 0000000000000..a89c80a61ccbc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/categories_with_stores_rollback.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$registry = $objectManager->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */ +$collection = $objectManager->create(\Magento\Catalog\Model\ResourceModel\Category\Collection::class); +$collection + ->addAttributeToFilter('level', 2) + ->load() + ->delete(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php index bcc7c9ed313d3..84fa9b3044af9 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores.php @@ -9,6 +9,7 @@ $installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Catalog\Setup\CategorySetup::class ); + require __DIR__ . '/categories_with_stores.php'; $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -33,8 +34,6 @@ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) - ->setStoreId(1) - ->setWebsiteIds([1]) ->setName('Simple Product') ->setSku('simple') ->setPrice(10) diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores_rollback.php new file mode 100644 index 0000000000000..86f0ce34af00c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_with_stores_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('simple', true); + if ($product->getId()) { + $productRepository->delete($product); + } +} catch (NoSuchEntityException $e) { +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); From 37731349799b541f05e6799bb8f66da23de0d2f7 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Mon, 24 Feb 2020 15:34:18 +0200 Subject: [PATCH 116/369] Mftf test, changed properties visibility --- .../Grid/Column/Renderer/ScheduleStatus.php | 31 +++++---- .../Data/AdminIndexManagementGridData.xml | 13 ++++ .../Section/AdminIndexManagementSection.xml | 2 + ...inSystemIndexManagementGridChangesTest.xml | 63 +++++++++++++++++++ 4 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 app/code/Magento/Indexer/Test/Mftf/Data/AdminIndexManagementGridData.xml create mode 100644 app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php index 7737077609b51..4d90d9c178c12 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -3,11 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Indexer\Block\Backend\Grid\Column\Renderer; +use Magento\Backend\Block\Context; use Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer; +use Magento\Framework\DataObject; use Magento\Framework\Escaper; use Magento\Framework\Mview\View; +use Magento\Framework\Mview\ViewInterface; use Magento\Framework\Phrase; /** @@ -16,23 +21,23 @@ class ScheduleStatus extends AbstractRenderer { /** - * @var \Magento\Framework\Escaper + * @var Escaper */ - protected $escaper; + private $escaper; /** - * @var \Magento\Framework\Mview\ViewInterface + * @var ViewInterface */ - protected $viewModel; + private $viewModel; - /** - * @param \Magento\Backend\Block\Context $context - * @param \Magento\Framework\Escaper $escaper - * @param \Magento\Framework\Mview\ViewInterface $viewModel - * @param array $data - */ + /** + * @param Context $context + * @param Escaper $escaper + * @param ViewInterface $viewModel + * @param array $data + */ public function __construct( - \Magento\Backend\Block\Context $context, + Context $context, Escaper $escaper, View $viewModel, array $data = [] @@ -45,10 +50,10 @@ public function __construct( /** * Render indexer status * - * @param \Magento\Framework\DataObject $row + * @param DataObject $row * @return string */ - public function render(\Magento\Framework\DataObject $row) + public function render(DataObject $row) { try { if (!$row->getIsScheduled()) { diff --git a/app/code/Magento/Indexer/Test/Mftf/Data/AdminIndexManagementGridData.xml b/app/code/Magento/Indexer/Test/Mftf/Data/AdminIndexManagementGridData.xml new file mode 100644 index 0000000000000..74494ee6b469c --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/Data/AdminIndexManagementGridData.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="AdminIndexManagementGridData"> + <data key="rowProductPrice">Product Price</data> + </entity> +</entities> diff --git a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml index 020cd9654e36b..825358e74f2af 100644 --- a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml +++ b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml @@ -17,5 +17,7 @@ <element name="indexerStatus" type="text" selector="//tr[descendant::td[contains(., '{{status}}')]]//*[contains(@class, 'col-indexer_status')]/span" parameterized="true"/> <element name="successMessage" type="text" selector="//*[@data-ui-id='messages-message-success']" timeout="120"/> <element name="selectMassAction" type="select" selector="#gridIndexer_massaction-mass-select"/> + <element name="columnScheduleStatus" type="text" selector="//th[contains(@class, 'col-indexer_schedule_status')]"/> + <element name="indexerScheduleStatus" type="text" selector="//tr[contains(.,'{{var1}}')]//td[contains(@class,'col-indexer_schedule_status')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml new file mode 100644 index 0000000000000..75b040a623451 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/Test/AdminSystemIndexManagementGridChangesTest.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSystemIndexManagementGridChangesTest"> + <annotations> + <features value="Indexer"/> + <stories value="Menu Navigation"/> + <title value="Admin system index management grid change test"/> + <description value="Verify changes in 'Schedule column' on system index management"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!--Open Index Management Page and Select Index mode "Update by Schedule" --> + <magentoCLI command="indexer:set-mode" arguments="schedule" stepKey="setIndexerModeSchedule"/> + <magentoCLI command="indexer:reindex" stepKey="indexerReindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/></before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <magentoCLI command="indexer:set-mode" arguments="realtime" stepKey="setIndexerModeRealTime"/> + <magentoCLI command="indexer:reindex" stepKey="indexerReindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToIndexManagementPageFirst"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemToolsIndexManagement.dataUiId}}"/> + </actionGroup> + <grabTextFrom selector="{{AdminIndexManagementSection.indexerScheduleStatus(AdminIndexManagementGridData.rowProductPrice)}}" stepKey="gradScheduleStatusBeforeChange"/> + + <!-- Verify 'Schedule status' column is present --> + <seeElement selector="{{AdminIndexManagementSection.columnScheduleStatus}}" stepKey="seeScheduleStatusColumn"/> + + <!--Adding Special price to product--> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="openAdminProductEditPage"/> + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProductForm"/> + + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToIndexManagementPageSecond"> + <argument name="menuUiId" value="{{AdminMenuSystem.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuSystemToolsIndexManagement.dataUiId}}"/> + </actionGroup> + <grabTextFrom selector="{{AdminIndexManagementSection.indexerScheduleStatus(AdminIndexManagementGridData.rowProductPrice)}}" stepKey="gradScheduleStatusAfterChange"/> + + <!-- Verify 'Schedule Status' column changes for 'Product Price' --> + <assertNotEquals stepKey="assertChange"> + <expectedResult type="string">$gradScheduleStatusBeforeChange</expectedResult> + <actualResult type="string">$gradScheduleStatusAfterChange</actualResult> + </assertNotEquals> + </test> +</tests> From 7ac4c6fa018b57d6d9898287ef10b5bbddf658d3 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Mon, 24 Feb 2020 17:49:14 -0600 Subject: [PATCH 117/369] MQE-1993:Refactor MFTF tests/actionGroups using <executeInSelenium> Fixing CategoryData.xml --- .../Magento/Catalog/Test/Mftf/Data/CategoryData.xml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 1d7a163aeb58d..f54ce9af83e88 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -127,18 +127,6 @@ <entity name="SubCategoryNonAnchor" extends="SubCategoryWithParent"> <requiredEntity type="custom_attribute">CustomAttributeCategoryNonAnchor</requiredEntity> </entity> - <entity name="SubCategoryWithParent" type="category"> - <data key="name" unique="suffix">ApiCategory</data> - <data key="is_active">true</data> - </entity> - <entity name="ConfigurableProductWithAttributeSet" type="category"> - <data key="name" unique="suffix">ApiCategory</data> - <data key="is_active">true</data> - </entity> - <entity name="ApiCategory" type="category"> - <data key="name" unique="suffix">ApiCategory</data> - <data key="is_active">true</data> - </entity> <entity name="ApiCategoryA" type="category"> <data key="name" unique="suffix">Category A</data> <data key="is_active">true</data> From 76b00811ed0f4cc0e5d05b58536ef516b4b064de Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Mon, 24 Feb 2020 19:10:40 -0600 Subject: [PATCH 118/369] MQE-1993:Refactor MFTF tests/actionGroups using <executeInSelenium> removed new references for DeleteAllCatalogPriceRuleActionGroup --- .../Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml | 5 +++-- ...InformationInShoppingCartForCustomerPhysicalQuoteTest.xml | 2 -- ...xInformationInShoppingCartForCustomerVirtualQuoteTest.xml | 2 -- ...TaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml | 2 -- ...TTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml | 2 -- 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml index ccdf90cc648c5..b089589decdf9 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminReorderWithCatalogPriceRuleDiscountTest.xml @@ -24,7 +24,6 @@ <!--Create product--> <createData entity="SimpleProduct2" stepKey="createSimpleProductApi"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> <!--Create the catalog price rule --> <createData entity="CatalogRuleToPercent" stepKey="createCatalogRule"/> <!--Create order via API--> @@ -44,7 +43,9 @@ <after> <deleteData createDataKey="createSimpleProductApi" stepKey="deleteSimpleProductApi"/> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> + <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteCatalogPriceRule"> + <argument name="ruleName" value="{{CatalogRuleToPercent.name}}"/> + </actionGroup> <actionGroup ref="logout" stepKey="logoutFromAdmin"/> </after> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml index a43f8a306902f..cf100cb20ab83 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml @@ -56,8 +56,6 @@ <argument name="valueForFPT" value="20"/> </actionGroup> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> - <!-- Delete all catalog price rules that can (and actually do) affect this test--> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> </before> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml index 7c6394e6f8848..f12bc611abd44 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml @@ -41,8 +41,6 @@ <createData entity="Simple_US_Customer_NY" stepKey="createCustomer"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Delete all catalog price rules that can (and actually do) affect this test--> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> </before> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml index 7a1077675cbee..6d34df81f6e77 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml @@ -54,8 +54,6 @@ <argument name="valueForFPT" value="20"/> </actionGroup> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> - <!-- Delete all catalog price rules that can (and actually do) affect this test--> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> </before> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml index a7059fcfb4644..0d8634d893d22 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml @@ -39,8 +39,6 @@ </createData> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!-- Delete all catalog price rules that can (and actually do) affect this test--> - <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> </before> From 0f9452b30291f34b2b4aa8c26c07512c1aa70deb Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Tue, 25 Feb 2020 03:31:30 +0100 Subject: [PATCH 119/369] Add missing filtering of Grid by e-mail address --- .../Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml index ebbd9ce469587..87c612db08698 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerActionGroup.xml @@ -15,9 +15,13 @@ <arguments> <argument name="customerEmail"/> </arguments> - + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomersPage"/> <conditionalClick selector="{{AdminCustomerFiltersSection.clearAll}}" dependentSelector="{{AdminCustomerFiltersSection.clearAll}}" visible="true" stepKey="clickClearFilters"/> + <waitForPageLoad stepKey="waitForFiltersClear"/> + <click selector="{{AdminCustomerFiltersSection.filtersButton}}" stepKey="openFilters"/> + <fillField selector="{{AdminCustomerFiltersSection.emailInput}}" userInput="{{customerEmail}}" stepKey="fillEmail"/> + <click selector="{{AdminCustomerFiltersSection.apply}}" stepKey="clickApplyFilters"/> <click stepKey="chooseCustomer" selector="{{AdminCustomerGridMainActionsSection.customerCheckbox(customerEmail)}}"/> <click stepKey="openActions" selector="{{AdminCustomerGridMainActionsSection.actions}}"/> <waitForPageLoad stepKey="waitActions"/> From bb45c866910f84c5425540e0636e5192d65d48c9 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Tue, 25 Feb 2020 11:25:20 -0600 Subject: [PATCH 120/369] MQE-1993:Refactor MFTF tests/actionGroups using <executeInSelenium> removed new references for DeleteAllCatalogPriceRuleActionGroup --- ...uleForConfigurableProductWithAssignedSimpleProducts2Test.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml index 6f7b8654d9402..00c4e0434ffd8 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml @@ -169,7 +169,7 @@ <!-- Delete created price rules --> <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteCatalogPriceRule"> - <argument name="ruleName" value="{{_defaultCatalogRule.name}}"/> + <argument name="ruleName" value="{{CatalogRuleToFixed.name}}"/> </actionGroup> <!-- Admin log out --> <actionGroup ref="logout" stepKey="logoutFromAdmin"/> From 08319582702be82b594225db5b66b873a87d18e7 Mon Sep 17 00:00:00 2001 From: Sathish <srsathish92@gmail.com> Date: Tue, 25 Feb 2020 23:59:26 +0530 Subject: [PATCH 121/369] Fix #26992 Add new rating is active checkbox alignment issue --- .../backend/Magento_Review/web/css/source/_module.less | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less index 08606402f7a0e..9eae708819a0b 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less @@ -77,3 +77,11 @@ } } } + +.review-rating-edit { + .admin__field-control.control { + input[type='checkbox'] { + margin: 8px 0 0 0; + } + } +} From 31b3b1d586ce1c1320680424a28d0ec067ff8578 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Tue, 25 Feb 2020 13:23:47 -0600 Subject: [PATCH 122/369] MQE-1993:Refactor MFTF tests/actionGroups using <executeInSelenium> removed new references for DeleteAllCatalogPriceRuleActionGroup --- ...uleForConfigurableProductWithAssignedSimpleProducts2Test.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml index 00c4e0434ffd8..6f7b8654d9402 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml @@ -169,7 +169,7 @@ <!-- Delete created price rules --> <actionGroup ref="RemoveCatalogPriceRuleActionGroup" stepKey="deleteCatalogPriceRule"> - <argument name="ruleName" value="{{CatalogRuleToFixed.name}}"/> + <argument name="ruleName" value="{{_defaultCatalogRule.name}}"/> </actionGroup> <!-- Admin log out --> <actionGroup ref="logout" stepKey="logoutFromAdmin"/> From 77fee571409509a8f4e6c825a03de4eaf3ce3388 Mon Sep 17 00:00:00 2001 From: AleksLi <aleksliwork@gmail.com> Date: Tue, 25 Feb 2020 22:12:29 +0100 Subject: [PATCH 123/369] MC-26683: Removed get errors of cart allowing to add product to cart --- .../QuoteGraphQl/Model/Cart/AddProductsToCart.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index 91c77a1a3ecc5..94fc525209997 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -54,16 +54,6 @@ public function execute(Quote $cart, array $cartItems): void $this->addProductToCart->execute($cart, $cartItemData); } - if ($cart->getData('has_error')) { - $e = new GraphQlInputException(__('Shopping cart errors')); - $errors = $cart->getErrors(); - foreach ($errors as $error) { - /** @var MessageInterface $error */ - $e->addError(new GraphQlInputException(__($error->getText()))); - } - throw $e; - } - $this->cartRepository->save($cart); } } From 1ec6c7f3b46a84d3b2bd887c0f4a4e1cc921a3d0 Mon Sep 17 00:00:00 2001 From: "Vincent.Le" <vinh.le2@niteco.se> Date: Wed, 26 Feb 2020 16:44:39 +0700 Subject: [PATCH 124/369] Issue 27009: Fix error fire on catch when create new theme --- lib/internal/Magento/Framework/View/Page/Config/Renderer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php index 6e64ed2150d4d..3e7000fabfbd3 100644 --- a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php +++ b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php @@ -414,6 +414,7 @@ protected function renderAssetHtml(\Magento\Framework\View\Asset\PropertyGroup $ $attributes = $this->getGroupAttributes($group); $result = ''; + $template= ''; try { /** @var $asset \Magento\Framework\View\Asset\AssetInterface */ foreach ($assets as $asset) { From 744b0f72ffcd5048c241381861cf5500e82f259a Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Wed, 26 Feb 2020 14:09:17 +0200 Subject: [PATCH 125/369] ObjectManager cleanup - Remove usage from AdminNotification module Fix small issues after code review --- app/code/Magento/AdminNotification/Block/System/Messages.php | 4 ++-- .../Controller/Adminhtml/Notification/MarkAsRead.php | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/AdminNotification/Block/System/Messages.php b/app/code/Magento/AdminNotification/Block/System/Messages.php index 2fbd918c2d824..318b8f8384e2e 100644 --- a/app/code/Magento/AdminNotification/Block/System/Messages.php +++ b/app/code/Magento/AdminNotification/Block/System/Messages.php @@ -74,8 +74,8 @@ public function getLastCritical() { $items = array_values($this->_messages->getItems()); - if (isset($items[0]) && (int)$items[0]->getSeverity() === MessageInterface::SEVERITY_CRITICAL) { - return $items[0]; + if (!empty($items) && current($items)->getSeverity() === MessageInterface::SEVERITY_CRITICAL) { + return current($items); } return null; } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php index edc6c702abe26..6dd40b56e0a24 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php @@ -9,6 +9,7 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\NotificationService; use Magento\Backend\App\Action; +use Magento\Framework\Exception\LocalizedException; class MarkAsRead extends Notification { @@ -40,7 +41,7 @@ public function execute() try { $this->notificationService->markAsRead($notificationId); $this->messageManager->addSuccessMessage(__('The message has been marked as Read.')); - } catch (\Magento\Framework\Exception\LocalizedException $e) { + } catch (LocalizedException $e) { $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addExceptionMessage( From e8e164f63fc090341b334439e96b0049261c9cde Mon Sep 17 00:00:00 2001 From: Alexander MEnk <a.menk@imi.de> Date: Wed, 26 Feb 2020 14:12:21 +0100 Subject: [PATCH 126/369] #23191 Port delete processing from commit 769c48d239648fd700f5b18157f2a5ef8d726b77 after merge --- .../Model/Import/Product/LinkProcessor.php | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index ff5e4527a0ebf..fdb0ec3534f30 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -140,6 +140,7 @@ private function processLinkBunches( $productIds = []; $linkRows = []; $positionRows = []; + $linksToDelete = []; $bunch = array_filter($bunch, [$importEntity, 'isRowAllowedToImport'], ARRAY_FILTER_USE_BOTH); foreach ($bunch as $rowData) { @@ -156,6 +157,12 @@ function ($linkName) use ($rowData) { ); foreach ($linkNameToId as $linkName => $linkId) { $linkSkus = explode($importEntity->getMultipleValueSeparator(), $rowData[$linkName . 'sku']); + + //process empty value + if (!empty($linkSkus[0]) && $linkSkus[0] === $importEntity->getEmptyAttributeValueConstant()) { + $linksToDelete[$linkId][] = $productId; + continue; + } $linkPositions = ! empty($rowData[$linkName . 'position']) ? explode($importEntity->getMultipleValueSeparator(), $rowData[$linkName . 'position']) : []; @@ -203,9 +210,43 @@ function ($linkedSku) use ($sku, $importEntity) { } } } + + $this->deleteProductsLinks($importEntity, $resource, $linksToDelete); $this->saveLinksData($importEntity, $resource, $productIds, $linkRows, $positionRows); } + /** + * Delete links + * + * @param Product $importEntity + * @param Link $resource + * @param array $linksToDelete + * @return void + * @throws LocalizedException + */ + private function deleteProductsLinks( + Product $importEntity, + Link $resource, + array $linksToDelete + ) + { + if (!empty($linksToDelete) && Import::BEHAVIOR_APPEND === $importEntity->getBehavior()) { + foreach ($linksToDelete as $linkTypeId => $productIds) { + if (!empty($productIds)) { + $whereLinkId = $importEntity->getConnection()->quoteInto('link_type_id', $linkTypeId); + $whereProductId = $importEntity->getConnection()->quoteInto( + 'product_id IN (?)', + array_unique($productIds) + ); + $importEntity->getConnection()->delete( + $resource->getMainTable(), + $whereLinkId . ' AND ' . $whereProductId + ); + } + } + } + } + /** * Check if product exists for specified SKU * From 37ad6a2dcfeff6542a6a23dc93d67744eab3008b Mon Sep 17 00:00:00 2001 From: Alexander MEnk <a.menk@imi.de> Date: Wed, 26 Feb 2020 18:25:50 +0100 Subject: [PATCH 127/369] #23191 Add unit test to check new ability to inject more custom link types + CSFix --- .../Model/Import/Product/LinkProcessor.php | 3 +- .../Import/Product/LinkProcessorTest.php | 124 ++++++++++++++++++ 2 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index fdb0ec3534f30..4c4438e3e9d7d 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -228,8 +228,7 @@ private function deleteProductsLinks( Product $importEntity, Link $resource, array $linksToDelete - ) - { + ) { if (!empty($linksToDelete) && Import::BEHAVIOR_APPEND === $importEntity->getBehavior()) { foreach ($linksToDelete as $linkTypeId => $productIds) { if (!empty($productIds)) { diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php new file mode 100644 index 0000000000000..0e053a339071f --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogImportExport\Test\Unit\Model\Import\Product; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class CategoryProcessorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $objectManager; + + /** @var ObjectManagerHelper */ + protected $objectManagerHelper; + + /** + * @var \Magento\CatalogImportExport\Model\Import\Product\LinkProcessor|\PHPUnit_Framework_MockObject_MockObject + */ + protected $linkProcessor; + + /** + * @var \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType + */ + protected $product; + + /** + * @var \Magento\ImportExport\Model\ResourceModel\Helper + */ + protected $resourceHelper; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Product\Link + */ + protected $resource; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Product\LinkFactory + */ + protected $linkFactory; + + /** + * @var \Magento\CatalogImportExport\Model\Import\Product\SkuProcessor::class + */ + protected $skuProcessor; + + /** + * @var \Psr\Log\LoggerInterface + */ + protected $logger; + + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->objectManagerHelper = new ObjectManagerHelper($this); + + $this->resourceHelper = $this->createMock(\Magento\ImportExport\Model\ResourceModel\Helper::class); + + $this->resource = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Link::class); + $this->resource->method('getMainTable')->willReturn('main_link_table'); + + $this->linkFactory = $this->createPartialMock(\Magento\Catalog\Model\ResourceModel\Product\LinkFactory::class, ['create']); + $this->linkFactory->method('create')->willReturn($this->resource); + + $this->skuProcessor = $this->createMock(\Magento\CatalogImportExport\Model\Import\Product\SkuProcessor::class, []); + $this->logger = $this->createMock(\Psr\Log\LoggerInterface::class); + } + + /** + * @dataProvider diConfigDataProvider + * @param $expectedCallCount + * @param $linkToNameId + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testSaveLinks($expectedCallCount, $linkToNameId) + { + $this->linkProcessor = + new \Magento\CatalogImportExport\Model\Import\Product\LinkProcessor( + $this->linkFactory, + $this->resourceHelper, + $this->skuProcessor, + $this->logger, + $linkToNameId + ); + + $importEntity = $this->createMock(\Magento\CatalogImportExport\Model\Import\Product::class); + $connection = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); + $importEntity->method('getConnection')->willReturn($connection); + $select = $this->createMock(\Magento\Framework\DB\Select::class); + + // expect one call per linkToNameId + $connection->expects($this->exactly($expectedCallCount))->method('select')->willReturn($select); + + $select->method('from')->willReturn($select); + + $dataSourceModel = $this->createMock(\Magento\ImportExport\Model\ResourceModel\Import\Data::class); + + $this->linkProcessor->saveLinks($importEntity, $dataSourceModel, '_related_'); + } + + /** + * @return array + */ + public function diConfigDataProvider() + { + return [ + [3, [ + '_related_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_RELATED, + '_crosssell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL, + '_upsell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL, + ]], + [4, [ + '_related_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_RELATED, + '_crosssell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_CROSSSELL, + '_upsell_' => \Magento\Catalog\Model\Product\Link::LINK_TYPE_UPSELL, + '_custom_link_' => 9, + ]], + ]; + } +} From 11c3525adab3175b6a4245d8e68b7b91564cccb3 Mon Sep 17 00:00:00 2001 From: Alexander MEnk <a.menk@imi.de> Date: Wed, 26 Feb 2020 21:16:58 +0100 Subject: [PATCH 128/369] #23191 Fix class name of test --- .../Test/Unit/Model/Import/Product/LinkProcessorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php index 0e053a339071f..b402c9aa05922 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php @@ -8,7 +8,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -class CategoryProcessorTest extends \PHPUnit\Framework\TestCase +class LinkProcessorTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager From 0a79989737fd30ac4f39e0a24d75b5328d635f2c Mon Sep 17 00:00:00 2001 From: AleksLi <aleksliwork@gmail.com> Date: Wed, 26 Feb 2020 21:37:41 +0100 Subject: [PATCH 129/369] MC-26683: Updated description of the class --- app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index 94fc525209997..cfe78389fffe4 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -8,12 +8,11 @@ namespace Magento\QuoteGraphQl\Model\Cart; use Magento\Framework\GraphQl\Exception\GraphQlInputException; -use Magento\Framework\Message\MessageInterface; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\Quote; /** - * Add products to cart + * Adding products to cart using GraphQL */ class AddProductsToCart { From 0fd6fd364a3816fbcd9c642bc3368017694e2883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Wed, 26 Feb 2020 23:10:51 +0100 Subject: [PATCH 130/369] Fix #20309 - fix url rewrite check with query params, add integration test cases --- .../Magento/UrlRewrite/Controller/Router.php | 27 +++++++++++++++---- .../Test/Unit/Controller/RouterTest.php | 14 ++++++++++ .../UrlRewrite/Controller/UrlRewriteTest.php | 15 +++++++++++ .../Magento/UrlRewrite/_files/url_rewrite.php | 18 +++++++++++++ .../_files/url_rewrite_rollback.php | 4 ++- 5 files changed, 72 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Controller/Router.php b/app/code/Magento/UrlRewrite/Controller/Router.php index edefbb5f4ba3a..040aa6fc85702 100644 --- a/app/code/Magento/UrlRewrite/Controller/Router.php +++ b/app/code/Magento/UrlRewrite/Controller/Router.php @@ -90,9 +90,26 @@ public function match(RequestInterface $request) $this->storeManager->getStore()->getId() ); - if ($rewrite === null || $rewrite->getRequestPath() === $rewrite->getTargetPath()) { - // Either no rewrite rule matching current URl found or found one with request path equal to - // target path, continuing with processing of this URL. + if ($rewrite === null) { + // No rewrite rule matching current URl found, continuing with + // processing of this URL. + return null; + } + + $requestStringTrimmed = ltrim($request->getRequestString(), '/'); + $rewriteRequestPath = $rewrite->getRequestPath(); + $rewriteTargetPath = $rewrite->getTargetPath(); + $rewriteTargetPathTrimmed = ltrim($rewriteTargetPath, '/'); + + if (preg_replace('/\?.*/', '', $rewriteRequestPath) === preg_replace('/\?.*/', '', $rewriteTargetPath) && + ( + !$requestStringTrimmed || + !$rewriteTargetPathTrimmed || + strpos($requestStringTrimmed, $rewriteTargetPathTrimmed) === 0 + ) + ) { + // Request and target paths of rewrite found without query params are equal and current request string + // starts with request target path, continuing with processing of this URL. return null; } @@ -104,9 +121,9 @@ public function match(RequestInterface $request) // Rule provides actual URL that can be processed by a controller. $request->setAlias( UrlInterface::REWRITE_REQUEST_PATH_ALIAS, - $rewrite->getRequestPath() + $rewriteRequestPath ); - $request->setPathInfo('/' . $rewrite->getTargetPath()); + $request->setPathInfo('/' . $rewriteTargetPath); return $this->actionFactory->create( Forward::class ); diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php index 7038e75f16456..7a3a6d346a792 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php @@ -114,6 +114,8 @@ public function testNoRewriteExist() { $this->request->method('getPathInfo') ->willReturn(''); + $this->request->method('getRequestString') + ->willReturn(''); $this->urlFinder->method('findOneByData') ->willReturn(null); $this->storeManager->method('getStore') @@ -142,6 +144,8 @@ public function testRewriteAfterStoreSwitcher() ->willReturn($oldStoreAlias); $this->request->method('getPathInfo') ->willReturn($initialRequestPath); + $this->request->method('getRequestString') + ->willReturn($initialRequestPath); $oldStore = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->getMock(); @@ -192,6 +196,7 @@ public function testRewriteAfterStoreSwitcher() public function testNoRewriteAfterStoreSwitcherWhenNoOldRewrite() { $this->request->method('getPathInfo')->willReturn('request-path'); + $this->request->method('getRequestString')->willReturn('request-path'); $this->request->method('getParam')->with('___from_store') ->willReturn('old-store'); $oldStore = $this->getMockBuilder(Store::class)->disableOriginalConstructor()->getMock(); @@ -217,6 +222,7 @@ public function testNoRewriteAfterStoreSwitcherWhenNoOldRewrite() public function testNoRewriteAfterStoreSwitcherWhenOldRewriteEqualsToNewOne() { $this->request->method('getPathInfo')->willReturn('request-path'); + $this->request->method('getRequestString')->willReturn('request-path'); $this->request->method('getParam')->with('___from_store') ->willReturn('old-store'); $oldStore = $this->getMockBuilder(Store::class)->disableOriginalConstructor()->getMock(); @@ -268,6 +274,8 @@ public function testMatchWithRedirect() ->willReturn($this->store); $this->request->method('getPathInfo') ->willReturn($requestPath); + $this->request->method('getRequestString') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor() ->getMock(); @@ -312,6 +320,8 @@ public function testMatchWithCustomInternalRedirect($requestPath, $targetPath, $ ->willReturn($this->store); $this->request->method('getPathInfo') ->willReturn($requestPath); + $this->request->method('getRequestString') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor() ->getMock(); @@ -369,6 +379,8 @@ public function testMatchWithCustomExternalRedirect($targetPath) $this->storeManager->method('getStore')->willReturn($this->store); $this->request->method('getPathInfo') ->willReturn($requestPath); + $this->request->method('getRequestString') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); $urlRewrite->method('getEntityType')->willReturn('custom'); @@ -408,6 +420,8 @@ public function testMatch() $this->storeManager->method('getStore')->willReturn($this->store); $this->request->method('getPathInfo') ->willReturn($requestPath); + $this->request->method('getRequestString') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); $urlRewrite->method('getRedirectType')->willReturn(0); diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Controller/UrlRewriteTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Controller/UrlRewriteTest.php index cea44226f6192..867fcd4c2f338 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/Controller/UrlRewriteTest.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Controller/UrlRewriteTest.php @@ -133,6 +133,21 @@ public function requestDataProvider(): array 'request' => '/page-external4?param1=custom1¶m2=custom2', 'redirect' => 'https://example.com/external2/?param2=value2', ], + 'Use Case #17: Rewrite: / --(301)--> /; No redirect' => [ + 'request' => '/', + 'redirect' => '/', + 'expectedCode' => HttpResponse::STATUS_CODE_200, + ], + 'Use Case #18: Rewrite: contact/ --(301)--> contact?param1=1; ' + . 'Request: contact/ --(301)--> contact?param1=1' => [ + 'request' => 'contact/', + 'redirect' => 'contact?param1=1', + ], + 'Use Case #19: Rewrite: contact/?param2=2 --(301)--> contact?param1=1¶m2=2; ' + . 'Request: contact/?¶m2=2 --(301)--> contact?param1=1¶m2=2' => [ + 'request' => 'contact/?¶m2=2', + 'redirect' => 'contact?param1=1¶m2=2', + ], ]; } } diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite.php index 68d1212539c6d..62aa27562537a 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite.php @@ -156,6 +156,24 @@ ->setDescription('From page-similar-query-param with trailing slash to page-e with query param'); $rewriteResource->save($rewrite); +$rewrite = $objectManager->create(UrlRewrite::class); +$rewrite->setEntityType('custom') + ->setRequestPath('/') + ->setTargetPath('/') + ->setRedirectType(OptionProvider::PERMANENT) + ->setStoreId($storeID) + ->setDescription('From / to /'); +$rewriteResource->save($rewrite); + +$rewrite = $objectManager->create(UrlRewrite::class); +$rewrite->setEntityType('custom') + ->setRequestPath('contact/') + ->setTargetPath('contact?param1=1') + ->setRedirectType(OptionProvider::PERMANENT) + ->setStoreId($storeID) + ->setDescription('From contact with trailing slash to contact with query param'); +$rewriteResource->save($rewrite); + $rewrite = $objectManager->create(UrlRewrite::class); $rewrite->setEntityType('custom') ->setRequestPath('page-external1') diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite_rollback.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite_rollback.php index a5c21f7a00e48..4d2c148141943 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite_rollback.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrite_rollback.php @@ -43,7 +43,9 @@ 'http://example.com/external', 'https://example.com/external2/', 'http://example.com/external?param1=value1', - 'https://example.com/external2/?param2=value2' + 'https://example.com/external2/?param2=value2', + '/', + 'contact?param1=1' ] ) ->load() From 1140cb7a518b61142077928c6da8be1f3195ba38 Mon Sep 17 00:00:00 2001 From: Tjitse <Tjitse@vendic.nl> Date: Thu, 27 Feb 2020 08:56:56 +0100 Subject: [PATCH 131/369] Fixing code style issues --- lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php b/lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php index fdb6dd5c816dc..646908f99693c 100644 --- a/lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php +++ b/lib/internal/Magento/Framework/Stdlib/DateTime/DateTime.php @@ -70,8 +70,7 @@ public function gmtDate($format = null, $input = null) } /** - * Converts input date into date with timezone offset - * Input date must be in GMT timezone + * Converts input date into date with timezone offset. Input date must be in GMT timezone. * * @param string $format * @param int|string $input date in GMT timezone @@ -114,8 +113,7 @@ public function gmtTimestamp($input = null) } /** - * Converts input date into timestamp with timezone offset - * Input date must be in GMT timezone + * Converts input date into timestamp with timezone offset. Input date must be in GMT timezone. * * @param int|string $input date in GMT timezone * @return int From b1d4c72e09d5d5e024d2a29efbceb51f6219eda8 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 27 Feb 2020 13:50:02 +0200 Subject: [PATCH 132/369] cover changes with setup-integration test --- .../Magento/TestSetupDeclarationModule1/etc/db_schema.xml | 1 + .../fixture/valid_xml_revision_1.php | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/etc/db_schema.xml b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/etc/db_schema.xml index ae6c98e4627d2..9de94cf0549be 100644 --- a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/etc/db_schema.xml +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/etc/db_schema.xml @@ -48,6 +48,7 @@ <column xsi:type="longtext" name="longtext"/> <column xsi:type="mediumtext" name="mediumtext"/> <column xsi:type="varchar" name="varchar" length="254" nullable="true"/> + <column xsi:type="char" name="char" length="255" nullable="true"/> <column xsi:type="mediumblob" name="mediumblob"/> <column xsi:type="blob" name="blob"/> <column xsi:type="boolean" name="boolean"/> diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/valid_xml_revision_1.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/valid_xml_revision_1.php index a064d096f6d38..0109b644e546f 100644 --- a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/valid_xml_revision_1.php +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/valid_xml_revision_1.php @@ -199,6 +199,12 @@ 'type' => 'mediumtext', 'name' => 'mediumtext', ], + 'char' => [ + 'type' => 'char', + 'name' => 'char', + 'length' => '255', + 'nullable' => 'true', + ], 'varchar' => [ 'type' => 'varchar', 'name' => 'varchar', From 502d66e2ff322bf2f3884f146f378228977b956a Mon Sep 17 00:00:00 2001 From: Alexander MEnk <a.menk@imi.de> Date: Thu, 27 Feb 2020 14:02:13 +0100 Subject: [PATCH 133/369] #23191 Fix static tests We have to ignore the CyclomaticComplexity of 11 of \Magento\CatalogImportExport\Model\Import\Product\LinkProcessor::processLinkBunches It's since we added the if() to check for empty links. I do not find a easy way to reduce it, because the if contains a continue statement. --- .../Model/Import/Product/LinkProcessor.php | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index 4c4438e3e9d7d..ddbbf242ee67a 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -15,7 +15,9 @@ use Psr\Log\LoggerInterface; /** - * Class LinkProcessor + * Processor for links between products + * + * Remark: Via DI it is possible to supply additional link types. */ class LinkProcessor { @@ -76,7 +78,7 @@ public function __construct( * @param Product $importEntity * @param Data $dataSourceModel * @param string $linkField - * @return $this + * @return void * @throws LocalizedException */ public function saveLinks( @@ -128,6 +130,7 @@ public function addNameToIds(array $nameToIds): void * * @return void * @throws LocalizedException + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function processLinkBunches( Product $importEntity, @@ -163,6 +166,7 @@ function ($linkName) use ($rowData) { $linksToDelete[$linkId][] = $productId; continue; } + $linkPositions = ! empty($rowData[$linkName . 'position']) ? explode($importEntity->getMultipleValueSeparator(), $rowData[$linkName . 'position']) : []; @@ -180,7 +184,7 @@ function ($linkedSku) use ($sku, $importEntity) { } ); foreach ($linkSkus as $linkedKey => $linkedSku) { - $linkedId = $this->getProductLinkedId($linkedSku); + $linkedId = $this->getProductLinkedId($importEntity, $linkedSku); if ($linkedId == null) { // Import file links to a SKU which is skipped for some reason, which leads to a "NULL" // link causing fatal errors. @@ -259,6 +263,19 @@ private function isSkuExist(Product $importEntity, string $sku): bool return isset($importEntity->getOldSku()[$sku]); } + /** + * Get existing SKU record + * + * @param Product $importEntity + * @param string $sku + * @return mixed + */ + private function getExistingSku(Product $importEntity, string $sku) + { + $sku = strtolower($sku); + return $importEntity->getOldSku()[$sku]; + } + /** * Fetches Product Links * @@ -289,15 +306,18 @@ private function fetchProductLinks(Product $importEntity, Link $resource, int $p /** * Gets the Id of the Sku * + * @param Product $importEntity * @param string $linkedSku - * * @return int|null */ - private function getProductLinkedId(string $linkedSku): ?int + private function getProductLinkedId(Product $importEntity, string $linkedSku): ?int { $linkedSku = trim($linkedSku); $newSku = $this->skuProcessor->getNewSku($linkedSku); - $linkedId = ! empty($newSku) ? $newSku['entity_id'] : $this->getExistingSku($linkedSku)['entity_id']; + + $linkedId = ! empty($newSku) ? + $newSku['entity_id'] : + $this->getExistingSku($importEntity, $linkedSku)['entity_id']; return $linkedId; } From e05bb102f5b8b50e014415927a415337291ceabb Mon Sep 17 00:00:00 2001 From: Alexander MEnk <a.menk@imi.de> Date: Thu, 27 Feb 2020 14:13:58 +0100 Subject: [PATCH 134/369] #23191 Reduce CyclomaticComplexity of processLinkBunches --- .../Model/Import/Product/LinkProcessor.php | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php index ddbbf242ee67a..a45338c391a58 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php @@ -130,7 +130,6 @@ public function addNameToIds(array $nameToIds): void * * @return void * @throws LocalizedException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function processLinkBunches( Product $importEntity, @@ -151,13 +150,8 @@ private function processLinkBunches( $productId = $this->skuProcessor->getNewSku($sku)[$linkField]; $productIds[] = $productId; $productLinkKeys = $this->fetchProductLinks($importEntity, $resource, $productId); - $linkNameToId = array_filter( - $this->linkNameToId, - function ($linkName) use ($rowData) { - return isset($rowData[$linkName . 'sku']); - }, - ARRAY_FILTER_USE_KEY - ); + $linkNameToId = $this->filterProvidedLinkTypes($rowData); + foreach ($linkNameToId as $linkName => $linkId) { $linkSkus = explode($importEntity->getMultipleValueSeparator(), $rowData[$linkName . 'sku']); @@ -171,18 +165,8 @@ function ($linkName) use ($rowData) { ? explode($importEntity->getMultipleValueSeparator(), $rowData[$linkName . 'position']) : []; - $linkSkus = array_filter( - $linkSkus, - function ($linkedSku) use ($sku, $importEntity) { - $linkedSku = trim($linkedSku); + $linkSkus = $this->filterValidLinks($importEntity, $sku, $linkSkus); - return ( - $this->skuProcessor->getNewSku($linkedSku) !== null - || $this->isSkuExist($importEntity, $linkedSku) - ) - && strcasecmp($linkedSku, $sku) !== 0; - } - ); foreach ($linkSkus as $linkedKey => $linkedSku) { $linkedId = $this->getProductLinkedId($importEntity, $linkedSku); if ($linkedId == null) { @@ -373,4 +357,45 @@ private function composeLinkKey(int $productId, int $linkedId, int $linkTypeId): { return "{$productId}-{$linkedId}-{$linkTypeId}"; } + + /** + * Filter out link types which are not provided in the rowData + * + * @param array $rowData + * @return array + */ + private function filterProvidedLinkTypes(array $rowData) + { + return array_filter( + $this->linkNameToId, + function ($linkName) use ($rowData) { + return isset($rowData[$linkName . 'sku']); + }, + ARRAY_FILTER_USE_KEY + ); + } + + /** + * Filter out invalid links + * + * @param Product $importEntity + * @param string $sku + * @param array $linkSkus + * @return array + */ + private function filterValidLinks(Product $importEntity, string $sku, array $linkSkus) + { + return array_filter( + $linkSkus, + function ($linkedSku) use ($sku, $importEntity) { + $linkedSku = trim($linkedSku); + + return ( + $this->skuProcessor->getNewSku($linkedSku) !== null + || $this->isSkuExist($importEntity, $linkedSku) + ) + && strcasecmp($linkedSku, $sku) !== 0; + } + ); + } } From 6070770d1fc2d22f275147c2b6972b9e15a248ba Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 27 Feb 2020 15:28:46 +0200 Subject: [PATCH 135/369] add cgar column to fixtures --- .../fixture/declarative_installer/column_modification.php | 1 + .../Magento/TestSetupDeclarationModule1/fixture/dry_run_log.php | 1 + 2 files changed, 2 insertions(+) diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/column_modification.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/column_modification.php index a69e456ec4a8b..f2a34c338edc2 100644 --- a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/column_modification.php +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/column_modification.php @@ -36,6 +36,7 @@ `longtext` longtext, `mediumtext` mediumtext, `varchar` varchar(100) DEFAULT NULL, + `char` char(255) DEFAULT NULL, `mediumblob` mediumblob, `blob` blob, `boolean` tinyint(1) DEFAULT \'1\', diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/dry_run_log.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/dry_run_log.php index e642e57701149..7735bd433d9d2 100644 --- a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/dry_run_log.php +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/dry_run_log.php @@ -38,6 +38,7 @@ `longtext` longtext NULL , `mediumtext` mediumtext NULL , `varchar` varchar(254) NULL , +`char` char(255) NULL , `mediumblob` mediumblob NULL , `blob` blob NULL , `boolean` BOOLEAN NULL , From aaac9b3173fcc00c555d6c10f37cdcdd8a9091ea Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Thu, 27 Feb 2020 15:10:37 +0000 Subject: [PATCH 136/369] Fixed directory isExists method handling of relative paths with double-dots --- .../Framework/Filesystem/Directory/ReadTest.php | 14 ++++++++++---- .../Framework/Filesystem/Directory/Read.php | 14 ++++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php index bc77eeb932c9a..e04063eabc36b 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Directory/ReadTest.php @@ -73,17 +73,17 @@ public function testGetRelativePathOutside() $exceptions = 0; $dir = $this->getDirectoryInstance('foo'); try { - $dir->getRelativePath(__DIR__ .'/ReadTest.php'); + $dir->getRelativePath(__DIR__ . '/ReadTest.php'); } catch (ValidatorException $exception) { $exceptions++; } try { - $dir->getRelativePath(__DIR__ .'//./..////Directory/ReadTest.php'); + $dir->getRelativePath(__DIR__ . '//./..////Directory/ReadTest.php'); } catch (ValidatorException $exception) { $exceptions++; } try { - $dir->getRelativePath(__DIR__ .'\..\Directory\ReadTest.php'); + $dir->getRelativePath(__DIR__ . '\..\Directory\ReadTest.php'); } catch (ValidatorException $exception) { $exceptions++; } @@ -222,7 +222,13 @@ public function testIsExist($dirPath, $path, $exists) */ public function existsProvider() { - return [['foo', 'bar', true], ['foo', 'bar/baz/', true], ['foo', 'bar/notexists', false]]; + return [ + ['foo', 'bar', true], + ['foo', 'bar/baz/', true], + ['foo', 'bar/notexists', false], + ['foo', 'foo/../bar/', true], + ['foo', 'foo/../notexists/', false] + ]; } public function testIsExistOutside() diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/Read.php b/lib/internal/Magento/Framework/Filesystem/Directory/Read.php index a3a4cec59953f..e23eadd57d866 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/Read.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/Read.php @@ -9,6 +9,7 @@ use Magento\Framework\Exception\ValidatorException; /** + * Filesystem directory instance for read operations * @api */ class Read implements ReadInterface @@ -40,8 +41,6 @@ class Read implements ReadInterface private $pathValidator; /** - * Constructor. Set properties. - * * @param \Magento\Framework\Filesystem\File\ReadFactory $fileFactory * @param \Magento\Framework\Filesystem\DriverInterface $driver * @param string $path @@ -60,6 +59,8 @@ public function __construct( } /** + * Validate the path is correct and within the directory + * * @param null|string $path * @param null|string $scheme * @param bool $absolutePath @@ -96,8 +97,7 @@ protected function setPath($path) } /** - * Retrieves absolute path - * E.g.: /var/www/application/file.txt + * Retrieves absolute path i.e. /var/www/application/file.txt * * @param string $path * @param string $scheme @@ -151,7 +151,7 @@ public function read($path = null) /** * Read recursively * - * @param null $path + * @param string|null $path * @throws ValidatorException * @return string[] */ @@ -207,7 +207,9 @@ public function isExist($path = null) { $this->validatePath($path); - return $this->driver->isExists($this->driver->getAbsolutePath($this->path, $path)); + return $this->driver->isExists( + $this->driver->getRealPathSafety($this->driver->getAbsolutePath($this->path, $path)) + ); } /** From d437cec4e0a794b54eb41f80b8c4499a29e3a2fe Mon Sep 17 00:00:00 2001 From: Alexander MEnk <a.menk@imi.de> Date: Thu, 27 Feb 2020 17:52:53 +0100 Subject: [PATCH 137/369] #23191 Fix static tests for B2B edition, which checks tests --- .../Unit/Model/Import/Product/LinkProcessorTest.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php index b402c9aa05922..a109b7029ff74 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php @@ -8,6 +8,9 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class LinkProcessorTest extends \PHPUnit\Framework\TestCase { /** @@ -63,10 +66,15 @@ protected function setUp() $this->resource = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Link::class); $this->resource->method('getMainTable')->willReturn('main_link_table'); - $this->linkFactory = $this->createPartialMock(\Magento\Catalog\Model\ResourceModel\Product\LinkFactory::class, ['create']); + $this->linkFactory = $this->createPartialMock( + \Magento\Catalog\Model\ResourceModel\Product\LinkFactory::class, + ['create'] + ); $this->linkFactory->method('create')->willReturn($this->resource); - $this->skuProcessor = $this->createMock(\Magento\CatalogImportExport\Model\Import\Product\SkuProcessor::class, []); + $this->skuProcessor = $this->createMock( + \Magento\CatalogImportExport\Model\Import\Product\SkuProcessor::class, + ); $this->logger = $this->createMock(\Psr\Log\LoggerInterface::class); } From 92ec5943ef3186f72a31214011abc5271db646a9 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 27 Feb 2020 20:04:25 +0200 Subject: [PATCH 138/369] added assertion --- .../fixture/declarative_installer/constraint_modification.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.php index 01e007edb7684..0b1ec6245478b 100644 --- a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.php +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.php @@ -38,6 +38,7 @@ `longtext` longtext, `mediumtext` mediumtext, `varchar` varchar(254) DEFAULT NULL, + `char` char(255) DEFAULT NULL, `mediumblob` mediumblob, `blob` blob, `boolean` tinyint(1) DEFAULT NULL, From 5ca49ae2b1a62cc87e8dd0c2989a05bd19ec74da Mon Sep 17 00:00:00 2001 From: Alexander MEnk <a.menk@imi.de> Date: Thu, 27 Feb 2020 20:16:53 +0100 Subject: [PATCH 139/369] #23191 Fix static tests: trailing comma in function call --- .../Test/Unit/Model/Import/Product/LinkProcessorTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php index a109b7029ff74..284ef0b783ec8 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/LinkProcessorTest.php @@ -73,7 +73,7 @@ protected function setUp() $this->linkFactory->method('create')->willReturn($this->resource); $this->skuProcessor = $this->createMock( - \Magento\CatalogImportExport\Model\Import\Product\SkuProcessor::class, + \Magento\CatalogImportExport\Model\Import\Product\SkuProcessor::class ); $this->logger = $this->createMock(\Psr\Log\LoggerInterface::class); } From 54f93e22ae3d9387d5a8c259d6933efbcd5461e8 Mon Sep 17 00:00:00 2001 From: Vaha <vaha@atwix.com> Date: Fri, 28 Feb 2020 09:03:21 +0200 Subject: [PATCH 140/369] fixed 'wrong image gallery', 'wrong gallery behavior when query url' for swatches, 'product image updating' for dropdown type, added MFTF tests to cover all cases: #26473, #26856, #26858 --- .../AdminAssignImageBaseRoleActionGroup.xml | 25 ++ .../Catalog/Test/Mftf/Data/ProductData.xml | 8 + ...rontConfigurableOptionsThumbImagesTest.xml | 228 ++++++++++++++++++ ...ramsConfigurableOptionsThumbImagesTest.xml | 38 +++ .../view/frontend/web/js/configurable.js | 43 +++- ...minUpdateAttributeInputTypeActionGroup.xml | 20 ++ ...nfigurableSwatchOptionsThumbImagesTest.xml | 48 ++++ ...nfigurableSwatchOptionsThumbImagesTest.xml | 54 +++++ .../view/base/web/js/swatch-renderer.js | 54 +++-- lib/web/mage/gallery/gallery.js | 2 +- 10 files changed, 488 insertions(+), 32 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageBaseRoleActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/ActionGroup/AdminUpdateAttributeInputTypeActionGroup.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableSwatchOptionsThumbImagesTest.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageBaseRoleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageBaseRoleActionGroup.xml new file mode 100644 index 0000000000000..aa5311d389d7a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageBaseRoleActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssignImageBaseRoleActionGroup"> + <annotations> + <description>Requires the navigation to the Product Creation page. Checks the Base Role area for image.</description> + </annotations> + <arguments> + <argument name="image"/> + </arguments> + <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageFile(image.fileName)}}" visible="false" stepKey="expandImages"/> + <waitForElementVisible selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="seeProductImageName"/> + <click selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="clickProductImage"/> + <waitForElementVisible selector="{{AdminProductImagesSection.altText}}" stepKey="seeAltTextSection"/> + <checkOption selector="{{AdminProductImagesSection.roleBase}}" stepKey="checkRoles"/> + <click selector="{{AdminSlideOutDialogSection.closeButton}}" stepKey="clickCloseButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index a44db8010a822..867f0fa9ccde5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -876,6 +876,14 @@ <data key="weight">5</data> <requiredEntity type="product_extension_attribute">EavStock100</requiredEntity> </entity> + <entity name="Magento2" type="image"> + <data key="title" unique="suffix">Magento2</data> + <data key="file_type">Upload File</data> + <data key="shareable">Yes</data> + <data key="file">magento2.jpg</data> + <data key="filename">magento2</data> + <data key="file_extension">jpg</data> + </entity> <entity name="Magento3" type="image"> <data key="title" unique="suffix">Magento3</data> <data key="price">1.00</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml new file mode 100644 index 0000000000000..134fd89c2c813 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml @@ -0,0 +1,228 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontConfigurableOptionsThumbImagesTest"> + <annotations> + <stories value="Configurable Product"/> + <title value="Check thumbnail images and active image for Configurable Product"/> + <description value="Login as admin, create attribute with two options, configurable product with two + associated simple products. Add few images for products, check the fotorama thumbnail images + (visible and active) for each selected option for the configurable product"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + + <!-- Create Default Category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create an attribute with two options to be used in the first child product --> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Add the attribute just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + + <!-- Get the first option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Get the second option of the attribute created --> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create Configurable product --> + <createData entity="BaseConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create a simple product and give it the attribute with the first option --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + + <!--Create a simple product and give it the attribute with the second option --> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Create the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Add the first simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + + <!-- Add the second simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <!-- ConfigProduct --> + <!-- Go to Product Page (ConfigProduct) --> + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="goToConfigProduct"> + <argument name="productId" value="$$createConfigProduct.id$$"/> + </actionGroup> + + <!--Switch to 'Default Store View' scope and open product page--> + <actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="SwitchDefaultStoreViewForConfigProduct"> + <argument name="storeViewName" value="'Default Store View'"/> + </actionGroup> + + <!-- Add images for ConfigProduct --> + <actionGroup ref="AddProductImageActionGroup" stepKey="addConfigProductMagento3"> + <argument name="image" value="Magento3"/> + </actionGroup> + + <actionGroup ref="AddProductImageActionGroup" stepKey="addConfigProductTestImageAdobe"> + <argument name="image" value="TestImageAdobe"/> + </actionGroup> + <actionGroup ref="AdminAssignImageBaseRoleActionGroup" stepKey="assignTestImageAdobeBaseRole"> + <argument name="image" value="TestImageAdobe"/> + </actionGroup> + + <!-- Save changes fot ConfigProduct --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveConfigProductProduct"/> + + <!-- ChildProduct1 --> + <!-- Go to Product Page (ChildProduct1) --> + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="goToChildProduct1"> + <argument name="productId" value="$$createConfigChildProduct1.id$$"/> + </actionGroup> + + <!--Switch to 'Default Store View' scope and open product page--> + <actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="SwitchDefaultStoreViewForChildProduct1"> + <argument name="storeViewName" value="'Default Store View'"/> + </actionGroup> + + <!-- Add images for ChildProduct1 --> + <actionGroup ref="AddProductImageActionGroup" stepKey="addChildProduct1ProductImage"> + <argument name="image" value="ProductImage"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addChildProduct1Magento2"> + <argument name="image" value="Magento2"/> + </actionGroup> + <actionGroup ref="AdminAssignImageRolesIfUnassignedActionGroup" stepKey="assignMagento2Role"> + <argument name="image" value="Magento2"/> + </actionGroup> + + <!-- Save changes fot ChildProduct1 --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveChildProduct1Product"/> + + <!-- ChildProduct2 --> + <!-- Go to Product Page (ChildProduct2) --> + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="goToChildProduct2"> + <argument name="productId" value="$$createConfigChildProduct2.id$$"/> + </actionGroup> + + <!--Switch to 'Default Store View' scope and open product page--> + <actionGroup ref="SwitchToTheNewStoreViewActionGroup" stepKey="SwitchDefaultStoreViewForChildProduct2"> + <argument name="storeViewName" value="'Default Store View'"/> + </actionGroup> + + <!-- Add image for ChildProduct2 --> + <actionGroup ref="AddProductImageActionGroup" stepKey="addChildProduct2TestImageNew"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!-- Save changes fot ChildProduct2 --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveChildProduct2Product"/> + </before> + <after> + <!-- Delete Created Data --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="logout" stepKey="logout"/> + + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + </after> + + <!-- Open ConfigProduct in Store Front Page --> + <amOnPage url="$$createConfigProduct.sku$$.html" stepKey="openProductInStoreFront"/> + <waitForPageLoad stepKey="waitForProductToLoad"/> + + <!-- Check fotorama thumbnail images (no selected options) --> + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeMagento3ForNoOption"> + <argument name="fileName" value="{{Magento3.filename}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeActiveTestImageAdobeForNoOption"> + <argument name="fileName" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + + <!-- Select first option --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectFirstOptionValue"> + <argument name="attributeLabel" value="$$createConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$getConfigAttributeOption1.label$$"/> + </actionGroup> + + <!-- Check fotorama thumbnail images (first option selected) --> + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeMagento3ForFirstOption"> + <argument name="fileName" value="{{Magento3.filename}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeTestImageAdobeForFirstOption"> + <argument name="fileName" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeProductImageForFirstOption"> + <argument name="fileName" value="{{ProductImage.filename}}"/> + </actionGroup> + + <!-- Check active fotorama thumbnail image (first option selected) --> + <actionGroup ref="StorefrontAssertActiveProductImageActionGroup" stepKey="seeActiveMagento2ForFirstOption"> + <argument name="fileName" value="{{Magento2.filename}}"/> + </actionGroup> + + <!-- Select second option --> + <actionGroup ref="StorefrontProductPageSelectDropDownOptionValueActionGroup" stepKey="selectSecondOptionValue"> + <argument name="attributeLabel" value="$$createConfigProductAttribute.default_frontend_label$$"/> + <argument name="optionLabel" value="$$getConfigAttributeOption2.label$$"/> + </actionGroup> + + <!-- Check fotorama thumbnail images (second option selected) --> + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeMagento3ForSecondOption"> + <argument name="fileName" value="{{Magento3.filename}}"/> + </actionGroup> + + <actionGroup ref="StorefrontAssertFotoramaImageAvailabilityActionGroup" stepKey="seeTestImageAdobeForSecondOption"> + <argument name="fileName" value="{{TestImageAdobe.filename}}"/> + </actionGroup> + + <!-- Check active fotorama thumbnail image (second option selected) --> + <actionGroup ref="StorefrontAssertActiveProductImageActionGroup" stepKey="seeActiveTestImageNewForSecondOption"> + <argument name="fileName" value="{{TestImageNew.filename}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest.xml new file mode 100644 index 0000000000000..941ae6fb84c56 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontSelectedByQueryParamsConfigurableOptionsThumbImagesTest" + extends="StorefrontConfigurableOptionsThumbImagesTest"> + <annotations> + <stories value="Configurable Product"/> + <title value="Check thumbnail images and active image for Configurable Product with predefined + by query params options"/> + <description value="Login as admin, create attribute with two options, configurable product with two + associated simple products. Add few images for products, check the fotorama thumbnail images + (visible and active) for each option for the configurable product using product URL with params + to selected needed option."/> + <group value="catalog"/> + </annotations> + + <!-- Select first option using product query params URL --> + <amOnPage + url="$$createConfigProduct.sku$$.html#$$createConfigProductAttribute.attribute_id$$=$$getConfigAttributeOption1.value$$" + stepKey="selectFirstOptionValue"/> + <reloadPage stepKey="selectFirstOptionValueRefreshPage" after="selectFirstOptionValue"/> + <waitForPageLoad stepKey="waitForProductWithSelectedFirstOptionToLoad" after="selectFirstOptionValueRefreshPage"/> + + <!-- Select second option using product query params URL --> + <amOnPage + url="$$createConfigProduct.sku$$.html#$$createConfigProductAttribute.attribute_id$$=$$getConfigAttributeOption2.value$$" + stepKey="selectSecondOptionValue"/> + <reloadPage stepKey="selectSecondOptionValueRefreshPage" after="selectSecondOptionValue"/> + <waitForPageLoad stepKey="waitForProductWithSelectedSecondOptionToLoad" after="selectSecondOptionValueRefreshPage"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index 6f3af43bf5c7a..8fd1d761040d7 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -13,7 +13,8 @@ define([ 'priceUtils', 'priceBox', 'jquery-ui-modules/widget', - 'jquery/jquery.parsequery' + 'jquery/jquery.parsequery', + 'fotoramaVideoEvents' ], function ($, _, mageTemplate, $t, priceUtils) { 'use strict'; @@ -307,9 +308,13 @@ define([ _changeProductImage: function () { var images, initialImages = this.options.mediaGalleryInitial, - galleryObject = $(this.options.mediaGallerySelector).data('gallery'); + gallery = $(this.options.mediaGallerySelector).data('gallery'); + + if (_.isUndefined(gallery)) { + $(this.options.mediaGallerySelector).on('gallery:loaded', function () { + this._changeProductImage(); + }.bind(this)); - if (!galleryObject) { return; } @@ -325,17 +330,35 @@ define([ images = $.extend(true, [], images); images = this._setImageIndex(images); - galleryObject.updateData(images); - - $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({ - selectedOption: this.simpleProduct, - dataMergeStrategy: this.options.gallerySwitchStrategy - }); + gallery.updateData(images); + this._addFotoramaVideoEvents(false); } else { - galleryObject.updateData(initialImages); + gallery.updateData(initialImages); + this._addFotoramaVideoEvents(true); + } + }, + + /** + * Add video events + * + * @param {Boolean} isInitial + * @private + */ + _addFotoramaVideoEvents: function (isInitial) { + if (_.isUndefined($.mage.AddFotoramaVideoEvents)) { + return; + } + + if (isInitial) { $(this.options.mediaGallerySelector).AddFotoramaVideoEvents(); + + return; } + $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({ + selectedOption: this.simpleProduct, + dataMergeStrategy: this.options.gallerySwitchStrategy + }); }, /** diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AdminUpdateAttributeInputTypeActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AdminUpdateAttributeInputTypeActionGroup.xml new file mode 100644 index 0000000000000..23264601cad94 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AdminUpdateAttributeInputTypeActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminUpdateAttributeInputTypeActionGroup"> + <annotations> + <description>Set value for the "Catalog Input Type for Store Owner" attribute option</description> + </annotations> + <arguments> + <argument name="value" type="string" defaultValue="swatch_visual"/> + </arguments> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="{{value}}" stepKey="setInputType"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableSwatchOptionsThumbImagesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableSwatchOptionsThumbImagesTest.xml new file mode 100644 index 0000000000000..8c90d88f51a10 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableSwatchOptionsThumbImagesTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontConfigurableSwatchOptionsThumbImagesTest" + extends="StorefrontConfigurableOptionsThumbImagesTest"> + <annotations> + <features value="Swatches"/> + <stories value="Configurable product with swatch attribute"/> + <title value="Check thumbnail images and active image for Configurable Product with swatch attribute"/> + <description value="Login as admin, create attribute with two options, configurable product with two + associated simple products. Add few images for products, check the fotorama thumbnail images + (visible and active) for each selected option for the configurable product"/> + <group value="swatches"/> + </annotations> + <before> + <!-- Go to created attribute (attribute page) --> + <actionGroup ref="NavigateToEditProductAttributeActionGroup" stepKey="navigateToSkuProductAttribute" after="createConfigProductOption"> + <argument name="ProductAttribute" value="$$createConfigProductAttribute.default_frontend_label$$"/> + </actionGroup> + + <!-- Set 'swatch_visual' value for option "Catalog Input Type for Store Owner" --> + <actionGroup ref="AdminUpdateAttributeInputTypeActionGroup" stepKey="selectSwatchVisualInputType" after="navigateToSkuProductAttribute"/> + + <!-- Set 'yes' value for option "Update Product Preview Image" --> + <actionGroup ref="AdminUpdateProductPreviewImageActionGroup" stepKey="setUpdateProductPreviewImage" after="selectSwatchVisualInputType"/> + + <!-- Save Product Attribute --> + <actionGroup ref="SaveProductAttributeActionGroup" stepKey="saveAttribute" after="setUpdateProductPreviewImage"/> + </before> + + <!-- Select first option --> + <actionGroup ref="StorefrontSelectSwatchOptionOnProductPageActionGroup" stepKey="selectFirstOptionValue"> + <argument name="optionName" value="$$getConfigAttributeOption1.label$$"/> + </actionGroup> + + <!-- Select second option --> + <actionGroup ref="StorefrontSelectSwatchOptionOnProductPageActionGroup" stepKey="selectSecondOptionValue"> + <argument name="optionName" value="$$getConfigAttributeOption2.label$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest.xml new file mode 100644 index 0000000000000..8e4c6c0217b3a --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontSelectedByQueryParamsConfigurableSwatchOptionsThumbImagesTest" + extends="StorefrontConfigurableOptionsThumbImagesTest"> + <annotations> + <features value="Swatches"/> + <stories value="Configurable product with swatch attribute"/> + <title value="Check thumbnail images and active image for Configurable Product with swatch attribute + with predefined by query params options"/> + <description value="Login as admin, create swatch attribute with two options, configurable product with two + associated simple products. Add few images for products, check the fotorama thumbnail images + (visible and active) for each option for the configurable product using product URL with params + to selected needed option."/> + <group value="swatches"/> + </annotations> + <before> + <!-- Go to created attribute (attribute page) --> + <actionGroup ref="NavigateToEditProductAttributeActionGroup" stepKey="navigateToSkuProductAttribute" after="createConfigProductOption"> + <argument name="ProductAttribute" value="$$createConfigProductAttribute.default_frontend_label$$"/> + </actionGroup> + + <!-- Set 'swatch_visual' value for option "Catalog Input Type for Store Owner" --> + <actionGroup ref="AdminUpdateAttributeInputTypeActionGroup" stepKey="selectSwatchVisualInputType" after="navigateToSkuProductAttribute"/> + + <!-- Set 'yes' value for option "Update Product Preview Image" --> + <actionGroup ref="AdminUpdateProductPreviewImageActionGroup" stepKey="setUpdateProductPreviewImage" after="selectSwatchVisualInputType"/> + + <!-- Save Product Attribute --> + <actionGroup ref="SaveProductAttributeActionGroup" stepKey="saveAttribute" after="setUpdateProductPreviewImage"/> + </before> + + <!-- Select first option using product query params URL --> + <amOnPage + url="$$createConfigProduct.sku$$.html#$$createConfigProductAttribute.attribute_id$$=$$getConfigAttributeOption1.value$$" + stepKey="selectFirstOptionValue"/> + <reloadPage stepKey="selectFirstOptionValueRefreshPage" after="selectFirstOptionValue"/> + <waitForPageLoad stepKey="waitForProductWithSelectedFirstOptionToLoad" after="selectFirstOptionValueRefreshPage"/> + + <!-- Select second option using product query params URL --> + <amOnPage + url="$$createConfigProduct.sku$$.html#$$createConfigProductAttribute.attribute_id$$=$$getConfigAttributeOption2.value$$" + stepKey="selectSecondOptionValue"/> + <reloadPage stepKey="selectSecondOptionValueRefreshPage" after="selectSecondOptionValue"/> + <waitForPageLoad stepKey="waitForProductWithSelectedSecondOptionToLoad" after="selectSecondOptionValueRefreshPage"/> + </test> +</tests> diff --git a/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js index 8b5dfcd80deb4..3e100d2c39168 100644 --- a/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/base/web/js/swatch-renderer.js @@ -1267,6 +1267,14 @@ define([ isInitial; if (isInProductView) { + if (_.isUndefined(gallery)) { + context.find(this.options.mediaGallerySelector).on('gallery:loaded', function () { + this.updateBaseImage(images, context, isInProductView); + }.bind(this)); + + return; + } + imagesToUpdate = images.length ? this._setImageType($.extend(true, [], images)) : []; isInitial = _.isEqual(imagesToUpdate, initialImages); @@ -1276,32 +1284,36 @@ define([ imagesToUpdate = this._setImageIndex(imagesToUpdate); - if (!_.isUndefined(gallery)) { - gallery.updateData(imagesToUpdate); - } else { - context.find(this.options.mediaGallerySelector).on('gallery:loaded', function (loadedGallery) { - loadedGallery = context.find(this.options.mediaGallerySelector).data('gallery'); - loadedGallery.updateData(imagesToUpdate); - }.bind(this)); - } - - if (isInitial) { - $(this.options.mediaGallerySelector).AddFotoramaVideoEvents(); - } else { - $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({ - selectedOption: this.getProduct(), - dataMergeStrategy: this.options.gallerySwitchStrategy - }); - } - - if (gallery) { - gallery.first(); - } + gallery.updateData(imagesToUpdate); + this._addFotoramaVideoEvents(isInitial); } else if (justAnImage && justAnImage.img) { context.find('.product-image-photo').attr('src', justAnImage.img); } }, + /** + * Add video events + * + * @param {Boolean} isInitial + * @private + */ + _addFotoramaVideoEvents: function (isInitial) { + if (_.isUndefined($.mage.AddFotoramaVideoEvents)) { + return; + } + + if (isInitial) { + $(this.options.mediaGallerySelector).AddFotoramaVideoEvents(); + + return; + } + + $(this.options.mediaGallerySelector).AddFotoramaVideoEvents({ + selectedOption: this.getProduct(), + dataMergeStrategy: this.options.gallerySwitchStrategy + }); + }, + /** * Set correct indexes for image set. * diff --git a/lib/web/mage/gallery/gallery.js b/lib/web/mage/gallery/gallery.js index 65a14f77de257..02ba72f64127b 100644 --- a/lib/web/mage/gallery/gallery.js +++ b/lib/web/mage/gallery/gallery.js @@ -480,7 +480,7 @@ define([ settings.fotoramaApi.load(data); mainImageIndex = getMainImageIndex(data); - if (mainImageIndex) { + if (settings.fotoramaApi.activeIndex !== mainImageIndex) { settings.fotoramaApi.show({ index: mainImageIndex, time: 0 From 4e540340bb74de1c40f3ad5c1196ffcc6057aff9 Mon Sep 17 00:00:00 2001 From: "Galla, Daniel" <d.galla@imi.de> Date: Fri, 28 Feb 2020 13:04:13 +0100 Subject: [PATCH 141/369] #26499 Use product urlKey instead of name --- .../Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml | 2 +- .../Test/AdminCreateAndEditConfigurableProductSettingsTest.xml | 2 +- .../Test/AdminCreateAndEditDownloadableProductSettingsTest.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml index a80d5f040f825..7aa1126f38923 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml @@ -126,7 +126,7 @@ <!-- Verify Url Key after changing --> <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> - <argument name="productUrl" value="{{ApiBundleProduct.name}}"/> + <argument name="productUrl" value="{{ApiBundleProduct.urlKey}}"/> </actionGroup> <!-- Assert product design settings "Layout empty" --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndEditConfigurableProductSettingsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndEditConfigurableProductSettingsTest.xml index 6632cbcee30f2..e07e288dc00bf 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndEditConfigurableProductSettingsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndEditConfigurableProductSettingsTest.xml @@ -91,7 +91,7 @@ <!-- Verify Url Key after changing --> <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> - <argument name="productUrl" value="{{ApiConfigurableProduct.name}}"/> + <argument name="productUrl" value="{{ApiConfigurableProduct.urlKey}}"/> </actionGroup> <!-- Assert product design settings "Layout empty" --> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndEditDownloadableProductSettingsTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndEditDownloadableProductSettingsTest.xml index ebd36dddc0b6c..b52e378ac56b3 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndEditDownloadableProductSettingsTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndEditDownloadableProductSettingsTest.xml @@ -97,7 +97,7 @@ <!-- Verify Url Key after changing --> <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> - <argument name="productUrl" value="{{ApiDownloadableProduct.name}}"/> + <argument name="productUrl" value="{{ApiDownloadableProduct.urlKey}}"/> </actionGroup> <!-- Assert product design settings "left bar is present at product page with 2 columns" --> From 97642b5f28da27bec827c906dc2a096aa95fb502 Mon Sep 17 00:00:00 2001 From: Sathish <srsathish92@gmail.com> Date: Sat, 29 Feb 2020 00:46:37 +0530 Subject: [PATCH 142/369] Fixed alignment of other checkboxes --- .../Magento_Catalog/web/css/source/_module.less | 13 ++++++++++++- .../Magento_Customer/web/css/source/_module.less | 6 ------ .../Magento_Review/web/css/source/_module.less | 8 -------- .../backend/Magento_Tax/web/css/source/_module.less | 6 ------ .../backend/web/css/source/forms/_fields.less | 2 +- 5 files changed, 13 insertions(+), 22 deletions(-) diff --git a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less index ffbbaeb084162..f494bb0ad0088 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less @@ -20,7 +20,7 @@ .admin__grid-control-value { display: none; } - } + } } .product-composite-configure-inner { @@ -102,3 +102,14 @@ } } } + +// +// Catalog Product Edit Action Attribute +// --------------------------------------------- +.admin__field-control { + .attribute-change-checkbox { + input[type='checkbox'].checkbox { + margin-top: 0; + } + } +} diff --git a/app/design/adminhtml/Magento/backend/Magento_Customer/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Customer/web/css/source/_module.less index 0c7dd7e7cb94c..e526ae4857469 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Customer/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Customer/web/css/source/_module.less @@ -101,10 +101,4 @@ } } } - - > .admin__field > .admin__field-control { - input[type='checkbox'] { - margin: 8px 0 0 0; - } - } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less index 9eae708819a0b..08606402f7a0e 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less @@ -77,11 +77,3 @@ } } } - -.review-rating-edit { - .admin__field-control.control { - input[type='checkbox'] { - margin: 8px 0 0 0; - } - } -} diff --git a/app/design/adminhtml/Magento/backend/Magento_Tax/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Tax/web/css/source/_module.less index 2377f7c9a9c11..49459bc11cfb2 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Tax/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Tax/web/css/source/_module.less @@ -25,9 +25,3 @@ font-size: 1.3rem; } } - -.admin__fieldset > .admin__field > .admin__field-control { - input.zip-is-range-checkbox { - margin: 8px 0 0 0; - } -} diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index bfb515c700b33..256ac453578df 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -122,7 +122,7 @@ > .admin__field-control { #mix-grid .column(@field-control-grid__column, @field-grid__columns); input[type="checkbox"] { - margin-top: 0; + margin-top: @indent__s; } } From bcadcfb6cd7f6957618d77f50cf199aae85d3c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Sat, 29 Feb 2020 00:46:26 +0100 Subject: [PATCH 143/369] Cleanup ObjectManager usage - Magento_Analytics --- .../System/Config/CollectionTimeLabel.php | 31 +++++++----- .../System/Config/CollectionTimeLabelTest.php | 50 ++++++++++++------- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php index 34f2b7d53d9be..62ef86c7dafb5 100644 --- a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php @@ -3,42 +3,47 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Analytics\Block\Adminhtml\System\Config; -use Magento\Framework\App\ObjectManager; +use Magento\Backend\Block\Template\Context; +use Magento\Config\Block\System\Config\Form\Field; +use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\Locale\ResolverInterface; /** * Provides label with default Time Zone */ -class CollectionTimeLabel extends \Magento\Config\Block\System\Config\Form\Field +class CollectionTimeLabel extends Field { /** - * @var \Magento\Framework\Locale\ResolverInterface + * @var ResolverInterface */ private $localeResolver; /** - * @param \Magento\Backend\Block\Template\Context $context + * @param Context $context + * @param ResolverInterface $localeResolver * @param array $data - * @param \Magento\Framework\Locale\ResolverInterface|null $localeResolver */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - array $data = [], - \Magento\Framework\Locale\ResolverInterface $localeResolver = null + Context $context, + ResolverInterface $localeResolver, + array $data = [] ) { - $this->localeResolver = $localeResolver ?: - ObjectManager::getInstance()->get(\Magento\Framework\Locale\ResolverInterface::class); parent::__construct($context, $data); + $this->localeResolver = $localeResolver; } /** * Add current time zone to comment, properly translated according to locale * - * @param \Magento\Framework\Data\Form\Element\AbstractElement $element + * @param AbstractElement $element + * * @return string */ - public function render(\Magento\Framework\Data\Form\Element\AbstractElement $element) + public function render(AbstractElement $element) { $timeZoneCode = $this->_localeDate->getConfigTimezone(); $locale = $this->localeResolver->getLocale(); @@ -46,7 +51,7 @@ public function render(\Magento\Framework\Data\Form\Element\AbstractElement $ele ->getDisplayName(false, \IntlTimeZone::DISPLAY_LONG, $locale); $element->setData( 'comment', - sprintf("%s (%s)", $getLongTimeZoneName, $timeZoneCode) + sprintf('%s (%s)', $getLongTimeZoneName, $timeZoneCode) ); return parent::render($element); } diff --git a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php index 08ee3c356937a..3a98fd6acbf4d 100644 --- a/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Block/Adminhtml/System/Config/CollectionTimeLabelTest.php @@ -3,17 +3,25 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Analytics\Test\Unit\Block\Adminhtml\System\Config; use Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel; use Magento\Backend\Block\Template\Context; use Magento\Framework\Data\Form; use Magento\Framework\Data\Form\Element\AbstractElement; +use Magento\Framework\Escaper; use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class CollectionTimeLabelTest extends \PHPUnit\Framework\TestCase +/** + * Test class for \Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel + */ +class CollectionTimeLabelTest extends TestCase { /** * @var CollectionTimeLabel @@ -21,25 +29,33 @@ class CollectionTimeLabelTest extends \PHPUnit\Framework\TestCase private $collectionTimeLabel; /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|MockObject */ private $contextMock; /** - * @var TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ResolverInterface|MockObject + */ + private $localeResolverMock; + + /** + * @var Form|MockObject + */ + private $formMock; + + /** + * @var TimezoneInterface|MockObject */ private $timeZoneMock; /** - * @var AbstractElement|\PHPUnit_Framework_MockObject_MockObject + * @var AbstractElement|MockObject */ private $abstractElementMock; /** - * @var ResolverInterface|\PHPUnit_Framework_MockObject_MockObject + * @inheritDoc */ - private $localeResolver; - protected function setUp() { $this->abstractElementMock = $this->getMockBuilder(AbstractElement::class) @@ -48,7 +64,7 @@ protected function setUp() ->getMock(); $objectManager = new ObjectManager($this); - $escaper = $objectManager->getObject(\Magento\Framework\Escaper::class); + $escaper = $objectManager->getObject(Escaper::class); $reflection = new \ReflectionClass($this->abstractElementMock); $reflection_property = $reflection->getProperty('_escaper'); $reflection_property->setAccessible(true); @@ -64,35 +80,35 @@ protected function setUp() $this->timeZoneMock = $this->getMockBuilder(TimezoneInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->contextMock->expects($this->any()) - ->method('getLocaleDate') + $this->contextMock->method('getLocaleDate') ->willReturn($this->timeZoneMock); - $this->localeResolver = $this->getMockBuilder(ResolverInterface::class) + $this->localeResolverMock = $this->getMockBuilder(ResolverInterface::class) ->disableOriginalConstructor() ->setMethods(['getLocale']) ->getMockForAbstractClass(); - $objectManager = new ObjectManager($this); $this->collectionTimeLabel = $objectManager->getObject( CollectionTimeLabel::class, [ 'context' => $this->contextMock, - 'localeResolver' => $this->localeResolver + 'localeResolver' => $this->localeResolverMock ] ); } + /** + * Test for \Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel::render() + */ public function testRender() { - $timeZone = "America/New_York"; + $timeZone = 'America/New_York'; $this->abstractElementMock->setForm($this->formMock); $this->timeZoneMock->expects($this->once()) ->method('getConfigTimezone') ->willReturn($timeZone); - $this->abstractElementMock->expects($this->any()) - ->method('getComment') + $this->abstractElementMock->method('getComment') ->willReturn('Eastern Standard Time (America/New_York)'); - $this->localeResolver->expects($this->once()) + $this->localeResolverMock->expects($this->once()) ->method('getLocale') ->willReturn('en_US'); $this->assertRegExp( From 3cdbc41df134ae477d5895eba65ee3560cd04b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Sat, 29 Feb 2020 00:55:33 +0100 Subject: [PATCH 144/369] Cleanup ObjectManager usage - Magento_AsynchronousOperations --- .../Model/BulkManagement.php | 31 ++++++++++--------- .../Model/MassConsumer.php | 20 ++++++------ .../Model/MassSchedule.php | 30 +++++++++--------- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php b/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php index faf01921e5737..983bac21f3c1b 100644 --- a/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php +++ b/app/code/Magento/AsynchronousOperations/Model/BulkManagement.php @@ -3,25 +3,26 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\AsynchronousOperations\Model; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\App\ResourceConnection; use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterface; use Magento\AsynchronousOperations\Api\Data\BulkSummaryInterfaceFactory; use Magento\AsynchronousOperations\Api\Data\OperationInterface; -use Magento\Framework\MessageQueue\BulkPublisherInterface; -use Magento\Framework\EntityManager\EntityManager; -use Magento\Framework\EntityManager\MetadataPool; use Magento\AsynchronousOperations\Model\ResourceModel\Operation\CollectionFactory; use Magento\Authorization\Model\UserContextInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Bulk\BulkManagementInterface; +use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\MessageQueue\BulkPublisherInterface; +use Psr\Log\LoggerInterface; /** - * Class BulkManagement - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * Asynchronous Bulk Management */ -class BulkManagement implements \Magento\Framework\Bulk\BulkManagementInterface +class BulkManagement implements BulkManagementInterface { /** * @var EntityManager @@ -54,12 +55,12 @@ class BulkManagement implements \Magento\Framework\Bulk\BulkManagementInterface private $resourceConnection; /** - * @var \Magento\Authorization\Model\UserContextInterface + * @var UserContextInterface */ private $userContext; /** - * @var \Psr\Log\LoggerInterface + * @var LoggerInterface */ private $logger; @@ -71,7 +72,7 @@ class BulkManagement implements \Magento\Framework\Bulk\BulkManagementInterface * @param BulkPublisherInterface $publisher * @param MetadataPool $metadataPool * @param ResourceConnection $resourceConnection - * @param \Psr\Log\LoggerInterface $logger + * @param LoggerInterface $logger * @param UserContextInterface $userContext */ public function __construct( @@ -81,8 +82,8 @@ public function __construct( BulkPublisherInterface $publisher, MetadataPool $metadataPool, ResourceConnection $resourceConnection, - \Psr\Log\LoggerInterface $logger, - UserContextInterface $userContext = null + LoggerInterface $logger, + UserContextInterface $userContext ) { $this->entityManager = $entityManager; $this->bulkSummaryFactory= $bulkSummaryFactory; @@ -91,7 +92,7 @@ public function __construct( $this->resourceConnection = $resourceConnection; $this->publisher = $publisher; $this->logger = $logger; - $this->userContext = $userContext ?: ObjectManager::getInstance()->get(UserContextInterface::class); + $this->userContext = $userContext; } /** diff --git a/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php index e3ba8b0681971..e2f756a9e8fcd 100644 --- a/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php +++ b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php @@ -3,17 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\AsynchronousOperations\Model; -use Magento\Framework\Registry; use Magento\Framework\MessageQueue\CallbackInvokerInterface; use Magento\Framework\MessageQueue\ConsumerConfigurationInterface; +use Magento\Framework\MessageQueue\ConsumerInterface; use Magento\Framework\MessageQueue\EnvelopeInterface; use Magento\Framework\MessageQueue\QueueInterface; -use Magento\Framework\MessageQueue\ConsumerInterface; +use Magento\Framework\Registry; /** * Class Consumer used to process OperationInterface messages. @@ -28,19 +27,19 @@ class MassConsumer implements ConsumerInterface private $invoker; /** - * @var \Magento\Framework\MessageQueue\ConsumerConfigurationInterface + * @var ConsumerConfigurationInterface */ private $configuration; /** - * @var Registry + * @var MassConsumerEnvelopeCallbackFactory */ - private $registry; + private $massConsumerEnvelopeCallback; /** - * @var MassConsumerEnvelopeCallbackFactory + * @var Registry */ - private $massConsumerEnvelopeCallback; + private $registry; /** * Initialize dependencies. @@ -54,13 +53,12 @@ public function __construct( CallbackInvokerInterface $invoker, ConsumerConfigurationInterface $configuration, MassConsumerEnvelopeCallbackFactory $massConsumerEnvelopeCallback, - Registry $registry = null + Registry $registry ) { $this->invoker = $invoker; $this->configuration = $configuration; $this->massConsumerEnvelopeCallback = $massConsumerEnvelopeCallback; - $this->registry = $registry ?? \Magento\Framework\App\ObjectManager::getInstance() - ->get(Registry::class); + $this->registry = $registry; } /** diff --git a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php index 89d468159c6e9..6e36339b8bff1 100644 --- a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php +++ b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php @@ -3,24 +3,22 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\AsynchronousOperations\Model; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\DataObject\IdentityGeneratorInterface; -use Magento\Framework\Exception\LocalizedException; -use Magento\AsynchronousOperations\Api\Data\ItemStatusInterfaceFactory; use Magento\AsynchronousOperations\Api\Data\AsyncResponseInterface; use Magento\AsynchronousOperations\Api\Data\AsyncResponseInterfaceFactory; use Magento\AsynchronousOperations\Api\Data\ItemStatusInterface; -use Magento\Framework\Bulk\BulkManagementInterface; -use Magento\Framework\Exception\BulkException; -use Psr\Log\LoggerInterface; +use Magento\AsynchronousOperations\Api\Data\ItemStatusInterfaceFactory; use Magento\AsynchronousOperations\Model\ResourceModel\Operation\OperationRepository; use Magento\Authorization\Model\UserContextInterface; +use Magento\Framework\Bulk\BulkManagementInterface; +use Magento\Framework\DataObject\IdentityGeneratorInterface; use Magento\Framework\Encryption\Encryptor; +use Magento\Framework\Exception\BulkException; +use Magento\Framework\Exception\LocalizedException; +use Psr\Log\LoggerInterface; /** * Class MassSchedule used for adding multiple entities as Operations to Bulk Management with the status tracking @@ -30,7 +28,7 @@ class MassSchedule { /** - * @var \Magento\Framework\DataObject\IdentityGeneratorInterface + * @var IdentityGeneratorInterface */ private $identityService; @@ -45,7 +43,7 @@ class MassSchedule private $itemStatusInterfaceFactory; /** - * @var \Magento\Framework\Bulk\BulkManagementInterface + * @var BulkManagementInterface */ private $bulkManagement; @@ -60,7 +58,7 @@ class MassSchedule private $operationRepository; /** - * @var \Magento\Authorization\Model\UserContextInterface + * @var UserContextInterface */ private $userContext; @@ -79,7 +77,7 @@ class MassSchedule * @param LoggerInterface $logger * @param OperationRepository $operationRepository * @param UserContextInterface $userContext - * @param Encryptor|null $encryptor + * @param Encryptor $encryptor */ public function __construct( IdentityGeneratorInterface $identityService, @@ -88,8 +86,8 @@ public function __construct( BulkManagementInterface $bulkManagement, LoggerInterface $logger, OperationRepository $operationRepository, - UserContextInterface $userContext = null, - Encryptor $encryptor = null + UserContextInterface $userContext, + Encryptor $encryptor ) { $this->identityService = $identityService; $this->itemStatusInterfaceFactory = $itemStatusInterfaceFactory; @@ -97,8 +95,8 @@ public function __construct( $this->bulkManagement = $bulkManagement; $this->logger = $logger; $this->operationRepository = $operationRepository; - $this->userContext = $userContext ?: ObjectManager::getInstance()->get(UserContextInterface::class); - $this->encryptor = $encryptor ?: ObjectManager::getInstance()->get(Encryptor::class); + $this->userContext = $userContext; + $this->encryptor = $encryptor; } /** From 807f055d704f5c8b8172dacaaad608826db22df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Fri, 21 Feb 2020 22:53:00 +0100 Subject: [PATCH 145/369] Cleanup ObjectManager usage - Magento_EncryptionKey --- .../Controller/Adminhtml/Crypt/Key/Index.php | 38 ++++++++--- .../Setup/Patch/Data/SodiumChachaPatch.php | 64 +++++++++++-------- app/code/Magento/EncryptionKey/etc/di.xml | 14 ++++ 3 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 app/code/Magento/EncryptionKey/etc/di.xml diff --git a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php index 86fc0082f7a5a..9b841fc32c383 100644 --- a/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php +++ b/app/code/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/Index.php @@ -1,18 +1,40 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Backend\App\Action\Context; +use Magento\EncryptionKey\Block\Adminhtml\Crypt\Key\Form; +use Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\DeploymentConfig\Writer; /** * Key Index action */ -class Index extends \Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key implements HttpGetActionInterface +class Index extends Key implements HttpGetActionInterface { + /** + * @var Writer + */ + private $writer; + + /** + * @param Context $context + * @param Writer $writer + */ + public function __construct( + Context $context, + Writer $writer + ) { + parent::__construct($context); + $this->writer = $writer; + } + /** * Render main page with form * @@ -20,10 +42,8 @@ class Index extends \Magento\EncryptionKey\Controller\Adminhtml\Crypt\Key implem */ public function execute() { - /** @var \Magento\Framework\App\DeploymentConfig\Writer $writer */ - $writer = $this->_objectManager->get(\Magento\Framework\App\DeploymentConfig\Writer::class); - if (!$writer->checkIfWritable()) { - $this->messageManager->addError(__('Deployment configuration file is not writable.')); + if (!$this->writer->checkIfWritable()) { + $this->messageManager->addErrorMessage(__('Deployment configuration file is not writable.')); } $this->_view->loadLayout(); @@ -31,8 +51,8 @@ public function execute() $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Encryption Key')); if (($formBlock = $this->_view->getLayout()->getBlock('crypt.key.form')) && - ($data = $this->_objectManager->get(\Magento\Backend\Model\Session::class)->getFormData(true))) { - /* @var \Magento\EncryptionKey\Block\Adminhtml\Crypt\Key\Form $formBlock */ + ($data = $this->_session->getFormData(true))) { + /* @var Form $formBlock */ $formBlock->setFormData($data); } diff --git a/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php b/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php index 9331b68675b67..e04fabeb0db34 100644 --- a/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php +++ b/app/code/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatch.php @@ -7,8 +7,14 @@ namespace Magento\EncryptionKey\Setup\Patch\Data; +use Magento\Config\Model\Config\Backend\Encrypted; +use Magento\Config\Model\Config\Structure; +use Magento\Framework\App\Area; +use Magento\Framework\App\State; +use Magento\Framework\Config\ScopeInterface; +use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; -use Magento\Framework\App\ObjectManager; /** * Migrate encrypted configuration values to the latest cipher @@ -16,50 +22,50 @@ class SodiumChachaPatch implements DataPatchInterface { /** - * @var \Magento\Framework\Config\ScopeInterface - */ - private $scope; - - /** - * @var \Magento\Framework\Setup\ModuleDataSetupInterface + * @var ModuleDataSetupInterface */ private $moduleDataSetup; /** - * @var \Magento\Config\Model\Config\Structure + * @var Structure */ private $structure; /** - * @var \Magento\Framework\Encryption\EncryptorInterface + * @var EncryptorInterface */ private $encryptor; /** - * @var \Magento\Framework\App\State + * @var State */ private $state; + /** + * @var ScopeInterface + */ + private $scope; + /** * SodiumChachaPatch constructor. - * @param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup - * @param \Magento\Config\Model\Config\Structure\Proxy $structure - * @param \Magento\Framework\Encryption\EncryptorInterface $encryptor - * @param \Magento\Framework\App\State $state - * @param \Magento\Framework\Config\ScopeInterface|null $scope + * @param ModuleDataSetupInterface $moduleDataSetup + * @param Structure $structure + * @param EncryptorInterface $encryptor + * @param State $state + * @param ScopeInterface $scope */ public function __construct( - \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup, - \Magento\Config\Model\Config\Structure\Proxy $structure, - \Magento\Framework\Encryption\EncryptorInterface $encryptor, - \Magento\Framework\App\State $state, - \Magento\Framework\Config\ScopeInterface $scope = null + ModuleDataSetupInterface $moduleDataSetup, + Structure $structure, + EncryptorInterface $encryptor, + State $state, + ScopeInterface $scope ) { $this->moduleDataSetup = $moduleDataSetup; $this->structure = $structure; $this->encryptor = $encryptor; $this->state = $state; - $this->scope = $scope ?? ObjectManager::getInstance()->get(\Magento\Framework\Config\ScopeInterface::class); + $this->scope = $scope; } /** @@ -72,6 +78,8 @@ public function apply() $this->reEncryptSystemConfigurationValues(); $this->moduleDataSetup->endSetup(); + + return $this; } /** @@ -106,19 +114,25 @@ private function reEncryptSystemConfigurationValues() $currentScope = $this->scope->getCurrentScope(); $structure = $this->structure; $paths = $this->state->emulateAreaCode( - \Magento\Framework\App\Area::AREA_ADMINHTML, + Area::AREA_ADMINHTML, function () use ($structure) { - $this->scope->setCurrentScope(\Magento\Framework\App\Area::AREA_ADMINHTML); + $this->scope->setCurrentScope(Area::AREA_ADMINHTML); /** Returns list of structure paths to be re encrypted */ $paths = $structure->getFieldPathsByAttribute( 'backend_model', - \Magento\Config\Model\Config\Backend\Encrypted::class + Encrypted::class ); /** Returns list of mapping between configPath => [structurePaths] */ $mappedPaths = $structure->getFieldPaths(); foreach ($mappedPaths as $mappedPath => $data) { foreach ($data as $structurePath) { - if ($structurePath !== $mappedPath && $key = array_search($structurePath, $paths)) { + if ($structurePath === $mappedPath) { + continue; + } + + $key = array_search($structurePath, $paths); + + if ($key) { $paths[$key] = $mappedPath; } } diff --git a/app/code/Magento/EncryptionKey/etc/di.xml b/app/code/Magento/EncryptionKey/etc/di.xml new file mode 100644 index 0000000000000..b4e471f4e40ef --- /dev/null +++ b/app/code/Magento/EncryptionKey/etc/di.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="\Magento\EncryptionKey\Setup\Patch\Data\SodiumChachaPatch"> + <arguments> + <argument name="structure" xsi:type="object">Magento\Config\Model\Config\Structure\Proxy</argument> + </arguments> + </type> +</config> From dee769295d724a6867c6f6d0390acbc84f0f50d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Sat, 29 Feb 2020 01:09:34 +0100 Subject: [PATCH 146/369] Cleanup ObjectManager usage - Magento_CacheInvalidate --- .../Observer/InvalidateVarnishObserver.php | 60 ++++++------ .../InvalidateVarnishObserverTest.php | 98 ++++++++++++------- 2 files changed, 94 insertions(+), 64 deletions(-) diff --git a/app/code/Magento/CacheInvalidate/Observer/InvalidateVarnishObserver.php b/app/code/Magento/CacheInvalidate/Observer/InvalidateVarnishObserver.php index 3527d4f6cdf4e..a3062ea9d6a76 100644 --- a/app/code/Magento/CacheInvalidate/Observer/InvalidateVarnishObserver.php +++ b/app/code/Magento/CacheInvalidate/Observer/InvalidateVarnishObserver.php @@ -3,61 +3,76 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CacheInvalidate\Observer; +use Magento\CacheInvalidate\Model\PurgeCache; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\App\Cache\Tag\Resolver; +use Magento\PageCache\Model\Config; +/** + * Observer used to invalidate varnish cache once Magento cache was cleaned + */ class InvalidateVarnishObserver implements ObserverInterface { /** * Application config object * - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ - protected $config; + private $config; /** - * @var \Magento\CacheInvalidate\Model\PurgeCache + * @var PurgeCache */ - protected $purgeCache; + private $purgeCache; /** * Invalidation tags resolver * - * @var \Magento\Framework\App\Cache\Tag\Resolver + * @var Resolver */ private $tagResolver; /** - * @param \Magento\PageCache\Model\Config $config - * @param \Magento\CacheInvalidate\Model\PurgeCache $purgeCache + * @param Config $config + * @param PurgeCache $purgeCache + * @param Resolver $tagResolver */ public function __construct( - \Magento\PageCache\Model\Config $config, - \Magento\CacheInvalidate\Model\PurgeCache $purgeCache + Config $config, + PurgeCache $purgeCache, + Resolver $tagResolver ) { $this->config = $config; $this->purgeCache = $purgeCache; + $this->tagResolver = $tagResolver; } /** - * If Varnish caching is enabled it collects array of tags - * of incoming object and asks to clean cache. + * If Varnish caching is enabled it collects array of tags of incoming object and asks to clean cache. + * + * @param Observer $observer * - * @param \Magento\Framework\Event\Observer $observer * @return void */ - public function execute(\Magento\Framework\Event\Observer $observer) + public function execute(Observer $observer) { $object = $observer->getEvent()->getObject(); + if (!is_object($object)) { return; } - if ($this->config->getType() == \Magento\PageCache\Model\Config::VARNISH && $this->config->isEnabled()) { - $bareTags = $this->getTagResolver()->getTags($object); + + if ((int)$this->config->getType() === Config::VARNISH && $this->config->isEnabled()) { + $bareTags = $this->tagResolver->getTags($object); $tags = []; - $pattern = "((^|,)%s(,|$))"; + $pattern = '((^|,)%s(,|$))'; foreach ($bareTags as $tag) { $tags[] = sprintf($pattern, $tag); } @@ -66,17 +81,4 @@ public function execute(\Magento\Framework\Event\Observer $observer) } } } - - /** - * @deprecated 100.1.2 - * @return \Magento\Framework\App\Cache\Tag\Resolver - */ - private function getTagResolver() - { - if ($this->tagResolver === null) { - $this->tagResolver = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\App\Cache\Tag\Resolver::class); - } - return $this->tagResolver; - } } diff --git a/app/code/Magento/CacheInvalidate/Test/Unit/Observer/InvalidateVarnishObserverTest.php b/app/code/Magento/CacheInvalidate/Test/Unit/Observer/InvalidateVarnishObserverTest.php index b333fcaaa678a..56684ed564dde 100644 --- a/app/code/Magento/CacheInvalidate/Test/Unit/Observer/InvalidateVarnishObserverTest.php +++ b/app/code/Magento/CacheInvalidate/Test/Unit/Observer/InvalidateVarnishObserverTest.php @@ -3,49 +3,77 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CacheInvalidate\Test\Unit\Observer; +use Magento\CacheInvalidate\Model\PurgeCache; +use Magento\CacheInvalidate\Observer\InvalidateVarnishObserver; +use Magento\Framework\App\Cache\Tag\Resolver; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\PageCache\Model\Config; +use Magento\Store\Model\Store; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class InvalidateVarnishObserverTest extends \PHPUnit\Framework\TestCase +/** + * Test class for \Magento\CacheInvalidate\Observer\InvalidateVarnishObserver + */ +class InvalidateVarnishObserverTest extends TestCase { - /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\CacheInvalidate\Observer\InvalidateVarnishObserver */ - protected $model; + /** + * @var InvalidateVarnishObserver + */ + private $model; - /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\Event\Observer */ - protected $observerMock; + /** + * @var Config|MockObject + */ + private $configMock; - /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\PageCache\Model\Config */ - protected $configMock; + /** + * @var PurgeCache|MockObject + */ + private $purgeCacheMock; - /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\CacheInvalidate\Model\PurgeCache */ - protected $purgeCache; + /** + * @var Resolver|MockObject + */ + private $tagResolverMock; - /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\DataObject\ */ - protected $observerObject; + /** + * @var Observer|MockObject + */ + private $observerMock; - /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\App\Cache\Tag\Resolver */ - private $tagResolver; + /** + * @var Store|MockObject + */ + private $observerObject; /** - * Set up all mocks and data for test + * @inheritDoc */ protected function setUp() { - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->configMock = $this->createPartialMock(\Magento\PageCache\Model\Config::class, ['getType', 'isEnabled']); - $this->purgeCache = $this->createMock(\Magento\CacheInvalidate\Model\PurgeCache::class); - $this->model = new \Magento\CacheInvalidate\Observer\InvalidateVarnishObserver( - $this->configMock, - $this->purgeCache - ); + $this->configMock = $this->createPartialMock(Config::class, ['getType', 'isEnabled']); + $this->purgeCacheMock = $this->createMock(PurgeCache::class); + $this->tagResolverMock = $this->createMock(Resolver::class); - $this->tagResolver = $this->createMock(\Magento\Framework\App\Cache\Tag\Resolver::class); - $helper->setBackwardCompatibleProperty($this->model, 'tagResolver', $this->tagResolver); + $this->observerMock = $this->createPartialMock(Observer::class, ['getEvent']); + $this->observerObject = $this->createMock(Store::class); - $this->observerMock = $this->createPartialMock(\Magento\Framework\Event\Observer::class, ['getEvent']); - $this->observerObject = $this->createMock(\Magento\Store\Model\Store::class); + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + InvalidateVarnishObserver::class, + [ + 'config' => $this->configMock, + 'purgeCache' => $this->purgeCacheMock, + 'tagResolver' => $this->tagResolverMock + ] + ); } /** @@ -56,21 +84,21 @@ public function testInvalidateVarnish() $tags = ['cache_1', 'cache_group']; $pattern = '((^|,)cache_1(,|$))|((^|,)cache_group(,|$))'; - $this->configMock->expects($this->once())->method('isEnabled')->will($this->returnValue(true)); + $this->configMock->expects($this->once())->method('isEnabled')->willReturn(true); $this->configMock->expects( $this->once() )->method( 'getType' - )->will( - $this->returnValue(\Magento\PageCache\Model\Config::VARNISH) + )->willReturn( + Config::VARNISH ); - $eventMock = $this->createPartialMock(\Magento\Framework\Event::class, ['getObject']); - $eventMock->expects($this->once())->method('getObject')->will($this->returnValue($this->observerObject)); - $this->observerMock->expects($this->once())->method('getEvent')->will($this->returnValue($eventMock)); - $this->tagResolver->expects($this->once())->method('getTags')->with($this->observerObject) - ->will($this->returnValue($tags)); - $this->purgeCache->expects($this->once())->method('sendPurgeRequest')->with($pattern); + $eventMock = $this->createPartialMock(Event::class, ['getObject']); + $eventMock->expects($this->once())->method('getObject')->willReturn($this->observerObject); + $this->observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $this->tagResolverMock->expects($this->once())->method('getTags')->with($this->observerObject) + ->willReturn($tags); + $this->purgeCacheMock->expects($this->once())->method('sendPurgeRequest')->with($pattern); $this->model->execute($this->observerMock); } From ee00b989feee1ecf0412abe9d860cb0320fb5f26 Mon Sep 17 00:00:00 2001 From: Eduard Chitoraga <e.chitoraga@atwix.com> Date: Sun, 1 Mar 2020 08:02:06 +0200 Subject: [PATCH 147/369] Small adjustment --- .../Magento/backend/Magento_Catalog/web/css/source/_module.less | 1 + 1 file changed, 1 insertion(+) diff --git a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less index f494bb0ad0088..650fe6177d04b 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less @@ -106,6 +106,7 @@ // // Catalog Product Edit Action Attribute // --------------------------------------------- + .admin__field-control { .attribute-change-checkbox { input[type='checkbox'].checkbox { From c2a27c36e9884c74825a7a5c6d1205bb3b3c8082 Mon Sep 17 00:00:00 2001 From: Michele Fantetti <mfantetti@ittweb.net> Date: Sun, 1 Mar 2020 19:55:52 +0100 Subject: [PATCH 148/369] Add Italy States --- .../Setup/Patch/Data/AddDataForItaly.php | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php diff --git a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php new file mode 100644 index 0000000000000..6da0792d0a3e9 --- /dev/null +++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php @@ -0,0 +1,194 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See PLPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Directory\Setup\Patch\Data; + +use Magento\Directory\Setup\DataInstaller; +use Magento\Directory\Setup\DataInstallerFactory; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +/** + * Add Italy States + */ +class AddDataForItaly implements DataPatchInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var DataInstallerFactory + */ + private $dataInstallerFactory; + + /** + * @param ModuleDataSetupInterface $moduleDataSetup + * @param DataInstallerFactory $dataInstallerFactory + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + DataInstallerFactory $dataInstallerFactory + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->dataInstallerFactory = $dataInstallerFactory; + } + + /** + * @inheritdoc + */ + public function apply() + { + /** @var DataInstaller $dataInstaller */ + $dataInstaller = $this->dataInstallerFactory->create(); + $dataInstaller->addCountryRegions( + $this->moduleDataSetup->getConnection(), + $this->getDataForItaly() + ); + } + + /** + * Italy states data. + * + * @return array + */ + private function getDataForItaly() + { + return [ + ['IT', 'AG', 'Agrigento'], + ['IT', 'AL', 'Alessandria'], + ['IT', 'AN', 'Ancona'], + ['IT', 'AO', 'Aosta'], + ['IT', 'AQ', 'L\'Aquila'], + ['IT', 'AR', 'Arezzo'], + ['IT', 'AP', 'Ascoli-Piceno'], + ['IT', 'AT', 'Asti'], + ['IT', 'AV', 'Avellino'], + ['IT', 'BA', 'Bari'], + ['IT', 'BT', 'Barletta-Andria-Trani'], + ['IT', 'BL', 'Belluno'], + ['IT', 'BN', 'Benevento'], + ['IT', 'BG', 'Bergamo'], + ['IT', 'BI', 'Biella'], + ['IT', 'BO', 'Bologna'], + ['IT', 'BZ', 'Bolzano'], + ['IT', 'BS', 'Brescia'], + ['IT', 'BR', 'Brindisi'], + ['IT', 'CA', 'Cagliari'], + ['IT', 'CL', 'Caltanissetta'], + ['IT', 'CB', 'Campobasso'], + ['IT', 'CI', 'Carbonia Iglesias'], + ['IT', 'CE', 'Caserta'], + ['IT', 'CT', 'Catania'], + ['IT', 'CZ', 'Catanzaro'], + ['IT', 'CH', 'Chieti'], + ['IT', 'CO', 'Como'], + ['IT', 'CS', 'Cosenza'], + ['IT', 'CR', 'Cremona'], + ['IT', 'KR', 'Crotone'], + ['IT', 'CN', 'Cuneo'], + ['IT', 'EN', 'Enna'], + ['IT', 'FM', 'Fermo'], + ['IT', 'FE', 'Ferrara'], + ['IT', 'FI', 'Firenze'], + ['IT', 'FG', 'Foggia'], + ['IT', 'FC', 'Forli-Cesena'], + ['IT', 'FR', 'Frosinone'], + ['IT', 'GE', 'Genova'], + ['IT', 'GO', 'Gorizia'], + ['IT', 'GR', 'Grosseto'], + ['IT', 'IM', 'Imperia'], + ['IT', 'IS', 'Isernia'], + ['IT', 'SP', 'La-Spezia'], + ['IT', 'LT', 'Latina'], + ['IT', 'LE', 'Lecce'], + ['IT', 'LC', 'Lecco'], + ['IT', 'LI', 'Livorno'], + ['IT', 'LO', 'Lodi'], + ['IT', 'LU', 'Lucca'], + ['IT', 'MC', 'Macerata'], + ['IT', 'MN', 'Mantova'], + ['IT', 'MS', 'Massa-Carrara'], + ['IT', 'MT', 'Matera'], + ['IT', 'VS', 'Medio Campidano'], + ['IT', 'ME', 'Messina'], + ['IT', 'MI', 'Milano'], + ['IT', 'MO', 'Modena'], + ['IT', 'MB', 'Monza-Brianza'], + ['IT', 'NA', 'Napoli'], + ['IT', 'NO', 'Novara'], + ['IT', 'NU', 'Nuoro'], + ['IT', 'OG', 'Ogliastra'], + ['IT', 'OT', 'Olbia Tempio'], + ['IT', 'OR', 'Oristano'], + ['IT', 'PD', 'Padova'], + ['IT', 'PA', 'Palermo'], + ['IT', 'PR', 'Parma'], + ['IT', 'PV', 'Pavia'], + ['IT', 'PG', 'Perugia'], + ['IT', 'PU', 'Pesaro-Urbino'], + ['IT', 'PE', 'Pescara'], + ['IT', 'PC', 'Piacenza'], + ['IT', 'PI', 'Pisa'], + ['IT', 'PT', 'Pistoia'], + ['IT', 'PN', 'Pordenone'], + ['IT', 'PZ', 'Potenza'], + ['IT', 'PO', 'Prato'], + ['IT', 'RG', 'Ragusa'], + ['IT', 'RA', 'Ravenna'], + ['IT', 'RC', 'Reggio-Calabria'], + ['IT', 'RE', 'Reggio-Emilia'], + ['IT', 'RI', 'Rieti'], + ['IT', 'RN', 'Rimini'], + ['IT', 'RM', 'Roma'], + ['IT', 'RO', 'Rovigo'], + ['IT', 'SA', 'Salerno'], + ['IT', 'SS', 'Sassari'], + ['IT', 'SV', 'Savona'], + ['IT', 'SI', 'Siena'], + ['IT', 'SR', 'Siracusa'], + ['IT', 'SO', 'Sondrio'], + ['IT', 'TA', 'Taranto'], + ['IT', 'TE', 'Teramo'], + ['IT', 'TR', 'Terni'], + ['IT', 'TO', 'Torino'], + ['IT', 'TP', 'Trapani'], + ['IT', 'TN', 'Trento'], + ['IT', 'TV', 'Treviso'], + ['IT', 'TS', 'Trieste'], + ['IT', 'UD', 'Udine'], + ['IT', 'VA', 'Varese'], + ['IT', 'VE', 'Venezia'], + ['IT', 'VB', 'Verbania'], + ['IT', 'VC', 'Vercelli'], + ['IT', 'VR', 'Verona'], + ['IT', 'VV', 'Vibo-Valentia'], + ['IT', 'VI', 'Vicenza'], + ['IT', 'VT', 'Viterbo'], + ]; + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return [ + InitializeDirectoryData::class, + ]; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} From 972d382359bbd00286fa1a64ff62fed432db6e5b Mon Sep 17 00:00:00 2001 From: WaPoNe <michele.fantetti@gmail.com> Date: Sun, 1 Mar 2020 20:01:51 +0100 Subject: [PATCH 149/369] Add Italy States --- app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php index 6da0792d0a3e9..44288c9f2a276 100644 --- a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php +++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php @@ -1,7 +1,7 @@ <?php /** * Copyright © Magento, Inc. All rights reserved. - * See PLPYING.txt for license details. + * See COPYING.txt for license details. */ declare(strict_types=1); From 3590b44be005762c32ebf3fb7e3e0f4e28055e58 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Sun, 1 Mar 2020 20:24:52 +0100 Subject: [PATCH 150/369] #26989 Replace Magento Cron ActionGroup with <magentoCron> --- .../Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml | 2 +- .../AdminAssignProductAttributeToAttributeSetTest.xml | 2 +- ...nfigurableProductPriceWithDisabledChildProductTest.xml | 2 +- ...dminCheckCustomAttributeValuesAfterProductSaveTest.xml | 2 +- .../Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml | 2 +- ...AttributeVisibleInStorefrontAdvancedSearchFormTest.xml | 2 +- ...AttributeVisibleInStorefrontAdvancedSearchFormTest.xml | 2 +- .../Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml | 2 +- .../Test/AdminDeleteConfigurableChildProductsTest.xml | 2 +- ...DeleteDropdownProductAttributeFromAttributeSetTest.xml | 2 +- .../Test/Mftf/Test/AdminDeleteProductAttributeTest.xml | 2 +- ...eleteTextFieldProductAttributeFromAttributeSetTest.xml | 2 +- .../Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml | 2 +- .../AdminProductGridFilteringByCustomAttributeTest.xml | 2 +- ...eNotAvailableForProductOptionsWithoutTierPriceTest.xml | 2 +- .../AdminUnassignProductAttributeFromAttributeSetTest.xml | 2 +- .../Test/DeleteUsedInConfigurableProductAttributeTest.xml | 2 +- .../ProductAttributeWithoutValueInCompareListTest.xml | 2 +- ...tAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml | 2 +- .../Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml | 2 +- .../StorefrontProductsCompareWithEmptyAttributeTest.xml | 2 +- .../Test/Mftf/Test/AdminExportBundleProductTest.xml | 2 +- ...AdminExportImportConfigurableProductWithImagesTest.xml | 2 +- ...SimpleAndConfigurableProductsWithCustomOptionsTest.xml | 2 +- ...oductAndConfigurableProductsWithAssignedImagesTest.xml | 2 +- ...eAndConfigurableProductAssignedToCustomWebsiteTest.xml | 2 +- .../AssociatedProductToConfigurableOutOfStockTest.xml | 2 +- ...logRuleForConfigurableProductWithSpecialPricesTest.xml | 2 +- .../Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest.xml | 2 +- .../Test/ApplyCatalogPriceRuleByProductAttributeTest.xml | 2 +- ...pplyCatalogRuleForSimpleAndConfigurableProductTest.xml | 2 +- ...ConfigurableProductWithAssignedSimpleProducts2Test.xml | 2 +- ...rConfigurableProductWithAssignedSimpleProductsTest.xml | 2 +- ...yCatalogRuleForConfigurableProductWithOptions2Test.xml | 2 +- ...lyCatalogRuleForConfigurableProductWithOptionsTest.xml | 2 +- .../DeleteConfigurableProductFromShoppingCartTest.xml | 2 +- .../Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml | 2 +- ...StorefrontAddConfigurableProductToShoppingCartTest.xml | 2 +- .../StorefrontCheckoutWithSpecialPriceProductsTest.xml | 2 +- ...tDeleteConfigurableProductFromMiniShoppingCartTest.xml | 2 +- ...orefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml | 2 +- .../Mftf/Test/AdminAddDefaultImageConfigurableTest.xml | 2 +- ...eckConfigurableProductAttributeValueUniquenessTest.xml | 2 +- .../Test/AdminCheckResultsOfColorAndOtherFiltersTest.xml | 2 +- .../Test/AdminCheckValidatorConfigurableProductTest.xml | 2 +- .../Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml | 4 ++-- .../Mftf/Test/AdminConfigurableProductLongSkuTest.xml | 2 +- .../Mftf/Test/AdminConfigurableProductOutOfStockTest.xml | 6 +++--- .../Test/Mftf/Test/AdminConfigurableProductSearchTest.xml | 4 ++-- .../Test/AdminConfigurableProductUpdateAttributeTest.xml | 4 ++-- .../Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml | 4 ++-- ...onfigurableProductWithDisabledChildrenProductsTest.xml | 2 +- .../Test/AdminCreateConfigurableProductWithImagesTest.xml | 2 +- ...oductWithThreeProductDisplayOutOfStockProductsTest.xml | 2 +- ...tWithThreeProductDontDisplayOutOfStockProductsTest.xml | 2 +- ...eateConfigurableProductWithTierPriceForOneItemTest.xml | 2 +- .../Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml | 4 ++-- .../Test/Mftf/Test/AdminRelatedProductsTest.xml | 2 +- .../Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml | 2 +- .../Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml | 8 ++++---- .../ConfigurableProductPriceAdditionalStoreViewTest.xml | 2 +- .../Test/Mftf/Test/EndToEndB2CGuestUserTest.xml | 4 ++-- .../Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml | 2 +- .../Test/NewProductsListWidgetConfigurableProductTest.xml | 2 +- .../NoOptionAvailableToConfigureDisabledProductTest.xml | 2 +- ...dvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml | 2 +- .../Test/StorefrontConfigurableProductChildSearchTest.xml | 2 +- .../Test/StorefrontConfigurableProductDetailsTest.xml | 2 +- ...igurableProductChildAssignedToSeparateCategoryTest.xml | 2 +- ...ngByPriceForConfigurableWithCatalogRuleAppliedTest.xml | 4 ++-- ...rontVerifyConfigurableProductLayeredNavigationTest.xml | 2 +- .../Mftf/Test/StorefrontClearAllCompareProductsTest.xml | 2 +- .../StorefrontCheckAdvancedSearchOnElasticSearchTest.xml | 4 ++-- ...atSomeAttributesChangedValueToEmptyAfterImportTest.xml | 2 +- .../ActionGroup/CliRunReindexUsingCronJobsActionGroup.xml | 2 +- ...ntProductWithMapAssignedConfigProductIsCorrectTest.xml | 2 +- .../Test/StorefrontGuestCheckoutDisabledProductTest.xml | 2 +- .../AddConfigurableProductToOrderFromShoppingCartTest.xml | 2 +- ...heCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml | 2 +- .../Test/AdminCreateCreditMemoConfigurableProductTest.xml | 2 +- .../Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml | 2 +- .../Mftf/Test/CreateOrderFromEditCustomerPageTest.xml | 2 +- .../MoveConfigurableProductsInComparedOnOrderPageTest.xml | 2 +- .../MoveLastOrderedConfigurableProductOnOrderPageTest.xml | 2 +- ...veRecentlyViewedConfigurableProductOnOrderPageTest.xml | 2 +- .../Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml | 2 +- .../Mftf/Test/AdminCreatePercentOfProductPriceTest.xml | 2 +- .../Mftf/Test/CartPriceRuleForConfigurableProductTest.xml | 2 +- ...torefrontUsingElasticSearchWithWeightAttributeTest.xml | 2 +- .../Test/Mftf/Test/AdminCreateImageSwatchTest.xml | 2 +- ...ormationInShoppingCartForCustomerPhysicalQuoteTest.xml | 4 ++-- ...formationInShoppingCartForCustomerVirtualQuoteTest.xml | 4 ++-- ...InformationInShoppingCartForGuestPhysicalQuoteTest.xml | 4 ++-- ...xInformationInShoppingCartForGuestVirtualQuoteTest.xml | 4 ++-- ...torefrontDeleteConfigurableProductFromWishlistTest.xml | 2 +- 95 files changed, 112 insertions(+), 112 deletions(-) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml index eaaa9c4356617..26321f269e6d2 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml @@ -111,7 +111,7 @@ <deleteData createDataKey="createThirdBundleProduct" stepKey="deleteThirdBundleProduct"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Open created category on Storefront --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml index 01a1b26a1f034..83916d9d96027 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml @@ -35,7 +35,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Go to default attribute set edit page --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml index bc2efacfcbece..9821acc4b9e5d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml @@ -120,7 +120,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Open Product in Store Front Page --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml index c9a7bae1753b4..5b510a7ecdb0b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckCustomAttributeValuesAfterProductSaveTest.xml @@ -45,7 +45,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Open created product for edit --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml index 8f06565c147fa..f2783a9fcf2cb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml @@ -28,7 +28,7 @@ <deleteData createDataKey="createAttributeSet" stepKey="deleteAttributeSet"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="goToAttributeSets"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml index b36cbc27f7086..7ae42948175b1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml @@ -39,7 +39,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Filter product attribute set by attribute set name --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml index 2c8ec9ad7d1b9..62eea9d48ccc0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml @@ -43,7 +43,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Filter product attribute set by attribute set name --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml index f468f61fada04..13a7974575640 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml @@ -31,7 +31,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Navigate to Stores > Attributes > Attribute Set --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml index 3510b99a0c778..069789738d11f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml @@ -84,7 +84,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Open Product in Store Front Page --> <amOnPage url="$$createConfigProduct.sku$$.html" stepKey="openProductInStoreFront"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml index 373d14d4d0db4..db57b5e7d1181 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml @@ -31,7 +31,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Open Product Attribute Set Page --> <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="goToAttributeSets"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml index de95604e76a2f..d90bb162acca9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml @@ -25,7 +25,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="OpenProductAttributeFromSearchResultInGridActionGroup" stepKey="openProductAttributeFromSearchResultInGrid"> <argument name="productAttributeCode" value="$$createProductAttribute.attribute_code$$"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml index 8528212e8fa20..44996d167feaa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml @@ -34,7 +34,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Open Product Attribute Set Page --> <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="goToAttributeSets"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml index ab1ced89175bc..e88645fdc3782 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml @@ -92,7 +92,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Open Product Index Page--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml index 1f7b88e8bb27f..5593e8b56422f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByCustomAttributeTest.xml @@ -92,7 +92,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> <waitForPageLoad stepKey="waitForProductGridPageLoad"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml index ee8dab9c0ee37..b451c06176bc3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml @@ -85,7 +85,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Go to storefront product page an check price box css--> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml index c651d2db6a7ce..831444c924ec1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml @@ -39,7 +39,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Assert attribute presence in storefront product additional information --> <amOnPage url="/$$product.custom_attributes[url_key]$$.html" stepKey="onProductPage1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml index eb6661e12116d..026f3ce7067f6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml @@ -75,7 +75,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Go to Stores > Attributes > Products. Search and select the product attribute that was used to create the configurable product--> <actionGroup ref="OpenProductAttributeFromSearchResultInGridActionGroup" stepKey="openProductAttributeFromSearchResultInGrid"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml index 96907eb091b45..79fe21e0c0d77 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml @@ -46,7 +46,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Open product page--> <amOnPage url="{{StorefrontProductPage.url($$createProductDefault.name$$)}}" stepKey="goToProductDefaultPage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml index 2695c0f07f19e..be063d2387b25 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontEnsureThatAccordionAnchorIsVisibleOnViewportOnceClickedTest.xml @@ -71,7 +71,7 @@ <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Edit the product and set those attributes values --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml index feefcf6f4559d..cd97d64227e83 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml @@ -31,7 +31,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="amOnAttributeSetPage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml index 3b6284a6f6efa..849de20991c62 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml @@ -35,7 +35,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="amOnAttributeSetPage"/> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml index 5415f75879ae8..c81e699c38abb 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml @@ -109,7 +109,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Go to export page --> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml index 9ff9f54d36939..021b9ac624d7c 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -155,7 +155,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Go to System > Export --> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml index 03314206fa67f..4755df6aba54f 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml @@ -97,7 +97,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Go to export page --> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml index b199f4546b909..4c3228635eb47 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml @@ -113,7 +113,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Go to export page --> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml index 578a9586a36c5..4a34bd4f8241f 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml @@ -98,7 +98,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Go to export page --> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml index d0e3819ccc3cc..b1ab6a598eb88 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml @@ -88,7 +88,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Login as a customer --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml index bf4ec749d7264..db25ffc9c68b2 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml @@ -94,7 +94,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Add special prices for products --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest.xml index 730e04bfea7cf..17a959c374991 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest.xml @@ -184,7 +184,7 @@ <deleteData createDataKey="createConfigProductAttribute1" stepKey="deleteConfigProductAttribute1"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Delete the simple product and catalog price rule --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml index feb9423151299..400b03c9af21e 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml @@ -107,7 +107,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Add values to your attribute ( ex: red , green) --> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml index 2790dfbe46fda..d79a3805d79a4 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml @@ -98,7 +98,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Begin creating a new catalog price rule --> <actionGroup ref="NewCatalogPriceRuleByUIWithConditionIsCategoryActionGroup" stepKey="newCatalogPriceRuleByUIWithConditionIsCategory"> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml index 56e0573649d29..93ae9359eef23 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProducts2Test.xml @@ -174,7 +174,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create catalog price rule --> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml index bd73501b6117e..ff0c9058037df 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithAssignedSimpleProductsTest.xml @@ -178,7 +178,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create price rule --> <actionGroup ref="NewCatalogPriceRuleByUIActionGroup" stepKey="createPriceRule"> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml index 1cc2a8cb57256..f1aed1b2de5b2 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptions2Test.xml @@ -108,7 +108,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create price rule for first configurable product option --> diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml index 2375d50d73e3c..b7cae5980239b 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml +++ b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithOptionsTest.xml @@ -125,7 +125,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create price rule for first configurable product option --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml index 9d092b4b84a3c..62fa3063de08a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/DeleteConfigurableProductFromShoppingCartTest.xml @@ -62,7 +62,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Add configurable product to the cart --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml index 4a4428712ac9d..2b2316b20396e 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml @@ -113,7 +113,7 @@ <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogoutStorefront"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Add Simple Product to cart --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml index 7d91b13b7b833..b8e70d7492539 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml @@ -122,7 +122,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Add Configurable Product to the cart --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml index c76ec4cbc3c5c..dee2bb16a63d0 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutWithSpecialPriceProductsTest.xml @@ -104,7 +104,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Open Product page in StoreFront and assert product and price range --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromMiniShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromMiniShoppingCartTest.xml index 8fe2ac3a74791..92e185d1bbb00 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromMiniShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromMiniShoppingCartTest.xml @@ -74,7 +74,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Add Configurable Product to the cart --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml index 3fe5f1be2a53d..7f4e6f0201ce5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml @@ -111,7 +111,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create a Tax Rule --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml index a10545ba415fe..9e96b5847b8e7 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAddDefaultImageConfigurableTest.xml @@ -91,7 +91,7 @@ <deleteData createDataKey="productAttributeHandle" stepKey="deleteProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="productIndexPage"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml index 79d109de31821..c6a277295632b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckConfigurableProductAttributeValueUniquenessTest.xml @@ -36,7 +36,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logOut"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Create configurable product--> <comment userInput="Create configurable product" stepKey="createConfProd"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckResultsOfColorAndOtherFiltersTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckResultsOfColorAndOtherFiltersTest.xml index 7843d387eb2b9..21619ca911d8a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckResultsOfColorAndOtherFiltersTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckResultsOfColorAndOtherFiltersTest.xml @@ -112,7 +112,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create three configurable products with options --> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml index 635e4c6f84c72..2a2ef1947fdab 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml @@ -46,7 +46,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Find the product that we just created using the product grid --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml index eabeb1b6881a0..0d945ebecf73a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml @@ -72,7 +72,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- assert product visible in storefront --> @@ -230,7 +230,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Search for prefix of the 3 products we created via api --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductLongSkuTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductLongSkuTest.xml index 24a7a590566bd..9d01438a3c423 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductLongSkuTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductLongSkuTest.xml @@ -55,7 +55,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Create a configurable product with long name and sku--> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml index 2ab3a9e6e60c9..7e77f070bf6a5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml @@ -85,7 +85,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Check to make sure that the configurable product shows up as in stock --> @@ -209,7 +209,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Check to make sure that the configurable product shows up as in stock --> @@ -314,7 +314,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Check to make sure that the configurable product shows up as in stock --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest.xml index 7c20574868a66..6eb6d7a11f767 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductSearchTest.xml @@ -75,7 +75,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> @@ -157,7 +157,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml index fc10b2cc3b4e3..59cb7216ed264 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateAttributeTest.xml @@ -107,7 +107,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Get the current option of the attribute before it was changed --> @@ -226,7 +226,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Find the product that we just created using the product grid --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml index be039eb2f3389..fdc467728451a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml @@ -147,7 +147,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--check storefront for both options--> @@ -243,7 +243,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--check storefront for both options--> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithDisabledChildrenProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithDisabledChildrenProductsTest.xml index 586a22982b334..261b307a0718c 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithDisabledChildrenProductsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithDisabledChildrenProductsTest.xml @@ -56,7 +56,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create configurable product --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithImagesTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithImagesTest.xml index 7f03efbc7fd65..7d863b299f384 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithImagesTest.xml @@ -59,7 +59,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Create configurable product --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDisplayOutOfStockProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDisplayOutOfStockProductsTest.xml index 41a446e6bc320..14304d93b3c28 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDisplayOutOfStockProductsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDisplayOutOfStockProductsTest.xml @@ -79,7 +79,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Create configurable product --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml index 95891b286ab53..c6bb6fdf7e7e8 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml @@ -78,7 +78,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Create configurable product --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml index f9729c42f0acf..96ffecaf337ae 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml @@ -67,7 +67,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Create configurable product --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index e308bafb2ac46..361d58c147d38 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -46,7 +46,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Add configurations to product--> <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> @@ -153,7 +153,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Add configurations to product--> <comment userInput="Add configurations to product" stepKey="commentAddConfigurations"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml index d764876d3b5cc..d9300ad1b290b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRelatedProductsTest.xml @@ -91,7 +91,7 @@ <deleteData createDataKey="productAttributeHandle" stepKey="deleteProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <comment userInput="Filter and edit simple product 1" stepKey="filterAndEditComment1"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml index e0c4fda005666..a1a1bb5c8a35a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminRemoveDefaultImageConfigurableTest.xml @@ -90,7 +90,7 @@ <deleteData createDataKey="categoryHandle" stepKey="deleteCategory"/> <deleteData createDataKey="productAttributeHandle" stepKey="deleteProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="productIndexPage"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml index 05a9222eacaf9..0458238d7a479 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml @@ -85,7 +85,7 @@ <deleteData createDataKey="productAttributeHandle" stepKey="deleteProductDropDownAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> </test> <test name="AdvanceCatalogSearchConfigurableBySkuTest" extends="AdvanceCatalogSearchSimpleProductBySkuTest"> @@ -165,7 +165,7 @@ <deleteData createDataKey="productAttributeHandle" stepKey="deleteProductDropDownAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> </test> <test name="AdvanceCatalogSearchConfigurableByDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> @@ -245,7 +245,7 @@ <deleteData createDataKey="productAttributeHandle" stepKey="deleteProductDropDownAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> </test> <test name="AdvanceCatalogSearchConfigurableByShortDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> @@ -325,7 +325,7 @@ <deleteData createDataKey="productAttributeHandle" stepKey="deleteProductDropDownAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml index 49a1ab6b5e11d..a5f62da42575e 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ConfigurableProductPriceAdditionalStoreViewTest.xml @@ -79,7 +79,7 @@ </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="EnableWebUrlOptionsActionGroup" stepKey="addStoreCodeToUrls"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index a4904c67e0ef8..1c99cd722cf86 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -75,7 +75,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Verify Configurable Product in checkout cart items --> @@ -287,7 +287,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Verify Configurable Product in checkout cart items --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 3d797e62c806a..97a433fdb3bf0 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -75,7 +75,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Verify Configurable Product in checkout cart items --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml index 5caba34def165..3dc706645a33b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NewProductsListWidgetConfigurableProductTest.xml @@ -72,7 +72,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- A Cms page containing the New Products Widget gets created here via extends --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml index 55a109aee4b37..dbbeb4e252ef7 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml @@ -105,7 +105,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Disable child product --> <comment userInput="Disable child product" stepKey="disableChildProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml index 020e5dbbdfc77..d5b50f107161a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontAdvanceCatalogSearchConfigurableBySkuWithHyphenTest.xml @@ -86,7 +86,7 @@ <deleteData createDataKey="productAttributeHandle" stepKey="deleteProductDropDownAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml index eaf9fa689d218..d52cf1978d7fe 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductChildSearchTest.xml @@ -142,7 +142,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Quick search the storefront for the first attribute option --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest.xml index 387f2e212c4aa..56d9fee72daf2 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductDetailsTest.xml @@ -222,7 +222,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="openProductIndexPage"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontShouldSeeOnlyConfigurableProductChildAssignedToSeparateCategoryTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontShouldSeeOnlyConfigurableProductChildAssignedToSeparateCategoryTest.xml index fc32a1ca6ac8c..9296ebc7cece5 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontShouldSeeOnlyConfigurableProductChildAssignedToSeparateCategoryTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontShouldSeeOnlyConfigurableProductChildAssignedToSeparateCategoryTest.xml @@ -97,7 +97,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Go to the product page for the first product --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml index fcb9811a93dfa..c41088919269a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml @@ -100,7 +100,7 @@ </actionGroup> <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllRules"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </before> <after> @@ -124,7 +124,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Open category with products and Sort by price desc--> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml index 32f9d78828ed1..65db458e07a04 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml @@ -119,7 +119,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Open Product Index Page and Filter First Child product --> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml index 8e4f9edc085a4..6c6ce5c8619e8 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontClearAllCompareProductsTest.xml @@ -127,7 +127,7 @@ <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer1"> diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontCheckAdvancedSearchOnElasticSearchTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontCheckAdvancedSearchOnElasticSearchTest.xml index 7380ec085e0f3..532975eddabbe 100644 --- a/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontCheckAdvancedSearchOnElasticSearchTest.xml +++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontCheckAdvancedSearchOnElasticSearchTest.xml @@ -35,7 +35,7 @@ </actionGroup> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> <magentoCLI command="cache:flush" arguments="full_page" stepKey="flushFullPageCache"/> </before> @@ -52,7 +52,7 @@ <deleteData createDataKey="createConfigProductAttributeCreateConfigurableProductTwo" stepKey="deleteConfigProductAttributeForSecondProduct"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </after> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckThatSomeAttributesChangedValueToEmptyAfterImportTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckThatSomeAttributesChangedValueToEmptyAfterImportTest.xml index 9ccdb313b88e6..2d891982e8306 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckThatSomeAttributesChangedValueToEmptyAfterImportTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckThatSomeAttributesChangedValueToEmptyAfterImportTest.xml @@ -44,7 +44,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Create product--> <actionGroup ref="GoToSpecifiedCreateProductPageActionGroup" stepKey="openProductFillForm"/> diff --git a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/CliRunReindexUsingCronJobsActionGroup.xml b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/CliRunReindexUsingCronJobsActionGroup.xml index 9dc7547db40d6..50407aff70cd5 100644 --- a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/CliRunReindexUsingCronJobsActionGroup.xml +++ b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/CliRunReindexUsingCronJobsActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="CliRunReindexUsingCronJobsActionGroup"> + <actionGroup name="CliRunReindexUsingCronJobsActionGroup" deprecated="Use magentoCron instead"> <annotations> <description>Run cron 'index' group which reindex all invalidated indices.</description> </annotations> diff --git a/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml index cfc841cbeb0f6..72443a41d67f4 100644 --- a/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml +++ b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml @@ -107,7 +107,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Set Manufacturer's Suggested Retail Price to products--> diff --git a/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml index 020b495dc42a2..5f5138d6b9495 100644 --- a/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml +++ b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml @@ -89,7 +89,7 @@ <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetGridToDefaultKeywordSearch"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Step 1: Add simple product to shopping cart --> <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml index 9b03f566a5c57..49ed69d76196a 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml @@ -74,7 +74,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Login as customer --> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml index d08754a8a4127..7d6ce40659b1f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml @@ -110,7 +110,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create new customer order --> <comment userInput="Create new customer order" stepKey="createNewCustomerOrderComment"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoConfigurableProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoConfigurableProductTest.xml index 9c3aea8bc912e..5839ff4b3dfe2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoConfigurableProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoConfigurableProductTest.xml @@ -97,7 +97,7 @@ <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductGridFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create Order --> <actionGroup ref="NavigateToNewOrderPageExistingCustomerActionGroup" stepKey="navigateToNewOrderPage"> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml index cfdbd39838ecb..8ab26c04d5d6d 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml @@ -133,7 +133,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml index e0ede2ebe55b8..3bd6e9656ebc0 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml @@ -92,7 +92,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!--Filter and Open the customer edit page --> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml index a214979bef885..e126e7eab0abc 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml @@ -103,7 +103,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Login as customer --> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml index 6b6718c67cb4c..90b18266b22c4 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml @@ -68,7 +68,7 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create order --> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml index eae4de730f116..16e44fbb8842f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml @@ -82,7 +82,7 @@ <magentoCLI command="config:set reports/options/enabled 0" stepKey="disableReportModule"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Login as customer --> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml index 2f5ffbb96e8d7..04dadd95f9f43 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml @@ -228,7 +228,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreatePercentOfProductPriceTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreatePercentOfProductPriceTest.xml index 38986dc32f8d2..902c118129bab 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreatePercentOfProductPriceTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreatePercentOfProductPriceTest.xml @@ -22,7 +22,7 @@ <before> <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml index 41062b8153b3f..22dff89ebe8a6 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml @@ -92,7 +92,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Create the rule --> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontUsingElasticSearchWithWeightAttributeTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontUsingElasticSearchWithWeightAttributeTest.xml index 504dee5067187..4cdd8e6992367 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontUsingElasticSearchWithWeightAttributeTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontUsingElasticSearchWithWeightAttributeTest.xml @@ -47,7 +47,7 @@ <actionGroup ref="AdminSetUseInSearchValueForProductAttributeActionGroup" stepKey="makeAttributeSearchableInAQuickSearch"/> <actionGroup ref="SaveProductAttributeActionGroup" stepKey="saveAttribute"/> <!-- Step 3 --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> <!-- Step 4 --> <magentoCLI command="cache:clean" arguments="full_page" stepKey="clearFPC"/> <!-- Step 5 --> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml index 0e24d63728d9d..e7882675866d4 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml @@ -31,7 +31,7 @@ <actionGroup ref="NavigateToAndResetProductAttributeGridToDefaultViewActionGroup" stepKey="resetProductAttributeFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- Begin creating a new product attribute of type "Image Swatch" --> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml index befce13ef036b..958cd0a9d2f5e 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml @@ -58,7 +58,7 @@ <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> <!-- Delete all catalog price rules that can (and actually do) affect this test--> <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> + <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </before> <after> @@ -73,7 +73,7 @@ <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> + <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </after> <!-- Test Steps --> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml index 8abdbede98922..c28ffdf589380 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml @@ -43,7 +43,7 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <!-- Delete all catalog price rules that can (and actually do) affect this test--> <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> + <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </before> <after> @@ -55,7 +55,7 @@ <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> + <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </after> <!-- Test Steps --> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml index d4e5ed74be9a3..4e10bef874565 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml @@ -56,7 +56,7 @@ <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> <!-- Delete all catalog price rules that can (and actually do) affect this test--> <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> + <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </before> <after> @@ -68,7 +68,7 @@ <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductsGridFilters"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> + <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </after> <!-- Test Steps --> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml index 0fc4af813c5a1..3b01f623cace7 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/StorefrontFPTTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml @@ -41,7 +41,7 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <!-- Delete all catalog price rules that can (and actually do) affect this test--> <actionGroup ref="AdminCatalogPriceRuleDeleteAllActionGroup" stepKey="deleteAllCatalogPriceRule"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> + <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </before> <after> @@ -50,7 +50,7 @@ <createData entity="DefaultTaxConfig" stepKey="defaultTaxConfiguration"/> <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexBrokenIndices"/> + <magentoCron groups="index" stepKey="reindexBrokenIndices"/> </after> <!-- Test Steps --> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromWishlistTest.xml index c380bddd2aca8..4ad87095ecd30 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontDeleteConfigurableProductFromWishlistTest.xml @@ -120,7 +120,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <!-- 1. Login as a customer --> From b6b69a6af9347e2f18930a7edc6388513c17b1e6 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Sun, 1 Mar 2020 21:15:55 +0100 Subject: [PATCH 151/369] #27117 Add `Test` suffix for Test names (Assuming that filename is correct) --- ...ontGoToDetailsPageWhenAddingToCartTest.xml | 2 +- .../Mftf/Test/CaptchaFormsDisplayingTest.xml | 2 +- .../Mftf/Test/AdminVerifyProductOrderTest.xml | 2 +- ...rifyDefaultWYSIWYGToolbarOnProductTest.xml | 2 +- .../Mftf/Test/SearchEntityResultsTest.xml | 32 +++++++++---------- .../Test/CheckCheckoutSuccessPageTest.xml | 4 +-- ...rontCheckCartAndCheckoutItemsCountTest.xml | 4 +-- .../Test/StorefrontCustomerCheckoutTest.xml | 4 +-- .../Mftf/Test/StorefrontGuestCheckoutTest.xml | 2 +- ...ontVerifySecureURLRedirectCheckoutTest.xml | 2 +- .../Test/Mftf/Test/ConfigurationTest.xml | 2 +- ...AdminConfigurableProductOutOfStockTest.xml | 2 +- .../ProductsQtyReturnAfterOrderCancelTest.xml | 2 +- ...rontVerifySecureURLRedirectContactTest.xml | 2 +- .../Mftf/Test/ChangeCustomerGroupTest.xml | 4 +-- ...ontVerifySecureURLRedirectCustomerTest.xml | 2 +- ...erifySecureURLRedirectDownloadableTest.xml | 2 +- .../Test/AdminGroupedProductsListTest.xml | 2 +- ...rifySecureURLRedirectMultishippingTest.xml | 2 +- ...tVerifySecureURLRedirectNewsletterTest.xml | 2 +- ...frontVerifySecureURLRedirectPaypalTest.xml | 2 +- ...frontVerifySecureURLRedirectReviewTest.xml | 2 +- ...efrontVerifySecureURLRedirectSalesTest.xml | 2 +- .../Mftf/Test/StorefrontTaxQuoteCartTest.xml | 8 ++--- .../Test/StorefrontTaxQuoteCheckoutTest.xml | 8 ++--- ...tipleStoreviewsDuringProductImportTest.xml | 2 +- ...writesForProductInAnchorCategoriesTest.xml | 8 ++--- ...efrontVerifySecureURLRedirectVaultTest.xml | 2 +- ...ontVerifySecureURLRedirectWishlistTest.xml | 2 +- 29 files changed, 57 insertions(+), 57 deletions(-) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml index 7ced26bab2c96..f0e16bbdd1b99 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontGoToDetailsPageWhenAddingToCart"> + <test name="StorefrontGoToDetailsPageWhenAddingToCartTest"> <annotations> <features value="Bundle"/> <stories value="Bundle products list on Storefront"/> diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml index 977ee78c0d201..b1c3ed52f163b 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml @@ -65,7 +65,7 @@ <scrollToTopOfPage stepKey="ScrollToTop"/> <click selector="{{CaptchaFormsDisplayingSection.captcha}}" stepKey="ClickToCloseCaptcha"/> </test> - <test name="CaptchaWithDisabledGuestCheckout"> + <test name="CaptchaWithDisabledGuestCheckoutTest"> <annotations> <features value="Captcha"/> <stories value="MC-5602 - CAPTCHA doesn't appear in login popup after refreshing page."/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml index 09ddcd040bea4..bd1a5aaf9ed42 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminVerifyProductOrder"> + <test name="AdminVerifyProductOrderTest"> <annotations> <features value="Catalog"/> <stories value="Verify Product Order"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml index 8c10f22b7b09e..ae74a000d76e8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyDefaultWYSIWYGToolbarOnProductTest.xml @@ -47,7 +47,7 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> </test> - <test name="Verifydefaultcontrolsonproductshortdescription"> + <test name="VerifydefaultcontrolsonproductshortdescriptionTest"> <annotations> <features value="Catalog"/> <stories value="Default toolbar configuration in Magento-MAGETWO-70412"/> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml index dc715a5d95f2f..890df17f113d5 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="QuickSearchProductBySku"> + <test name="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find products"/> @@ -41,7 +41,7 @@ <argument name="productUrlKey" value="$createSimpleProduct.custom_attributes[url_key]$"/> </actionGroup> </test> - <test name="QuickSearchProductByName" extends="QuickSearchProductBySku"> + <test name="QuickSearchProductByNameTest" extends="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find products via Name"/> @@ -56,7 +56,7 @@ <argument name="phrase" value="$createSimpleProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchProductByNameWithSpecialChars" extends="QuickSearchProductBySku"> + <test name="QuickSearchProductByNameWithSpecialCharsTest" extends="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="Quick Search can find products with names that contain special characters"/> @@ -76,7 +76,7 @@ <argument name="phrase" value="$createSimpleProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchEmptyResults"> + <test name="QuickSearchEmptyResultsTest"> <annotations> <features value="CatalogSearch"/> <stories value="Search Product on Storefront"/> @@ -109,7 +109,7 @@ <actionGroup ref="StorefrontCheckSearchIsEmptyActionGroup" stepKey="checkEmpty"/> </test> - <test name="QuickSearchWithTwoCharsEmptyResults" extends="QuickSearchEmptyResults"> + <test name="QuickSearchWithTwoCharsEmptyResultsTest" extends="QuickSearchEmptyResultsTest"> <annotations> <features value="CatalogSearch"/> <stories value="Search Product on Storefront"/> @@ -143,7 +143,7 @@ </actionGroup> </test> - <test name="QuickSearchProductByNameWithThreeLetters" extends="QuickSearchProductBySku"> + <test name="QuickSearchProductByNameWithThreeLettersTest" extends="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find products by their first three letters"/> @@ -159,7 +159,7 @@ <argument name="phrase" value="{$getFirstThreeLetters}"/> </actionGroup> </test> - <test name="QuickSearchProductBy128CharQuery" extends="QuickSearchProductBySku"> + <test name="QuickSearchProductBy128CharQueryTest" extends="QuickSearchProductBySkuTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search product with long names, using first 128 letters"/> @@ -180,7 +180,7 @@ </actionGroup> </test> - <test name="QuickSearchTwoProductsWithSameWeight"> + <test name="QuickSearchTwoProductsWithSameWeightTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="Quick Search should sort products with the same weight appropriately"/> @@ -263,7 +263,7 @@ <argument name="index" value="1"/> </actionGroup> </test> - <test name="QuickSearchTwoProductsWithDifferentWeight" extends="QuickSearchTwoProductsWithSameWeight"> + <test name="QuickSearchTwoProductsWithDifferentWeightTest" extends="QuickSearchTwoProductsWithSameWeightTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="Quick Search should sort products with the different weight appropriately"/> @@ -293,7 +293,7 @@ </actionGroup> </test> - <test name="QuickSearchAndAddToCart"> + <test name="QuickSearchAndAddToCartTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a simple product and add it to cart"/> @@ -325,7 +325,7 @@ <argument name="productName" value="$createSimpleProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartVirtual"> + <test name="QuickSearchAndAddToCartVirtualTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a virtual product and add it to cart"/> @@ -357,7 +357,7 @@ <argument name="productName" value="$createVirtualProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartConfigurable"> + <test name="QuickSearchAndAddToCartConfigurableTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a configurable product and add it to cart"/> @@ -401,7 +401,7 @@ <argument name="optionName" value="{{colorProductAttribute1.name}}"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartDownloadable"> + <test name="QuickSearchAndAddToCartDownloadableTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a downloadable product and add it to cart"/> @@ -438,7 +438,7 @@ <argument name="productName" value="$createProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartGrouped"> + <test name="QuickSearchAndAddToCartGroupedTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a grouped product and add it to cart"/> @@ -475,7 +475,7 @@ <argument name="productName" value="$createProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartBundleDynamic"> + <test name="QuickSearchAndAddToCartBundleDynamicTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a Bundle Dynamic product and add it to cart"/> @@ -531,7 +531,7 @@ <argument name="productName" value="$createBundleProduct.name$"/> </actionGroup> </test> - <test name="QuickSearchAndAddToCartBundleFixed"> + <test name="QuickSearchAndAddToCartBundleFixedTest"> <annotations> <stories value="Search Product on Storefront"/> <title value="User should be able to use Quick Search to find a Bundle Fixed product and add it to cart"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml index b939209751fcd..eb49f53921ea4 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckCheckoutSuccessPageTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CheckCheckoutSuccessPageAsRegisterCustomer"> + <test name="CheckCheckoutSuccessPageAsRegisterCustomerTest"> <annotations> <features value="Checkout"/> <stories value="Success page elements are presented for placed order as Customer"/> @@ -131,7 +131,7 @@ <seeElement selector="{{StorefrontCustomerOrderViewSection.orderTitle}}" stepKey="seeOrderTitleOnPrint"/> <switchToWindow stepKey="switchToWindow2"/> </test> - <test name="CheckCheckoutSuccessPageAsGuest"> + <test name="CheckCheckoutSuccessPageAsGuestTest"> <annotations> <features value="Checkout"/> <stories value="Success page elements are presented for placed order as Guest"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml index c3f173961f0c5..8db1f8801ae1d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckCartAndCheckoutItemsCountTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartItemsCountDisplayItemsQuantities"> + <test name="StorefrontCartItemsCountDisplayItemsQuantitiesTest"> <annotations> <stories value="Checkout order summary has wrong item count"/> <title value="Checkout order summary has wrong item count - display items quantities"/> @@ -57,7 +57,7 @@ <argument name="itemsText" value="3 Items in Cart"/> </actionGroup> </test> - <test name="StorefrontCartItemsCountDisplayUniqueItems" extends="StorefrontCartItemsCountDisplayItemsQuantities"> + <test name="StorefrontCartItemsCountDisplayUniqueItemsTest" extends="StorefrontCartItemsCountDisplayItemsQuantitiesTest"> <annotations> <stories value="Checkout order summary has wrong item count"/> <title value="Checkout order summary has wrong item count - display unique items"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index 580b4e32b0b28..3a686c8efdd5f 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -82,7 +82,7 @@ <waitForElementVisible selector="{{AdminEditCustomerOrdersSection.orderGrid}}" stepKey="waitForOrdersGridVisible"/> <see selector="{{AdminEditCustomerOrdersSection.orderGrid}}" userInput="$$createCustomer.firstname$$ $$createCustomer.lastname$$" stepKey="verifyOrder"/> </test> - <test name="StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRates"> + <test name="StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRatesTest"> <annotations> <features value="Checkout"/> <stories value="Customer checkout"/> @@ -198,7 +198,7 @@ <waitForPageLoad stepKey="waitForOrderSuccessPage2"/> <see selector="{{CheckoutSuccessMainSection.success}}" userInput="Your order number is:" stepKey="seeSuccessMessage2"/> </test> - <test name="StorefrontCustomerCheckoutTestWithRestrictedCountriesForPayment"> + <test name="StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest"> <annotations> <features value="Checkout"/> <stories value="Checkout flow if payment solutions are not available"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml index cce4d9f0345d7..53d7904ffdc38 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml @@ -93,7 +93,7 @@ <remove keyForRemoval="guestGoToCheckoutFromMinicart" /> <actionGroup ref="GoToCheckoutFromCartActionGroup" stepKey="guestGoToCheckoutFromCart" after="seeCartQuantity" /> </test> - <test name="StorefrontGuestCheckoutTestWithRestrictedCountriesForPayment"> + <test name="StorefrontGuestCheckoutTestWithRestrictedCountriesForPaymentTest"> <annotations> <features value="Checkout"/> <stories value="Checkout flow if payment solutions are not available"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml index cbf0072d44aed..778967c187f65 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectCheckout"> + <test name="StorefrontVerifySecureURLRedirectCheckoutTest"> <annotations> <features value="Checkout"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml b/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml index 916a5a09a09c0..9700d8024ce8f 100644 --- a/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml +++ b/app/code/Magento/Config/Test/Mftf/Test/ConfigurationTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="VerifyAllowDynamicMediaURLsSettingIsRemoved"> + <test name="VerifyAllowDynamicMediaURLsSettingIsRemovedTest"> <annotations> <features value="Backend"/> <stories value="Dynamic Media URL"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml index 2ab3a9e6e60c9..cfb85bd391f9f 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductOutOfStockTest.xml @@ -134,7 +134,7 @@ <see stepKey="checkForOutOfStock3" selector="{{StorefrontProductInfoMainSection.stockIndication}}" userInput="OUT OF STOCK"/> </test> - <test name="AdminConfigurableProductOutOfStockTestDeleteChildren"> + <test name="AdminConfigurableProductOutOfStockTestDeleteChildrenTest"> <annotations> <features value="ConfigurableProduct"/> <stories value="Product visibility when in stock/out of stock"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml index 24c60006a3504..0d74775f1e3ae 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="ProductsQtyReturnAfterOrderCancel"> + <test name="ProductsQtyReturnAfterOrderCancelTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/Contact/Test/Mftf/Test/StorefrontVerifySecureURLRedirectContactTest.xml b/app/code/Magento/Contact/Test/Mftf/Test/StorefrontVerifySecureURLRedirectContactTest.xml index 3ef941fa2e0ce..0c46ed4729d66 100644 --- a/app/code/Magento/Contact/Test/Mftf/Test/StorefrontVerifySecureURLRedirectContactTest.xml +++ b/app/code/Magento/Contact/Test/Mftf/Test/StorefrontVerifySecureURLRedirectContactTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectContact"> + <test name="StorefrontVerifySecureURLRedirectContactTest"> <annotations> <features value="Contact"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml index eb46a9d2b1ace..0eca9811f17f1 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="ChangingSingleCustomerGroupViaGrid"> + <test name="ChangingSingleCustomerGroupViaGridTest"> <annotations> <title value="DEPRECATED Change a single customer group via grid"/> <description value="From the selection of All Customers select a single customer to change their group"/> @@ -61,7 +61,7 @@ </actionGroup> </test> - <test name="ChangingAllCustomerGroupViaGrid" extends="ChangingSingleCustomerGroupViaGrid"> + <test name="ChangingAllCustomerGroupViaGridTest" extends="ChangingSingleCustomerGroupViaGridTest"> <annotations> <title value="DEPRECATED Change all customers' group via grid"/> <description value="Select All customers to change their group"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml index da9dddf0539d3..f504af2334e10 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCustomerTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectCustomer"> + <test name="StorefrontVerifySecureURLRedirectCustomerTest"> <annotations> <features value="Customer"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontVerifySecureURLRedirectDownloadableTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontVerifySecureURLRedirectDownloadableTest.xml index 6e039ca413a08..d7e0ce3b2ca22 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontVerifySecureURLRedirectDownloadableTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontVerifySecureURLRedirectDownloadableTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectDownloadable"> + <test name="StorefrontVerifySecureURLRedirectDownloadableTest"> <annotations> <features value="Downloadable"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductsListTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductsListTest.xml index 151a987ea89cc..7657c9a86a62b 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductsListTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminGroupedProductsListTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminGroupedProductsAreListedWhenOutOfStock"> + <test name="AdminGroupedProductsAreListedWhenOutOfStockTest"> <annotations> <features value="GroupedProduct"/> <stories value="MAGETWO-93181: Grouped product doesn't take care about his Linked Products when SalableQuantity < ProductLink.ExtensionAttributes.Qty after Source Deduction"/> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml index 085a710f2671c..e65747f4d63d0 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectMultishipping"> + <test name="StorefrontVerifySecureURLRedirectMultishippingTest"> <!--todo MC-5858: some urls don't redirect to https--> <annotations> <features value="Multishipping"/> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml index 01b5e706fcefb..c38725f263525 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/StorefrontVerifySecureURLRedirectNewsletterTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectNewsletter"> + <test name="StorefrontVerifySecureURLRedirectNewsletterTest"> <annotations> <features value="Newsletter"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontVerifySecureURLRedirectPaypalTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontVerifySecureURLRedirectPaypalTest.xml index b2fcfa43181dc..cf0e4b3d0b370 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontVerifySecureURLRedirectPaypalTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontVerifySecureURLRedirectPaypalTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectPaypal"> + <test name="StorefrontVerifySecureURLRedirectPaypalTest"> <annotations> <features value="Paypal"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifySecureURLRedirectReviewTest.xml b/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifySecureURLRedirectReviewTest.xml index b10af7a303cc7..8a2f441e5c4e8 100644 --- a/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifySecureURLRedirectReviewTest.xml +++ b/app/code/Magento/Review/Test/Mftf/Test/StorefrontVerifySecureURLRedirectReviewTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectReview"> + <test name="StorefrontVerifySecureURLRedirectReviewTest"> <annotations> <features value="Review"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifySecureURLRedirectSalesTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifySecureURLRedirectSalesTest.xml index 505493e4e5682..d49ea4cfcbec7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifySecureURLRedirectSalesTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontVerifySecureURLRedirectSalesTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectSales"> + <test name="StorefrontVerifySecureURLRedirectSalesTest"> <annotations> <features value="Sales"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml index 3d584f988780e..70211a1080951 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCartTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontTaxQuoteCartLoggedInSimple"> + <test name="StorefrontTaxQuoteCartLoggedInSimpleTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in Shopping Cart"/> @@ -127,7 +127,7 @@ </test> - <test name="StorefrontTaxQuoteCartLoggedInVirtual"> + <test name="StorefrontTaxQuoteCartLoggedInVirtualTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in Shopping Cart"/> @@ -243,7 +243,7 @@ <see stepKey="seeTotalExcl3" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$$virtualProduct1.price$$"/> </test> - <test name="StorefrontTaxQuoteCartGuestSimple"> + <test name="StorefrontTaxQuoteCartGuestSimpleTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in Shopping Cart"/> @@ -355,7 +355,7 @@ <see stepKey="seeTotalExcl" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$128.00"/> </test> - <test name="StorefrontTaxQuoteCartGuestVirtual"> + <test name="StorefrontTaxQuoteCartGuestVirtualTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in Shopping Cart"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml index 050ab3889984b..4c3ab91cf909c 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontTaxQuoteCheckoutGuestVirtual"> + <test name="StorefrontTaxQuoteCheckoutGuestVirtualTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in One Page Checkout"/> @@ -114,7 +114,7 @@ <see stepKey="seeTotalExcl" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$$virtualProduct1.price$$"/> </test> - <test name="StorefrontTaxQuoteCheckoutLoggedInSimple"> + <test name="StorefrontTaxQuoteCheckoutLoggedInSimpleTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in One Page Checkout"/> @@ -236,7 +236,7 @@ <see stepKey="seeTotalExcl2" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$128.00"/> </test> - <test name="StorefrontTaxQuoteCheckoutGuestSimple"> + <test name="StorefrontTaxQuoteCheckoutGuestSimpleTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in One Page Checkout"/> @@ -353,7 +353,7 @@ <see stepKey="seeTotalExcl" selector="{{CheckoutPaymentSection.orderSummaryTotalExcluding}}" userInput="$128.00"/> </test> - <test name="StorefrontTaxQuoteCheckoutLoggedInVirtual"> + <test name="StorefrontTaxQuoteCheckoutLoggedInVirtualTest"> <annotations> <features value="Tax"/> <stories value="Tax Calculation in One Page Checkout"/> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml index f635df7edb6f7..31b6ed78aa57d 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml @@ -133,7 +133,7 @@ <seeElement selector="{{AdminUrlRewriteIndexSection.gridCellByColumnValue('Request Path', 'category-dutch/productformagetwo68980-dutch.html')}}" stepKey="seeUrlInRequestPathColumn5"/> <seeElement selector="{{AdminUrlRewriteIndexSection.gridCellByColumnValue('Target Path', catalog/product/view/id/$grabProductIdFromUrl/category/$$createCategory.id$$)}}" stepKey="seeUrlInTargetPathColumn5"/> </test> - <test name="AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTestWithConfigurationTurnedOff"> + <test name="AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTestWithConfigurationTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url Rewrites for Multiple Storeviews"/> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml index 497ab653a0594..98c85114631aa 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml @@ -83,7 +83,7 @@ <seeElement selector="{{AdminUrlRewriteIndexSection.gridCellByColumnValue('Request Path', $simpleSubCategory1.custom_attributes[url_key]$-new/$simpleSubCategory2.custom_attributes[url_key]$/$simpleSubCategory3.custom_attributes[url_key]$/$createSimpleProduct.custom_attributes[url_key]$.html)}}" stepKey="seeInListValue7"/> </test> - <test name="AdminUrlRewritesForProductInAnchorCategoriesTestWithConfigurationTurnedOff"> + <test name="AdminUrlRewritesForProductInAnchorCategoriesTestWithConfigurationTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url-rewrites for product in anchor categories"/> @@ -187,7 +187,7 @@ <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="$$createSimpleProduct.name$$" stepKey="seeProductName7"/> </test> - <test name="AdminUrlRewritesForProductInAnchorCategoriesTestAllStoreView" extends="AdminUrlRewritesForProductInAnchorCategoriesTest"> + <test name="AdminUrlRewritesForProductInAnchorCategoriesTestAllStoreViewTest" extends="AdminUrlRewritesForProductInAnchorCategoriesTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url-rewrites for product in anchor categories for all store views"/> @@ -214,7 +214,7 @@ <remove keyForRemoval="uncheckRedirect2"/> </test> - <test name="AdminUrlRewritesForProductInAnchorCategoriesTestAllStoreViewWithConfigurationTurnedOff" extends="AdminUrlRewritesForProductInAnchorCategoriesTestWithConfigurationTurnedOff"> + <test name="AdminUrlRewritesForProductInAnchorCategoriesTestAllStoreViewWithConfigurationTurnedOffTest" extends="AdminUrlRewritesForProductInAnchorCategoriesTestWithConfigurationTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="Url-rewrites for product in anchor categories for all store views"/> @@ -241,7 +241,7 @@ <remove keyForRemoval="uncheckRedirect2"/> </test> - <test name="AdminUrlRewritesForProductsWithConfigurationTurnedOff"> + <test name="AdminUrlRewritesForProductsWithConfigurationTurnedOffTest"> <annotations> <features value="Url Rewrite"/> <stories value="No Url-rewrites for product if configuration to generate url rewrite for Generate 'category/product' URL Rewrites is enabled "/> diff --git a/app/code/Magento/Vault/Test/Mftf/Test/StorefrontVerifySecureURLRedirectVaultTest.xml b/app/code/Magento/Vault/Test/Mftf/Test/StorefrontVerifySecureURLRedirectVaultTest.xml index c9d4cb3391cfd..f496e500a4d9b 100644 --- a/app/code/Magento/Vault/Test/Mftf/Test/StorefrontVerifySecureURLRedirectVaultTest.xml +++ b/app/code/Magento/Vault/Test/Mftf/Test/StorefrontVerifySecureURLRedirectVaultTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectVault"> + <test name="StorefrontVerifySecureURLRedirectVaultTest"> <annotations> <features value="Vault"/> <stories value="Storefront Secure URLs"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontVerifySecureURLRedirectWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontVerifySecureURLRedirectWishlistTest.xml index 21fa334a43196..72f5bab1e6af5 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontVerifySecureURLRedirectWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontVerifySecureURLRedirectWishlistTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifySecureURLRedirectWishlist"> + <test name="StorefrontVerifySecureURLRedirectWishlistTest"> <annotations> <features value="Wishlist"/> <stories value="Storefront Secure URLs"/> From da8dfc2697639ad170fced8fea2fe40ad2165edc Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Sun, 1 Mar 2020 21:32:10 +0100 Subject: [PATCH 152/369] #27117 Fix invalid file names for Functional Tests --- ... CreateAnAdminOrderUsingBraintreePaymentTest.xml} | 2 +- ...WithOnlinePaymentIncludingTaxAndDiscountTest.xml} | 2 +- ...Product.xml => AdminDeleteABundleProductTest.xml} | 2 +- ...=> AdminFilterProductListByBundleProductTest.xml} | 2 +- ...tBundleProductShownInCategoryListAndGridTest.xml} | 2 +- ...efrontCheckBundleProductOptionTierPricesTest.xml} | 2 +- ...et.xml => AdminChangeProductAttributeSetTest.xml} | 4 ++-- ...dminCreateCategoryWithProductsGridFilterTest.xml} | 2 +- ... => AdminCreateProductCustomAttributeSetTest.xml} | 2 +- ...henAssignedToCategoryWithoutCustomURLKeyTest.xml} | 2 +- ... => StorefrontProductNameWithDoubleQuoteTest.xml} | 4 ++-- ...ductWithCustomOptionsWithLongValuesTitleTest.xml} | 2 +- ...on.xml => StoreFrontMobileViewValidationTest.xml} | 2 +- ...irectNavigateFromCustomerViewCartProductTest.xml} | 2 +- ...inCreateDownloadableProductWithTierPriceTest.xml} | 2 +- ...ttonInMobile.xml => ShopByButtonInMobileTest.xml} | 2 +- ...onfigPaymentsConflictResolutionForPayPalTest.xml} | 12 ++++++------ ...e.xml => AdminConfigPaymentsSectionStateTest.xml} | 2 +- ...ml => AdminChangeCustomerGroupInNewOrderTest.xml} | 2 +- ....xml => StorefrontRedirectToOrderHistoryTest.xml} | 2 +- ...ry.xml => StorefrontCartPriceRuleCountryTest.xml} | 2 +- ...e.xml => StorefrontCartPriceRulePostcodeTest.xml} | 2 +- ...y.xml => StorefrontCartPriceRuleQuantityTest.xml} | 8 ++++---- ...tate.xml => StorefrontCartPriceRuleStateTest.xml} | 2 +- ...l.xml => StorefrontCartPriceRuleSubtotalTest.xml} | 4 ++-- ...inCreateTaxRateInvalidPostcodeTestLengthTest.xml} | 2 +- 26 files changed, 37 insertions(+), 37 deletions(-) rename app/code/Magento/Braintree/Test/Mftf/Test/{CreateAnAdminOrderUsingBraintreePaymentTest1.xml => CreateAnAdminOrderUsingBraintreePaymentTest.xml} (98%) rename app/code/Magento/Braintree/Test/Mftf/Test/{CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml => CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml} (99%) rename app/code/Magento/Bundle/Test/Mftf/Test/{AdminDeleteABundleProduct.xml => AdminDeleteABundleProductTest.xml} (99%) rename app/code/Magento/Bundle/Test/Mftf/Test/{AdminFilterProductListByBundleProduct.xml => AdminFilterProductListByBundleProductTest.xml} (98%) rename app/code/Magento/Bundle/Test/Mftf/Test/{StorefrontBundleProductShownInCategoryListAndGrid.xml => StorefrontBundleProductShownInCategoryListAndGridTest.xml} (99%) rename app/code/Magento/Bundle/Test/Mftf/Test/{StorefrontCheckBundleProductOptionTierPrices.xml => StorefrontCheckBundleProductOptionTierPricesTest.xml} (99%) rename app/code/Magento/Catalog/Test/Mftf/Test/{AdminChangeProductAttributeSet.xml => AdminChangeProductAttributeSetTest.xml} (95%) rename app/code/Magento/Catalog/Test/Mftf/Test/{AdminCreateCategoryWithProductsGridFilter.xml => AdminCreateCategoryWithProductsGridFilterTest.xml} (99%) rename app/code/Magento/Catalog/Test/Mftf/Test/{AdminCreateProductCustomAttributeSet.xml => AdminCreateProductCustomAttributeSetTest.xml} (98%) rename app/code/Magento/Catalog/Test/Mftf/Test/{AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml => AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml} (99%) rename app/code/Magento/Catalog/Test/Mftf/Test/{StorefrontProductNameWithDoubleQuote.xml => StorefrontProductNameWithDoubleQuoteTest.xml} (98%) rename app/code/Magento/Catalog/Test/Mftf/Test/{StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml => StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml} (99%) rename app/code/Magento/Cms/Test/Mftf/Test/{StoreFrontMobileViewValidation.xml => StoreFrontMobileViewValidationTest.xml} (98%) rename app/code/Magento/Customer/Test/Mftf/Test/{AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml => AdminProductBackRedirectNavigateFromCustomerViewCartProductTest.xml} (99%) rename app/code/Magento/Downloadable/Test/Mftf/Test/{AdminCreateDownloadableProductWithTierPriceText.xml => AdminCreateDownloadableProductWithTierPriceTest.xml} (91%) rename app/code/Magento/LayeredNavigation/Test/Mftf/Test/{ShopByButtonInMobile.xml => ShopByButtonInMobileTest.xml} (99%) rename app/code/Magento/Paypal/Test/Mftf/Test/{AdminConfigPaymentsConflictResolutionForPayPal.xml => AdminConfigPaymentsConflictResolutionForPayPalTest.xml} (97%) rename app/code/Magento/Paypal/Test/Mftf/Test/{AdminConfigPaymentsSectionState.xml => AdminConfigPaymentsSectionStateTest.xml} (95%) rename app/code/Magento/Sales/Test/Mftf/Test/{AdminChangeCustomerGroupInNewOrder.xml => AdminChangeCustomerGroupInNewOrderTest.xml} (96%) rename app/code/Magento/Sales/Test/Mftf/Test/{StorefrontRedirectToOrderHistory.xml => StorefrontRedirectToOrderHistoryTest.xml} (98%) rename app/code/Magento/SalesRule/Test/Mftf/Test/{StorefrontCartPriceRuleCountry.xml => StorefrontCartPriceRuleCountryTest.xml} (99%) rename app/code/Magento/SalesRule/Test/Mftf/Test/{StorefrontCartPriceRulePostcode.xml => StorefrontCartPriceRulePostcodeTest.xml} (99%) rename app/code/Magento/SalesRule/Test/Mftf/Test/{StorefrontCartPriceRuleQuantity.xml => StorefrontCartPriceRuleQuantityTest.xml} (95%) rename app/code/Magento/SalesRule/Test/Mftf/Test/{StorefrontCartPriceRuleState.xml => StorefrontCartPriceRuleStateTest.xml} (99%) rename app/code/Magento/SalesRule/Test/Mftf/Test/{StorefrontCartPriceRuleSubtotal.xml => StorefrontCartPriceRuleSubtotalTest.xml} (97%) rename app/code/Magento/Tax/Test/Mftf/Test/{AdminCreateTaxRateInvalidPostcodeTestLength.xml => AdminCreateTaxRateInvalidPostcodeTestLengthTest.xml} (97%) diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest.xml similarity index 98% rename from app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml rename to app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest.xml index c45a8aece5ffc..77dc83eec1176 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CreateAnAdminOrderUsingBraintreePaymentTest1"> + <test name="CreateAnAdminOrderUsingBraintreePaymentTest1Test"> <annotations> <features value="Backend"/> <stories value="Creation an admin order using Braintree payment"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml similarity index 99% rename from app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml rename to app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml index d2b0479f2bba6..5efa5fd0db6b6 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscountTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CreateAdminOrderPayedWithOnlinePaymentIncludingTaxAndDiscount"> + <test name="CreateAdminOrderPayedWithOnlinePaymentIncludingTaxAndDiscountTest"> <annotations> <features value="Braintree"/> <stories value="Get access to a New Credit Memo Page from Invoice for Order payed with online payment via Admin"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml similarity index 99% rename from app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml index f272f3f98a8c9..a98d544aad3b6 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProduct.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteABundleProductTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminDeleteABundleProduct"> + <test name="AdminDeleteABundleProductTest"> <annotations> <features value="Bundle"/> <stories value="Admin list bundle products"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml similarity index 98% rename from app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml index 5aa72fb651985..dea39fcb45908 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProduct.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminFilterProductListByBundleProductTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminFilterProductListByBundleProduct"> + <test name="AdminFilterProductListByBundleProductTest"> <annotations> <features value="Bundle"/> <stories value="Admin list bundle products"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml similarity index 99% rename from app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml index 364d4fa68e590..62a66b7d092ef 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontBundleProductShownInCategoryListAndGrid"> + <test name="StorefrontBundleProductShownInCategoryListAndGridTest"> <annotations> <features value="Bundle"/> <stories value="Bundle products list on Storefront"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml similarity index 99% rename from app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml index 4bb54436e8729..0c9be915a7a8b 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPricesTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCheckBundleProductOptionTierPrices"> + <test name="StorefrontCheckBundleProductOptionTierPricesTest"> <annotations> <features value="Bundle"/> <stories value="View bundle products"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSetTest.xml similarity index 95% rename from app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSetTest.xml index cdb9a0a8b75d0..2e55d9fbfa4bc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSetTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminChangeProductAttributeSet"> + <test name="AdminChangeProductAttributeSetTest"> <annotations> <features value="Checkout"/> <stories value="The required product attribute is not displayed when change attribute set"/> @@ -49,7 +49,7 @@ <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> <!-- Reindex invalidated indices after product attribute has been created/deleted --> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> </after> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml similarity index 99% rename from app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml index 2a4718223ef0c..9a580df52259b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilterTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateCategoryWithProductsGridFilter"> + <test name="AdminCreateCategoryWithProductsGridFilterTest"> <annotations> <stories value="Create categories"/> <title value="Apply category products grid filter"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml similarity index 98% rename from app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml index 7f6feaff3ed5d..2fbd1ac2cf321 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSet.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateProductCustomAttributeSet"> + <test name="AdminCreateProductCustomAttributeSetTest"> <annotations> <features value="Catalog"/> <stories value="Add/Update attribute set"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml similarity index 99% rename from app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml index 400cc891b3c91..9babd94ef2641 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey"> + <test name="AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest"> <annotations> <stories value="Product"/> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml similarity index 98% rename from app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml index 07c2e8a972596..1615f75395fed 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontProductNameWithDoubleQuote"> + <test name="StorefrontProductNameWithDoubleQuoteTest"> <annotations> <features value="Catalog"/> <stories value="Create products"/> @@ -66,7 +66,7 @@ </actionGroup> </test> - <test name="StorefrontProductNameWithHTMLEntities"> + <test name="StorefrontProductNameWithHTMLEntitiesTest"> <annotations> <features value="Catalog"/> <stories value="Create product"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml similarity index 99% rename from app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml index 36a803b03199b..b8aed7f0ac2ad 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle"> + <test name="StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest"> <annotations> <features value="Catalog"/> <stories value="Custom options"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidationTest.xml similarity index 98% rename from app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml rename to app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidationTest.xml index 6165def067ef4..38005682287e8 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidation.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreFrontMobileViewValidationTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StoreFrontMobileViewValidation"> + <test name="StoreFrontMobileViewValidationTest"> <annotations> <features value="Cms"/> <stories value="Mobile view page footer should stick to the bottom of page on Store front"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProductTest.xml similarity index 99% rename from app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml rename to app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProductTest.xml index 18106836ce137..3e65c688e3474 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProductTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminProductBackRedirectNavigateFromCustomerViewCartProduct"> + <test name="AdminProductBackRedirectNavigateFromCustomerViewCartProductTest"> <annotations> <features value="Customer"/> <stories value="Product Back Button"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml similarity index 91% rename from app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.xml rename to app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml index ca4e560506ad0..768f766098aa6 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceText.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateDownloadableProductWithTierPriceText" extends="AdminCreateDownloadableProductWithGroupPriceTest"> + <test name="AdminCreateDownloadableProductWithTierPriceTextTest" extends="AdminCreateDownloadableProductWithGroupPriceTest"> <annotations> <features value="Catalog"/> <stories value="Create Downloadable Product"/> diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobile.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml similarity index 99% rename from app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobile.xml rename to app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml index 76a9cc8f920a1..0e0eb352c8d33 100644 --- a/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobile.xml +++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Test/ShopByButtonInMobileTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="ShopByButtonInMobile"> + <test name="ShopByButtonInMobileTest"> <annotations> <features value="Layered Navigation"/> <stories value="Storefront Shop By collapsible button in mobile themes"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPalTest.xml similarity index 97% rename from app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml rename to app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPalTest.xml index 3e1a825861b5e..db5fb3848dcf1 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPalTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -73,7 +73,7 @@ <argument name="countryCode" value="gb"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInJapan" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInJapanTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -113,7 +113,7 @@ <argument name="countryCode" value="jp"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInFrance" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInFranceTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -153,7 +153,7 @@ <argument name="countryCode" value="fr"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInHongKong" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInHongKongTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -193,7 +193,7 @@ <argument name="countryCode" value="hk"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInItaly" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInItalyTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> @@ -233,7 +233,7 @@ <argument name="countryCode" value="it"/> </actionGroup> </test> - <test name="AdminConfigPaymentsConflictResolutionForPayPalInSpain" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdom"> + <test name="AdminConfigPaymentsConflictResolutionForPayPalInSpainTest" extends="AdminConfigPaymentsConflictResolutionForPayPalInUnitedKingdomTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionStateTest.xml similarity index 95% rename from app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml rename to app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionStateTest.xml index ba5e701ceea66..62d77d8aae6f8 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionStateTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminConfigPaymentsSectionState"> + <test name="AdminConfigPaymentsSectionStateTest"> <annotations> <features value="PayPal"/> <stories value="Payment methods"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrderTest.xml similarity index 96% rename from app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml rename to app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrderTest.xml index cabb6edec2f52..c8b2b66a758eb 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrderTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminChangeCustomerGroupInNewOrder"> + <test name="AdminChangeCustomerGroupInNewOrderTest"> <annotations> <title value="Customer account group cannot be selected while creating a new customer in order"/> <stories value="MC-15290: Customer account group cannot be selected while creating a new customer in order"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml similarity index 98% rename from app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml rename to app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml index ad3a411d92414..ceb8c5f9b1aa2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistoryTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontRedirectToOrderHistory"> + <test name="StorefrontRedirectToOrderHistoryTest"> <annotations> <features value="Redirection Rules"/> <stories value="Create Invoice"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml similarity index 99% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml index 832b9ef8bd4b4..1406d4dfbde67 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountry.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleCountryTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRuleCountry"> + <test name="StorefrontCartPriceRuleCountryTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml similarity index 99% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml index 9882b04bdc956..aade41b30284c 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcode.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRulePostcodeTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRulePostcode"> + <test name="StorefrontCartPriceRulePostcodeTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml similarity index 95% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml index 08a7bd72cd18d..283d22351b1f1 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantity.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleQuantityTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRuleQuantity"> + <test name="StorefrontCartPriceRuleQuantityTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> @@ -24,7 +24,7 @@ <createData entity="_defaultProduct" stepKey="createPreReqProduct"> <requiredEntity createDataKey="createPreReqCategory"/> </createData> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> @@ -62,8 +62,8 @@ <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="1.00" stepKey="fillDiscountAmount"/> <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> - + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> + <!-- Add 1 product to the cart --> <amOnPage url="$$createPreReqProduct.name$$.html" stepKey="goToProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml similarity index 99% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml index 19ffb7c36f992..fafede4120573 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleState.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleStateTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRuleState"> + <test name="StorefrontCartPriceRuleStateTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml similarity index 97% rename from app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml rename to app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml index 94e53cfc88047..a32d42e26d15f 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotal.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCartPriceRuleSubtotal"> + <test name="StorefrontCartPriceRuleSubtotalTest"> <annotations> <features value="SalesRule"/> <stories value="Create cart price rule"/> @@ -24,7 +24,7 @@ <createData entity="_defaultProduct" stepKey="createPreReqProduct"> <requiredEntity createDataKey="createPreReqCategory"/> </createData> - <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLength.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLengthTest.xml similarity index 97% rename from app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLength.xml rename to app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLengthTest.xml index a98de31b42f81..3befada509afa 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLength.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateInvalidPostcodeTestLengthTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateTaxRateInvalidPostcodeTestLength"> + <test name="AdminCreateTaxRateInvalidPostcodeTestLengthTest"> <annotations> <stories value="Create tax rate"/> <title value="Create tax rate, invalid post code length"/> From 5da031ed4cae912235fe0ecc44eb4e1cbe94bffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Sat, 29 Feb 2020 01:35:12 +0100 Subject: [PATCH 153/369] Remove unnecessary chart.js files, move retrieving request params to controllers, apply review requirements --- .../Backend/Block/Dashboard/Totals.php | 3 +- .../Adminhtml/Dashboard/Chart/Amounts.php | 8 +- .../Adminhtml/Dashboard/Chart/Orders.php | 8 +- .../Magento/Backend/Helper/Dashboard/Data.php | 47 +- .../Magento/Backend/Model/Dashboard/Chart.php | 44 +- .../Backend/Model/Dashboard/Chart/Date.php | 23 +- .../Backend/Model/Dashboard/Period.php | 56 + .../Test/Unit/Helper/Dashboard/DataTest.php | 22 +- .../Test/Unit/Model/Dashboard/ChartTest.php | 43 +- .../Test/Unit/Model/Dashboard/PeriodTest.php | 49 + .../Backend/ViewModel/ChartDisabled.php | 7 +- .../Backend/ViewModel/ChartsPeriod.php | 36 +- .../layout/adminhtml_dashboard_index.xml | 3 + .../adminhtml/templates/dashboard/chart.phtml | 7 + .../view/adminhtml/web/js/dashboard/chart.js | 35 +- .../Magento/Ui/view/base/requirejs-config.js | 4 +- .../Block/Dashboard/Tab/OrdersTest.php | 94 - lib/web/chartjs/Chart.bundle.js | 20755 ---------------- lib/web/chartjs/Chart.bundle.min.js | 7 - lib/web/chartjs/Chart.css | 47 - lib/web/chartjs/Chart.js | 16151 ------------ lib/web/chartjs/Chart.min.css | 1 - 22 files changed, 246 insertions(+), 37204 deletions(-) create mode 100644 app/code/Magento/Backend/Model/Dashboard/Period.php create mode 100644 app/code/Magento/Backend/Test/Unit/Model/Dashboard/PeriodTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/Tab/OrdersTest.php delete mode 100644 lib/web/chartjs/Chart.bundle.js delete mode 100644 lib/web/chartjs/Chart.bundle.min.js delete mode 100644 lib/web/chartjs/Chart.css delete mode 100644 lib/web/chartjs/Chart.js delete mode 100644 lib/web/chartjs/Chart.min.css diff --git a/app/code/Magento/Backend/Block/Dashboard/Totals.php b/app/code/Magento/Backend/Block/Dashboard/Totals.php index 3921148acd33a..7da109c2fb602 100644 --- a/app/code/Magento/Backend/Block/Dashboard/Totals.php +++ b/app/code/Magento/Backend/Block/Dashboard/Totals.php @@ -8,6 +8,7 @@ namespace Magento\Backend\Block\Dashboard; use Magento\Backend\Block\Template\Context; +use Magento\Backend\Model\Dashboard\Period; use Magento\Framework\Module\Manager; use Magento\Reports\Model\ResourceModel\Order\Collection; use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; @@ -61,7 +62,7 @@ protected function _prepareLayout() ) || $this->getRequest()->getParam( 'group' ); - $period = $this->getRequest()->getParam('period', '24h'); + $period = $this->getRequest()->getParam('period', Period::PERIOD_24_HOURS); /* @var $collection Collection */ $collection = $this->_collectionFactory->create()->addCreateAtPeriodFilter( diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Amounts.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Amounts.php index f46ab3a3a7d26..a668296f5bf6f 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Amounts.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Amounts.php @@ -54,7 +54,13 @@ public function __construct( public function execute(): Json { $data = [ - 'data' => $this->chart->getByPeriod($this->_request->getParam('period'), 'revenue'), + 'data' => $this->chart->getByPeriod( + $this->_request->getParam('period'), + 'revenue', + $this->_request->getParam('store'), + $this->_request->getParam('website'), + $this->_request->getParam('group') + ), 'label' => __('Revenue') ]; diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Orders.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Orders.php index 11d3d875f1626..b9f7c86f17dc0 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Orders.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/Chart/Orders.php @@ -54,7 +54,13 @@ public function __construct( public function execute(): Json { $data = [ - 'data' => $this->chart->getByPeriod($this->_request->getParam('period'), 'quantity'), + 'data' => $this->chart->getByPeriod( + $this->_request->getParam('period'), + 'quantity', + $this->_request->getParam('store'), + $this->_request->getParam('website'), + $this->_request->getParam('group') + ), 'label' => __('Quantity') ]; diff --git a/app/code/Magento/Backend/Helper/Dashboard/Data.php b/app/code/Magento/Backend/Helper/Dashboard/Data.php index c06e7ea3ba38f..f691d2b7cd4b9 100644 --- a/app/code/Magento/Backend/Helper/Dashboard/Data.php +++ b/app/code/Magento/Backend/Helper/Dashboard/Data.php @@ -5,8 +5,14 @@ */ namespace Magento\Backend\Helper\Dashboard; +use Magento\Backend\Model\Dashboard\Period; use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\Helper\AbstractHelper; +use Magento\Framework\App\Helper\Context; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Config\ConfigOptionsListConstants; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Store\Model\StoreManagerInterface; /** * Data helper for dashboard @@ -14,10 +20,10 @@ * @api * @since 100.0.2 */ -class Data extends \Magento\Framework\App\Helper\AbstractHelper +class Data extends AbstractHelper { /** - * @var \Magento\Framework\Data\Collection\AbstractDb + * @var AbstractDb */ protected $_stores; @@ -27,25 +33,33 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper protected $_installDate; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ private $_storeManager; /** - * @param \Magento\Framework\App\Helper\Context $context - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @var Period + */ + private $period; + + /** + * @param Context $context + * @param StoreManagerInterface $storeManager * @param DeploymentConfig $deploymentConfig + * @param Period|null $period + * @throws \Magento\Framework\Exception\FileSystemException + * @throws \Magento\Framework\Exception\RuntimeException */ public function __construct( - \Magento\Framework\App\Helper\Context $context, - \Magento\Store\Model\StoreManagerInterface $storeManager, - DeploymentConfig $deploymentConfig + Context $context, + StoreManagerInterface $storeManager, + DeploymentConfig $deploymentConfig, + ?Period $period = null ) { - parent::__construct( - $context - ); + parent::__construct($context); $this->_installDate = $deploymentConfig->get(ConfigOptionsListConstants::CONFIG_PATH_INSTALL_DATE); $this->_storeManager = $storeManager; + $this->period = $period ?? ObjectManager::getInstance()->get(Period::class); } /** @@ -74,17 +88,14 @@ public function countStores() /** * Prepare array with periods for dashboard graphs * + * @deprecated periods were moved to it's own class + * @see Period::getDatePeriods() + * * @return array */ public function getDatePeriods() { - return [ - '24h' => __('Last 24 Hours'), - '7d' => __('Last 7 Days'), - '1m' => __('Current Month'), - '1y' => __('YTD'), - '2y' => __('2YTD') - ]; + return $this->period->getDatePeriods(); } /** diff --git a/app/code/Magento/Backend/Model/Dashboard/Chart.php b/app/code/Magento/Backend/Model/Dashboard/Chart.php index d986a7b8f7063..346c1153ac74e 100644 --- a/app/code/Magento/Backend/Model/Dashboard/Chart.php +++ b/app/code/Magento/Backend/Model/Dashboard/Chart.php @@ -7,21 +7,14 @@ namespace Magento\Backend\Model\Dashboard; -use Magento\Backend\Helper\Dashboard\Data as DataHelper; use Magento\Backend\Helper\Dashboard\Order as OrderHelper; use Magento\Backend\Model\Dashboard\Chart\Date; -use Magento\Framework\App\RequestInterface; /** * Dashboard chart data retriever */ class Chart { - /** - * @var RequestInterface - */ - private $request; - /** * @var Date */ @@ -33,47 +26,52 @@ class Chart private $orderHelper; /** - * @var DataHelper + * @var Period */ - private $dataHelper; + private $period; /** * Chart constructor. - * @param RequestInterface $request * @param Date $dateRetriever * @param OrderHelper $orderHelper - * @param DataHelper $dataHelper + * @param Period $period */ public function __construct( - RequestInterface $request, Date $dateRetriever, OrderHelper $orderHelper, - DataHelper $dataHelper + Period $period ) { - $this->request = $request; $this->dateRetriever = $dateRetriever; $this->orderHelper = $orderHelper; - $this->dataHelper = $dataHelper; + $this->period = $period; } /** - * Get chart data by period and chart type parameter + * Get chart data by period and chart type parameter, with possibility to pass scope parameters * * @param string $period * @param string $chartParam + * @param string|null $store + * @param string|null $website + * @param string|null $group * * @return array */ - public function getByPeriod($period, $chartParam): array - { - $this->orderHelper->setParam('store', $this->request->getParam('store')); - $this->orderHelper->setParam('website', $this->request->getParam('website')); - $this->orderHelper->setParam('group', $this->request->getParam('group')); + public function getByPeriod( + string $period, + string $chartParam, + string $store = null, + string $website = null, + string $group = null + ): array { + $this->orderHelper->setParam('store', $store); + $this->orderHelper->setParam('website', $website); + $this->orderHelper->setParam('group', $group); - $availablePeriods = array_keys($this->dataHelper->getDatePeriods()); + $availablePeriods = array_keys($this->period->getDatePeriods()); $this->orderHelper->setParam( 'period', - $period && in_array($period, $availablePeriods, false) ? $period : '24h' + $period && in_array($period, $availablePeriods, false) ? $period : Period::PERIOD_24_HOURS ); $dates = $this->dateRetriever->getByPeriod($period); diff --git a/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php b/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php index c91c42f940228..7e3f853714a34 100644 --- a/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php +++ b/app/code/Magento/Backend/Model/Dashboard/Chart/Date.php @@ -8,6 +8,7 @@ namespace Magento\Backend\Model\Dashboard\Chart; use DateTimeZone; +use Magento\Backend\Model\Dashboard\Period; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Reports\Model\ResourceModel\Order\CollectionFactory; @@ -17,14 +18,14 @@ class Date { /** - * @var TimezoneInterface + * @var CollectionFactory */ - private $localeDate; + private $collectionFactory; /** - * @var CollectionFactory + * @var TimezoneInterface */ - private $collectionFactory; + private $localeDate; /** * Date constructor. @@ -35,8 +36,8 @@ public function __construct( CollectionFactory $collectionFactory, TimezoneInterface $localeDate ) { - $this->localeDate = $localeDate; $this->collectionFactory = $collectionFactory; + $this->localeDate = $localeDate; } /** @@ -46,7 +47,7 @@ public function __construct( * * @return array */ - public function getByPeriod($period): array + public function getByPeriod(string $period): array { [$dateStart, $dateEnd] = $this->collectionFactory->create()->getDateRange( $period, @@ -60,7 +61,7 @@ public function getByPeriod($period): array $dateStart->setTimezone(new DateTimeZone($timezoneLocal)); $dateEnd->setTimezone(new DateTimeZone($timezoneLocal)); - if ($period === '24h') { + if ($period === Period::PERIOD_24_HOURS) { $dateEnd->modify('-1 hour'); } else { $dateEnd->setTime(23, 59, 59); @@ -71,13 +72,13 @@ public function getByPeriod($period): array while ($dateStart <= $dateEnd) { switch ($period) { - case '7d': - case '1m': + case Period::PERIOD_7_DAYS: + case Period::PERIOD_1_MONTH: $d = $dateStart->format('Y-m-d'); $dateStart->modify('+1 day'); break; - case '1y': - case '2y': + case Period::PERIOD_1_YEAR: + case Period::PERIOD_2_YEARS: $d = $dateStart->format('Y-m'); $dateStart->modify('first day of next month'); break; diff --git a/app/code/Magento/Backend/Model/Dashboard/Period.php b/app/code/Magento/Backend/Model/Dashboard/Period.php new file mode 100644 index 0000000000000..28286129e8a68 --- /dev/null +++ b/app/code/Magento/Backend/Model/Dashboard/Period.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Model\Dashboard; + +/** + * Dashboard period info retriever + */ +class Period +{ + public const PERIOD_24_HOURS = '24h'; + public const PERIOD_7_DAYS = '7d'; + public const PERIOD_1_MONTH = '1m'; + public const PERIOD_1_YEAR = '1y'; + public const PERIOD_2_YEARS = '2y'; + + private const PERIOD_UNIT_HOUR = 'hour'; + private const PERIOD_UNIT_DAY = 'day'; + private const PERIOD_UNIT_MONTH = 'month'; + + /** + * Prepare array with periods for dashboard graphs + * + * @return array + */ + public function getDatePeriods(): array + { + return [ + static::PERIOD_24_HOURS => __('Last 24 Hours'), + static::PERIOD_7_DAYS => __('Last 7 Days'), + static::PERIOD_1_MONTH => __('Current Month'), + static::PERIOD_1_YEAR => __('YTD'), + static::PERIOD_2_YEARS => __('2YTD') + ]; + } + + /** + * Prepare array with periods mapping to chart units + * + * @return array + */ + public function getPeriodChartUnits(): array + { + return [ + static::PERIOD_24_HOURS => static::PERIOD_UNIT_HOUR, + static::PERIOD_7_DAYS => static::PERIOD_UNIT_DAY, + static::PERIOD_1_MONTH => static::PERIOD_UNIT_DAY, + static::PERIOD_1_YEAR => static::PERIOD_UNIT_MONTH, + static::PERIOD_2_YEARS => static::PERIOD_UNIT_MONTH + ]; + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Helper/Dashboard/DataTest.php b/app/code/Magento/Backend/Test/Unit/Helper/Dashboard/DataTest.php index 21c72cb6b4477..7943c5e7fdcb1 100644 --- a/app/code/Magento/Backend/Test/Unit/Helper/Dashboard/DataTest.php +++ b/app/code/Magento/Backend/Test/Unit/Helper/Dashboard/DataTest.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Backend\Test\Unit\Helper\Dashboard; @@ -31,7 +30,7 @@ class DataTest extends TestCase private const STUB_CHART_DATA_HASH = '52870842b23068a78220e01eb9d4404d'; /** - * @var \Magento\Backend\Helper\Dashboard\Data + * @var HelperData */ private $helper; @@ -54,7 +53,7 @@ protected function setUp() $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class); $this->deploymentConfigMock->expects($this->once())->method('get') ->with(ConfigOptionsListConstants::CONFIG_PATH_INSTALL_DATE) - ->will($this->returnValue(self::STUB_PATH_INSTALL)); + ->willReturn(self::STUB_PATH_INSTALL); $objectManager = new ObjectManager($this); $this->helper = $objectManager->getObject( @@ -81,23 +80,6 @@ public function testGetStoresWhenStoreAttributeIsNull() $this->assertEquals($storeCollectionMock, $this->helper->getStores()); } - /** - * Test getDatePeriods() method - */ - public function testGetDatePeriods() - { - $this->assertEquals( - [ - '24h' => (string)__('Last 24 Hours'), - '7d' => (string)__('Last 7 Days'), - '1m' => (string)__('Current Month'), - '1y' => (string)__('YTD'), - '2y' => (string)__('2YTD') - ], - $this->helper->getDatePeriods() - ); - } - /** * Test getChartDataHash() method */ diff --git a/app/code/Magento/Backend/Test/Unit/Model/Dashboard/ChartTest.php b/app/code/Magento/Backend/Test/Unit/Model/Dashboard/ChartTest.php index 5aedb953dab37..fadd9e170e0b0 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Dashboard/ChartTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Dashboard/ChartTest.php @@ -3,13 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Backend\Test\Unit\Model\Dashboard; -use Magento\Backend\Helper\Dashboard\Data as DataHelper; use Magento\Backend\Helper\Dashboard\Order as OrderHelper; use Magento\Backend\Model\Dashboard\Chart; use Magento\Backend\Model\Dashboard\Chart\Date as DateRetriever; -use Magento\Framework\App\RequestInterface; +use Magento\Backend\Model\Dashboard\Period; use Magento\Framework\DataObject; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Reports\Model\ResourceModel\Order\Collection; @@ -28,21 +29,11 @@ class ChartTest extends TestCase */ private $objectManagerHelper; - /** - * @var RequestInterface|MockObject - */ - private $requestMock; - /** * @var DateRetriever|MockObject */ private $dateRetrieverMock; - /** - * @var DataHelper|MockObject - */ - private $dataHelperMock; - /** * @var OrderHelper|MockObject */ @@ -57,25 +48,10 @@ protected function setUp() { $this->objectManagerHelper = new ObjectManager($this); - $this->requestMock = $this->getMockForAbstractClass(RequestInterface::class); - $this->requestMock->method('getParam')->willReturn(null); - $this->dateRetrieverMock = $this->getMockBuilder(DateRetriever::class) ->disableOriginalConstructor() ->getMock(); - $this->dataHelperMock = $this->getMockBuilder(DataHelper::class) - ->disableOriginalConstructor() - ->getMock(); - $this->dataHelperMock->method('getDatePeriods') - ->willReturn([ - '24h' => __('Last 24 Hours'), - '7d' => __('Last 7 Days'), - '1m' => __('Current Month'), - '1y' => __('YTD'), - '2y' => __('2YTD') - ]); - $this->orderHelperMock = $this->getMockBuilder(OrderHelper::class) ->disableOriginalConstructor() ->getMock(); @@ -86,13 +62,14 @@ protected function setUp() $this->orderHelperMock->method('getCollection') ->willReturn($this->collectionMock); + $period = $this->objectManagerHelper->getObject(Period::class); + $this->model = $this->objectManagerHelper->getObject( Chart::class, [ - 'request' => $this->requestMock, 'dateRetriever' => $this->dateRetrieverMock, - 'dataHelper' => $this->dataHelperMock, - 'orderHelper' => $this->orderHelperMock + 'orderHelper' => $this->orderHelperMock, + 'period' => $period ] ); } @@ -156,7 +133,7 @@ public function getByPeriodDataProvider(): array { return [ [ - '7d', + Period::PERIOD_7_DAYS, 'revenue', [ [ @@ -178,7 +155,7 @@ public function getByPeriodDataProvider(): array ] ], [ - '1m', + Period::PERIOD_1_MONTH, 'quantity', [ [ @@ -200,7 +177,7 @@ public function getByPeriodDataProvider(): array ] ], [ - '1y', + Period::PERIOD_1_YEAR, 'quantity', [ [ diff --git a/app/code/Magento/Backend/Test/Unit/Model/Dashboard/PeriodTest.php b/app/code/Magento/Backend/Test/Unit/Model/Dashboard/PeriodTest.php new file mode 100644 index 0000000000000..5c71afdde3109 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Model/Dashboard/PeriodTest.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Test\Unit\Model\Dashboard; + +use Magento\Backend\Model\Dashboard\Period; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; + +class PeriodTest extends TestCase +{ + /** + * @var Period + */ + private $model; + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + Period::class, + [] + ); + } + + /** + * Test getDatePeriods() method + */ + public function testGetDatePeriods() + { + $this->assertEquals( + [ + Period::PERIOD_24_HOURS => (string)__('Last 24 Hours'), + Period::PERIOD_7_DAYS => (string)__('Last 7 Days'), + Period::PERIOD_1_MONTH => (string)__('Current Month'), + Period::PERIOD_1_YEAR => (string)__('YTD'), + Period::PERIOD_2_YEARS => (string)__('2YTD') + ], + $this->model->getDatePeriods() + ); + } +} diff --git a/app/code/Magento/Backend/ViewModel/ChartDisabled.php b/app/code/Magento/Backend/ViewModel/ChartDisabled.php index f40e757ee7b64..f71a2d26441ef 100644 --- a/app/code/Magento/Backend/ViewModel/ChartDisabled.php +++ b/app/code/Magento/Backend/ViewModel/ChartDisabled.php @@ -22,6 +22,11 @@ class ChartDisabled implements ArgumentInterface */ private const XML_PATH_ENABLE_CHARTS = 'admin/dashboard/enable_charts'; + /** + * Route to Stores -> Configuration section + */ + private const ROUTE_SYSTEM_CONFIG = 'adminhtml/system_config/edit'; + /** * @var UrlInterface */ @@ -52,7 +57,7 @@ public function __construct( public function getConfigUrl(): string { return $this->urlBuilder->getUrl( - 'adminhtml/system_config/edit', + static::ROUTE_SYSTEM_CONFIG, ['section' => 'admin', '_fragment' => 'admin_dashboard-link'] ); } diff --git a/app/code/Magento/Backend/ViewModel/ChartsPeriod.php b/app/code/Magento/Backend/ViewModel/ChartsPeriod.php index c118cb7c02a5f..effce0dc60585 100644 --- a/app/code/Magento/Backend/ViewModel/ChartsPeriod.php +++ b/app/code/Magento/Backend/ViewModel/ChartsPeriod.php @@ -7,7 +7,8 @@ namespace Magento\Backend\ViewModel; -use Magento\Backend\Helper\Dashboard\Data; +use Magento\Backend\Model\Dashboard\Period; +use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\View\Element\Block\ArgumentInterface; /** @@ -16,16 +17,25 @@ class ChartsPeriod implements ArgumentInterface { /** - * @var Data + * @var Period */ - private $dataHelper; + private $period; /** - * @param Data $dataHelper + * @var Json */ - public function __construct(Data $dataHelper) - { - $this->dataHelper = $dataHelper; + private $serializer; + + /** + * @param Period $period + * @param Json $serializer + */ + public function __construct( + Period $period, + Json $serializer + ) { + $this->period = $period; + $this->serializer = $serializer; } /** @@ -35,6 +45,16 @@ public function __construct(Data $dataHelper) */ public function getDatePeriods(): array { - return $this->dataHelper->getDatePeriods(); + return $this->period->getDatePeriods(); + } + + /** + * Get json-encoded chart period units + * + * @return string + */ + public function getPeriodUnits(): string + { + return $this->serializer->serialize($this->period->getPeriodChartUnits()); } } diff --git a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml index 86909162abb7c..4a255134ab062 100644 --- a/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml +++ b/app/code/Magento/Backend/view/adminhtml/layout/adminhtml_dashboard_index.xml @@ -56,14 +56,17 @@ <argument name="label" xsi:type="string" translate="true">Orders</argument> <argument name="title" xsi:type="string" translate="true">Orders</argument> <argument name="update_url" xsi:type="string">adminhtml/dashboard_chart/orders</argument> + <argument name="view_model" xsi:type="object">Magento\Backend\ViewModel\ChartsPeriod</argument> </arguments> </block> <block class="Magento\Backend\Block\Widget\Tab" name="dashboard.chart.amounts" as="amounts" template="Magento_Backend::dashboard/chart.phtml"> <arguments> + <argument name="chart_precision" xsi:type="number">2</argument> <argument name="html_id" xsi:type="string">amounts</argument> <argument name="label" xsi:type="string" translate="true">Amounts</argument> <argument name="title" xsi:type="string" translate="true">Amounts</argument> <argument name="update_url" xsi:type="string">adminhtml/dashboard_chart/amounts</argument> + <argument name="view_model" xsi:type="object">Magento\Backend\ViewModel\ChartsPeriod</argument> </arguments> </block> </block> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart.phtml b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart.phtml index f289246157c47..65c0d292ee187 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/dashboard/chart.phtml @@ -4,13 +4,16 @@ * See COPYING.txt for license details. */ +use Magento\Backend\ViewModel\ChartsPeriod; use Magento\Framework\Escaper; use Magento\Framework\View\Element\Template; /** * @var Template $block * @var Escaper $escaper + * @var ChartsPeriod $viewModel */ +$viewModel = $block->getViewModel(); ?> <div class="dashboard-diagram"> <div class="dashboard-diagram-graph"> @@ -28,6 +31,10 @@ use Magento\Framework\View\Element\Template; '_current' => true ])) ?>", "periodSelect": "#dashboard_chart_period", + "periodUnits": <?= /** @noEscape */ $viewModel->getPeriodUnits() ?>, + <?php if ($precision = $block->getData('chart_precision')): ?> + "precision": <?= (int)$precision ?>, + <?php endif; ?> "type": "<?= $escaper->escapeJs($block->getData('html_id')) ?>" } } diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js b/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js index c618e5db5c84a..9177939dcfa9f 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js +++ b/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/chart.js @@ -17,6 +17,8 @@ define([ options: { updateUrl: '', periodSelect: null, + periodUnits: [], + precision: 0, type: '' }, chart: null, @@ -72,40 +74,13 @@ define([ $(this.element).toggle(response.data.length > 0); $(this.element).next('.dashboard-diagram-nodata').toggle(response.data.length === 0); - this.chart.options.scales.xAxes[0].time.unit = this.getPeriodUnit(); + this.chart.options.scales.xAxes[0].time.unit = this.options.periodUnits[this.period] ? + this.options.periodUnits[this.period] : 'hour'; this.chart.data.datasets[0].data = response.data; this.chart.data.datasets[0].label = response.label; this.chart.update(); }, - /** - * @returns {String} time unit per currently set period - */ - getPeriodUnit: function () { - switch (this.period) { - case '7d': - case '1m': - return 'day'; - - case '1y': - case '2y': - return 'month'; - } - - return 'hour'; - }, - - /** - * @returns {Number} precision of numeric chart data - */ - getPrecision: function () { - if (this.options.type === 'amounts') { - return 2; - } - - return 0; - }, - /** * @returns {Object} chart object configuration */ @@ -137,7 +112,7 @@ define([ yAxes: [{ ticks: { beginAtZero: true, - precision: this.getPrecision() + precision: this.options.precision } }] } diff --git a/app/code/Magento/Ui/view/base/requirejs-config.js b/app/code/Magento/Ui/view/base/requirejs-config.js index 6685a51d6561a..5e76600673254 100644 --- a/app/code/Magento/Ui/view/base/requirejs-config.js +++ b/app/code/Magento/Ui/view/base/requirejs-config.js @@ -5,7 +5,7 @@ var config = { shim: { - 'chartjs/Chart': ['moment'], + 'chartjs/Chart.min': ['moment'], 'tiny_mce_4/tinymce.min': { exports: 'tinyMCE' } @@ -24,7 +24,7 @@ var config = { consoleLogger: 'Magento_Ui/js/lib/logger/console-logger', uiLayout: 'Magento_Ui/js/core/renderer/layout', buttonAdapter: 'Magento_Ui/js/form/button-adapter', - chartJs: 'chartjs/Chart', + chartJs: 'chartjs/Chart.min', tinymce4: 'tiny_mce_4/tinymce.min', wysiwygAdapter: 'mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter' } diff --git a/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/Tab/OrdersTest.php b/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/Tab/OrdersTest.php deleted file mode 100644 index 1ac68b8d7ff57..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Backend/Block/Dashboard/Tab/OrdersTest.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Backend\Block\Dashboard\Tab; - -use Magento\Backend\Block\Dashboard\Graph; -use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\View\LayoutInterface; -use Magento\TestFramework\Helper\Bootstrap; -use PHPUnit\Framework\TestCase; - -/** - * Test for \Magento\Backend\Block\Dashboard\Tab\Orders class. - * - * @magentoAppArea adminhtml - */ -class OrdersTest extends TestCase -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @var LayoutInterface - */ - private $layout; - - /** - * @var Graph - */ - private $graphBlock; - - /** - * @inheritdoc - */ - protected function setUp() - { - $this->objectManager = Bootstrap::getObjectManager(); - $this->layout = $this->objectManager->get(LayoutInterface::class); - $this->graphBlock = $this->layout->createBlock(Graph::class); - } - - /** - * @magentoDataFixture Magento/Sales/_files/order_list_with_invoice.php - * @dataProvider chartUrlDataProvider - * @param string $period - * @param string $expectedAxisRange - * @return void - */ - public function testGetChartUrl(string $period, string $expectedAxisRange): void - { - $this->graphBlock->getRequest()->setParams(['period' => $period]); - $ordersBlock = $this->layout->createBlock(Orders::class); - $decodedChartUrl = urldecode($ordersBlock->getChartUrl()); - $this->assertEquals($expectedAxisRange, $this->getUrlParamData($decodedChartUrl, 'chxr')); - } - - /** - * @return array - */ - public function chartUrlDataProvider(): array - { - return [ - 'Last 24 Hours' => ['24h', '1,0,2,1'], - 'Last 7 Days' => ['7d', '1,0,3,1'], - 'Current Month' => ['1m', '1,0,3,1'], - 'YTD' => ['1y', '1,0,4,1'], - '2YTD' => ['2y', '1,0,4,1'], - ]; - } - - /** - * @param string $chartUrl - * @param string $paramName - * @return string - */ - private function getUrlParamData(string $chartUrl, string $paramName): string - { - $chartUrlSegments = explode('&', $chartUrl); - foreach ($chartUrlSegments as $chartUrlSegment) { - [$paramKey, $paramValue] = explode('=', $chartUrlSegment); - if ($paramKey === $paramName) { - return $paramValue; - } - } - - return ''; - } -} diff --git a/lib/web/chartjs/Chart.bundle.js b/lib/web/chartjs/Chart.bundle.js deleted file mode 100644 index b6f4f388c7459..0000000000000 --- a/lib/web/chartjs/Chart.bundle.js +++ /dev/null @@ -1,20755 +0,0 @@ -/*! - * Chart.js v2.9.3 - * https://www.chartjs.org - * (c) 2019 Chart.js Contributors - * Released under the MIT License - */ -(function (global, factory) { -typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : -typeof define === 'function' && define.amd ? define(factory) : -(global = global || self, global.Chart = factory()); -}(this, (function () { 'use strict'; - -var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; - -function commonjsRequire () { - throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs'); -} - -function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; -} - -function getCjsExportFromNamespace (n) { - return n && n['default'] || n; -} - -var colorName = { - "aliceblue": [240, 248, 255], - "antiquewhite": [250, 235, 215], - "aqua": [0, 255, 255], - "aquamarine": [127, 255, 212], - "azure": [240, 255, 255], - "beige": [245, 245, 220], - "bisque": [255, 228, 196], - "black": [0, 0, 0], - "blanchedalmond": [255, 235, 205], - "blue": [0, 0, 255], - "blueviolet": [138, 43, 226], - "brown": [165, 42, 42], - "burlywood": [222, 184, 135], - "cadetblue": [95, 158, 160], - "chartreuse": [127, 255, 0], - "chocolate": [210, 105, 30], - "coral": [255, 127, 80], - "cornflowerblue": [100, 149, 237], - "cornsilk": [255, 248, 220], - "crimson": [220, 20, 60], - "cyan": [0, 255, 255], - "darkblue": [0, 0, 139], - "darkcyan": [0, 139, 139], - "darkgoldenrod": [184, 134, 11], - "darkgray": [169, 169, 169], - "darkgreen": [0, 100, 0], - "darkgrey": [169, 169, 169], - "darkkhaki": [189, 183, 107], - "darkmagenta": [139, 0, 139], - "darkolivegreen": [85, 107, 47], - "darkorange": [255, 140, 0], - "darkorchid": [153, 50, 204], - "darkred": [139, 0, 0], - "darksalmon": [233, 150, 122], - "darkseagreen": [143, 188, 143], - "darkslateblue": [72, 61, 139], - "darkslategray": [47, 79, 79], - "darkslategrey": [47, 79, 79], - "darkturquoise": [0, 206, 209], - "darkviolet": [148, 0, 211], - "deeppink": [255, 20, 147], - "deepskyblue": [0, 191, 255], - "dimgray": [105, 105, 105], - "dimgrey": [105, 105, 105], - "dodgerblue": [30, 144, 255], - "firebrick": [178, 34, 34], - "floralwhite": [255, 250, 240], - "forestgreen": [34, 139, 34], - "fuchsia": [255, 0, 255], - "gainsboro": [220, 220, 220], - "ghostwhite": [248, 248, 255], - "gold": [255, 215, 0], - "goldenrod": [218, 165, 32], - "gray": [128, 128, 128], - "green": [0, 128, 0], - "greenyellow": [173, 255, 47], - "grey": [128, 128, 128], - "honeydew": [240, 255, 240], - "hotpink": [255, 105, 180], - "indianred": [205, 92, 92], - "indigo": [75, 0, 130], - "ivory": [255, 255, 240], - "khaki": [240, 230, 140], - "lavender": [230, 230, 250], - "lavenderblush": [255, 240, 245], - "lawngreen": [124, 252, 0], - "lemonchiffon": [255, 250, 205], - "lightblue": [173, 216, 230], - "lightcoral": [240, 128, 128], - "lightcyan": [224, 255, 255], - "lightgoldenrodyellow": [250, 250, 210], - "lightgray": [211, 211, 211], - "lightgreen": [144, 238, 144], - "lightgrey": [211, 211, 211], - "lightpink": [255, 182, 193], - "lightsalmon": [255, 160, 122], - "lightseagreen": [32, 178, 170], - "lightskyblue": [135, 206, 250], - "lightslategray": [119, 136, 153], - "lightslategrey": [119, 136, 153], - "lightsteelblue": [176, 196, 222], - "lightyellow": [255, 255, 224], - "lime": [0, 255, 0], - "limegreen": [50, 205, 50], - "linen": [250, 240, 230], - "magenta": [255, 0, 255], - "maroon": [128, 0, 0], - "mediumaquamarine": [102, 205, 170], - "mediumblue": [0, 0, 205], - "mediumorchid": [186, 85, 211], - "mediumpurple": [147, 112, 219], - "mediumseagreen": [60, 179, 113], - "mediumslateblue": [123, 104, 238], - "mediumspringgreen": [0, 250, 154], - "mediumturquoise": [72, 209, 204], - "mediumvioletred": [199, 21, 133], - "midnightblue": [25, 25, 112], - "mintcream": [245, 255, 250], - "mistyrose": [255, 228, 225], - "moccasin": [255, 228, 181], - "navajowhite": [255, 222, 173], - "navy": [0, 0, 128], - "oldlace": [253, 245, 230], - "olive": [128, 128, 0], - "olivedrab": [107, 142, 35], - "orange": [255, 165, 0], - "orangered": [255, 69, 0], - "orchid": [218, 112, 214], - "palegoldenrod": [238, 232, 170], - "palegreen": [152, 251, 152], - "paleturquoise": [175, 238, 238], - "palevioletred": [219, 112, 147], - "papayawhip": [255, 239, 213], - "peachpuff": [255, 218, 185], - "peru": [205, 133, 63], - "pink": [255, 192, 203], - "plum": [221, 160, 221], - "powderblue": [176, 224, 230], - "purple": [128, 0, 128], - "rebeccapurple": [102, 51, 153], - "red": [255, 0, 0], - "rosybrown": [188, 143, 143], - "royalblue": [65, 105, 225], - "saddlebrown": [139, 69, 19], - "salmon": [250, 128, 114], - "sandybrown": [244, 164, 96], - "seagreen": [46, 139, 87], - "seashell": [255, 245, 238], - "sienna": [160, 82, 45], - "silver": [192, 192, 192], - "skyblue": [135, 206, 235], - "slateblue": [106, 90, 205], - "slategray": [112, 128, 144], - "slategrey": [112, 128, 144], - "snow": [255, 250, 250], - "springgreen": [0, 255, 127], - "steelblue": [70, 130, 180], - "tan": [210, 180, 140], - "teal": [0, 128, 128], - "thistle": [216, 191, 216], - "tomato": [255, 99, 71], - "turquoise": [64, 224, 208], - "violet": [238, 130, 238], - "wheat": [245, 222, 179], - "white": [255, 255, 255], - "whitesmoke": [245, 245, 245], - "yellow": [255, 255, 0], - "yellowgreen": [154, 205, 50] -}; - -var conversions = createCommonjsModule(function (module) { -/* MIT license */ - - -// NOTE: conversions should only return primitive values (i.e. arrays, or -// values that give correct `typeof` results). -// do not use box values types (i.e. Number(), String(), etc.) - -var reverseKeywords = {}; -for (var key in colorName) { - if (colorName.hasOwnProperty(key)) { - reverseKeywords[colorName[key]] = key; - } -} - -var convert = module.exports = { - rgb: {channels: 3, labels: 'rgb'}, - hsl: {channels: 3, labels: 'hsl'}, - hsv: {channels: 3, labels: 'hsv'}, - hwb: {channels: 3, labels: 'hwb'}, - cmyk: {channels: 4, labels: 'cmyk'}, - xyz: {channels: 3, labels: 'xyz'}, - lab: {channels: 3, labels: 'lab'}, - lch: {channels: 3, labels: 'lch'}, - hex: {channels: 1, labels: ['hex']}, - keyword: {channels: 1, labels: ['keyword']}, - ansi16: {channels: 1, labels: ['ansi16']}, - ansi256: {channels: 1, labels: ['ansi256']}, - hcg: {channels: 3, labels: ['h', 'c', 'g']}, - apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, - gray: {channels: 1, labels: ['gray']} -}; - -// hide .channels and .labels properties -for (var model in convert) { - if (convert.hasOwnProperty(model)) { - if (!('channels' in convert[model])) { - throw new Error('missing channels property: ' + model); - } - - if (!('labels' in convert[model])) { - throw new Error('missing channel labels property: ' + model); - } - - if (convert[model].labels.length !== convert[model].channels) { - throw new Error('channel and label counts mismatch: ' + model); - } - - var channels = convert[model].channels; - var labels = convert[model].labels; - delete convert[model].channels; - delete convert[model].labels; - Object.defineProperty(convert[model], 'channels', {value: channels}); - Object.defineProperty(convert[model], 'labels', {value: labels}); - } -} - -convert.rgb.hsl = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var min = Math.min(r, g, b); - var max = Math.max(r, g, b); - var delta = max - min; - var h; - var s; - var l; - - if (max === min) { - h = 0; - } else if (r === max) { - h = (g - b) / delta; - } else if (g === max) { - h = 2 + (b - r) / delta; - } else if (b === max) { - h = 4 + (r - g) / delta; - } - - h = Math.min(h * 60, 360); - - if (h < 0) { - h += 360; - } - - l = (min + max) / 2; - - if (max === min) { - s = 0; - } else if (l <= 0.5) { - s = delta / (max + min); - } else { - s = delta / (2 - max - min); - } - - return [h, s * 100, l * 100]; -}; - -convert.rgb.hsv = function (rgb) { - var rdif; - var gdif; - var bdif; - var h; - var s; - - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var v = Math.max(r, g, b); - var diff = v - Math.min(r, g, b); - var diffc = function (c) { - return (v - c) / 6 / diff + 1 / 2; - }; - - if (diff === 0) { - h = s = 0; - } else { - s = diff / v; - rdif = diffc(r); - gdif = diffc(g); - bdif = diffc(b); - - if (r === v) { - h = bdif - gdif; - } else if (g === v) { - h = (1 / 3) + rdif - bdif; - } else if (b === v) { - h = (2 / 3) + gdif - rdif; - } - if (h < 0) { - h += 1; - } else if (h > 1) { - h -= 1; - } - } - - return [ - h * 360, - s * 100, - v * 100 - ]; -}; - -convert.rgb.hwb = function (rgb) { - var r = rgb[0]; - var g = rgb[1]; - var b = rgb[2]; - var h = convert.rgb.hsl(rgb)[0]; - var w = 1 / 255 * Math.min(r, Math.min(g, b)); - - b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); - - return [h, w * 100, b * 100]; -}; - -convert.rgb.cmyk = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var c; - var m; - var y; - var k; - - k = Math.min(1 - r, 1 - g, 1 - b); - c = (1 - r - k) / (1 - k) || 0; - m = (1 - g - k) / (1 - k) || 0; - y = (1 - b - k) / (1 - k) || 0; - - return [c * 100, m * 100, y * 100, k * 100]; -}; - -/** - * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance - * */ -function comparativeDistance(x, y) { - return ( - Math.pow(x[0] - y[0], 2) + - Math.pow(x[1] - y[1], 2) + - Math.pow(x[2] - y[2], 2) - ); -} - -convert.rgb.keyword = function (rgb) { - var reversed = reverseKeywords[rgb]; - if (reversed) { - return reversed; - } - - var currentClosestDistance = Infinity; - var currentClosestKeyword; - - for (var keyword in colorName) { - if (colorName.hasOwnProperty(keyword)) { - var value = colorName[keyword]; - - // Compute comparative distance - var distance = comparativeDistance(rgb, value); - - // Check if its less, if so set as closest - if (distance < currentClosestDistance) { - currentClosestDistance = distance; - currentClosestKeyword = keyword; - } - } - } - - return currentClosestKeyword; -}; - -convert.keyword.rgb = function (keyword) { - return colorName[keyword]; -}; - -convert.rgb.xyz = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - - // assume sRGB - r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); - g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); - b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); - - var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); - var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); - var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); - - return [x * 100, y * 100, z * 100]; -}; - -convert.rgb.lab = function (rgb) { - var xyz = convert.rgb.xyz(rgb); - var x = xyz[0]; - var y = xyz[1]; - var z = xyz[2]; - var l; - var a; - var b; - - x /= 95.047; - y /= 100; - z /= 108.883; - - x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); - - l = (116 * y) - 16; - a = 500 * (x - y); - b = 200 * (y - z); - - return [l, a, b]; -}; - -convert.hsl.rgb = function (hsl) { - var h = hsl[0] / 360; - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var t1; - var t2; - var t3; - var rgb; - var val; - - if (s === 0) { - val = l * 255; - return [val, val, val]; - } - - if (l < 0.5) { - t2 = l * (1 + s); - } else { - t2 = l + s - l * s; - } - - t1 = 2 * l - t2; - - rgb = [0, 0, 0]; - for (var i = 0; i < 3; i++) { - t3 = h + 1 / 3 * -(i - 1); - if (t3 < 0) { - t3++; - } - if (t3 > 1) { - t3--; - } - - if (6 * t3 < 1) { - val = t1 + (t2 - t1) * 6 * t3; - } else if (2 * t3 < 1) { - val = t2; - } else if (3 * t3 < 2) { - val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; - } else { - val = t1; - } - - rgb[i] = val * 255; - } - - return rgb; -}; - -convert.hsl.hsv = function (hsl) { - var h = hsl[0]; - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var smin = s; - var lmin = Math.max(l, 0.01); - var sv; - var v; - - l *= 2; - s *= (l <= 1) ? l : 2 - l; - smin *= lmin <= 1 ? lmin : 2 - lmin; - v = (l + s) / 2; - sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); - - return [h, sv * 100, v * 100]; -}; - -convert.hsv.rgb = function (hsv) { - var h = hsv[0] / 60; - var s = hsv[1] / 100; - var v = hsv[2] / 100; - var hi = Math.floor(h) % 6; - - var f = h - Math.floor(h); - var p = 255 * v * (1 - s); - var q = 255 * v * (1 - (s * f)); - var t = 255 * v * (1 - (s * (1 - f))); - v *= 255; - - switch (hi) { - case 0: - return [v, t, p]; - case 1: - return [q, v, p]; - case 2: - return [p, v, t]; - case 3: - return [p, q, v]; - case 4: - return [t, p, v]; - case 5: - return [v, p, q]; - } -}; - -convert.hsv.hsl = function (hsv) { - var h = hsv[0]; - var s = hsv[1] / 100; - var v = hsv[2] / 100; - var vmin = Math.max(v, 0.01); - var lmin; - var sl; - var l; - - l = (2 - s) * v; - lmin = (2 - s) * vmin; - sl = s * vmin; - sl /= (lmin <= 1) ? lmin : 2 - lmin; - sl = sl || 0; - l /= 2; - - return [h, sl * 100, l * 100]; -}; - -// http://dev.w3.org/csswg/css-color/#hwb-to-rgb -convert.hwb.rgb = function (hwb) { - var h = hwb[0] / 360; - var wh = hwb[1] / 100; - var bl = hwb[2] / 100; - var ratio = wh + bl; - var i; - var v; - var f; - var n; - - // wh + bl cant be > 1 - if (ratio > 1) { - wh /= ratio; - bl /= ratio; - } - - i = Math.floor(6 * h); - v = 1 - bl; - f = 6 * h - i; - - if ((i & 0x01) !== 0) { - f = 1 - f; - } - - n = wh + f * (v - wh); // linear interpolation - - var r; - var g; - var b; - switch (i) { - default: - case 6: - case 0: r = v; g = n; b = wh; break; - case 1: r = n; g = v; b = wh; break; - case 2: r = wh; g = v; b = n; break; - case 3: r = wh; g = n; b = v; break; - case 4: r = n; g = wh; b = v; break; - case 5: r = v; g = wh; b = n; break; - } - - return [r * 255, g * 255, b * 255]; -}; - -convert.cmyk.rgb = function (cmyk) { - var c = cmyk[0] / 100; - var m = cmyk[1] / 100; - var y = cmyk[2] / 100; - var k = cmyk[3] / 100; - var r; - var g; - var b; - - r = 1 - Math.min(1, c * (1 - k) + k); - g = 1 - Math.min(1, m * (1 - k) + k); - b = 1 - Math.min(1, y * (1 - k) + k); - - return [r * 255, g * 255, b * 255]; -}; - -convert.xyz.rgb = function (xyz) { - var x = xyz[0] / 100; - var y = xyz[1] / 100; - var z = xyz[2] / 100; - var r; - var g; - var b; - - r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); - g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); - b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); - - // assume sRGB - r = r > 0.0031308 - ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) - : r * 12.92; - - g = g > 0.0031308 - ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) - : g * 12.92; - - b = b > 0.0031308 - ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) - : b * 12.92; - - r = Math.min(Math.max(0, r), 1); - g = Math.min(Math.max(0, g), 1); - b = Math.min(Math.max(0, b), 1); - - return [r * 255, g * 255, b * 255]; -}; - -convert.xyz.lab = function (xyz) { - var x = xyz[0]; - var y = xyz[1]; - var z = xyz[2]; - var l; - var a; - var b; - - x /= 95.047; - y /= 100; - z /= 108.883; - - x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); - - l = (116 * y) - 16; - a = 500 * (x - y); - b = 200 * (y - z); - - return [l, a, b]; -}; - -convert.lab.xyz = function (lab) { - var l = lab[0]; - var a = lab[1]; - var b = lab[2]; - var x; - var y; - var z; - - y = (l + 16) / 116; - x = a / 500 + y; - z = y - b / 200; - - var y2 = Math.pow(y, 3); - var x2 = Math.pow(x, 3); - var z2 = Math.pow(z, 3); - y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; - x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; - z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; - - x *= 95.047; - y *= 100; - z *= 108.883; - - return [x, y, z]; -}; - -convert.lab.lch = function (lab) { - var l = lab[0]; - var a = lab[1]; - var b = lab[2]; - var hr; - var h; - var c; - - hr = Math.atan2(b, a); - h = hr * 360 / 2 / Math.PI; - - if (h < 0) { - h += 360; - } - - c = Math.sqrt(a * a + b * b); - - return [l, c, h]; -}; - -convert.lch.lab = function (lch) { - var l = lch[0]; - var c = lch[1]; - var h = lch[2]; - var a; - var b; - var hr; - - hr = h / 360 * 2 * Math.PI; - a = c * Math.cos(hr); - b = c * Math.sin(hr); - - return [l, a, b]; -}; - -convert.rgb.ansi16 = function (args) { - var r = args[0]; - var g = args[1]; - var b = args[2]; - var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization - - value = Math.round(value / 50); - - if (value === 0) { - return 30; - } - - var ansi = 30 - + ((Math.round(b / 255) << 2) - | (Math.round(g / 255) << 1) - | Math.round(r / 255)); - - if (value === 2) { - ansi += 60; - } - - return ansi; -}; - -convert.hsv.ansi16 = function (args) { - // optimization here; we already know the value and don't need to get - // it converted for us. - return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); -}; - -convert.rgb.ansi256 = function (args) { - var r = args[0]; - var g = args[1]; - var b = args[2]; - - // we use the extended greyscale palette here, with the exception of - // black and white. normal palette only has 4 greyscale shades. - if (r === g && g === b) { - if (r < 8) { - return 16; - } - - if (r > 248) { - return 231; - } - - return Math.round(((r - 8) / 247) * 24) + 232; - } - - var ansi = 16 - + (36 * Math.round(r / 255 * 5)) - + (6 * Math.round(g / 255 * 5)) - + Math.round(b / 255 * 5); - - return ansi; -}; - -convert.ansi16.rgb = function (args) { - var color = args % 10; - - // handle greyscale - if (color === 0 || color === 7) { - if (args > 50) { - color += 3.5; - } - - color = color / 10.5 * 255; - - return [color, color, color]; - } - - var mult = (~~(args > 50) + 1) * 0.5; - var r = ((color & 1) * mult) * 255; - var g = (((color >> 1) & 1) * mult) * 255; - var b = (((color >> 2) & 1) * mult) * 255; - - return [r, g, b]; -}; - -convert.ansi256.rgb = function (args) { - // handle greyscale - if (args >= 232) { - var c = (args - 232) * 10 + 8; - return [c, c, c]; - } - - args -= 16; - - var rem; - var r = Math.floor(args / 36) / 5 * 255; - var g = Math.floor((rem = args % 36) / 6) / 5 * 255; - var b = (rem % 6) / 5 * 255; - - return [r, g, b]; -}; - -convert.rgb.hex = function (args) { - var integer = ((Math.round(args[0]) & 0xFF) << 16) - + ((Math.round(args[1]) & 0xFF) << 8) - + (Math.round(args[2]) & 0xFF); - - var string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; -}; - -convert.hex.rgb = function (args) { - var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); - if (!match) { - return [0, 0, 0]; - } - - var colorString = match[0]; - - if (match[0].length === 3) { - colorString = colorString.split('').map(function (char) { - return char + char; - }).join(''); - } - - var integer = parseInt(colorString, 16); - var r = (integer >> 16) & 0xFF; - var g = (integer >> 8) & 0xFF; - var b = integer & 0xFF; - - return [r, g, b]; -}; - -convert.rgb.hcg = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var max = Math.max(Math.max(r, g), b); - var min = Math.min(Math.min(r, g), b); - var chroma = (max - min); - var grayscale; - var hue; - - if (chroma < 1) { - grayscale = min / (1 - chroma); - } else { - grayscale = 0; - } - - if (chroma <= 0) { - hue = 0; - } else - if (max === r) { - hue = ((g - b) / chroma) % 6; - } else - if (max === g) { - hue = 2 + (b - r) / chroma; - } else { - hue = 4 + (r - g) / chroma + 4; - } - - hue /= 6; - hue %= 1; - - return [hue * 360, chroma * 100, grayscale * 100]; -}; - -convert.hsl.hcg = function (hsl) { - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var c = 1; - var f = 0; - - if (l < 0.5) { - c = 2.0 * s * l; - } else { - c = 2.0 * s * (1.0 - l); - } - - if (c < 1.0) { - f = (l - 0.5 * c) / (1.0 - c); - } - - return [hsl[0], c * 100, f * 100]; -}; - -convert.hsv.hcg = function (hsv) { - var s = hsv[1] / 100; - var v = hsv[2] / 100; - - var c = s * v; - var f = 0; - - if (c < 1.0) { - f = (v - c) / (1 - c); - } - - return [hsv[0], c * 100, f * 100]; -}; - -convert.hcg.rgb = function (hcg) { - var h = hcg[0] / 360; - var c = hcg[1] / 100; - var g = hcg[2] / 100; - - if (c === 0.0) { - return [g * 255, g * 255, g * 255]; - } - - var pure = [0, 0, 0]; - var hi = (h % 1) * 6; - var v = hi % 1; - var w = 1 - v; - var mg = 0; - - switch (Math.floor(hi)) { - case 0: - pure[0] = 1; pure[1] = v; pure[2] = 0; break; - case 1: - pure[0] = w; pure[1] = 1; pure[2] = 0; break; - case 2: - pure[0] = 0; pure[1] = 1; pure[2] = v; break; - case 3: - pure[0] = 0; pure[1] = w; pure[2] = 1; break; - case 4: - pure[0] = v; pure[1] = 0; pure[2] = 1; break; - default: - pure[0] = 1; pure[1] = 0; pure[2] = w; - } - - mg = (1.0 - c) * g; - - return [ - (c * pure[0] + mg) * 255, - (c * pure[1] + mg) * 255, - (c * pure[2] + mg) * 255 - ]; -}; - -convert.hcg.hsv = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; - - var v = c + g * (1.0 - c); - var f = 0; - - if (v > 0.0) { - f = c / v; - } - - return [hcg[0], f * 100, v * 100]; -}; - -convert.hcg.hsl = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; - - var l = g * (1.0 - c) + 0.5 * c; - var s = 0; - - if (l > 0.0 && l < 0.5) { - s = c / (2 * l); - } else - if (l >= 0.5 && l < 1.0) { - s = c / (2 * (1 - l)); - } - - return [hcg[0], s * 100, l * 100]; -}; - -convert.hcg.hwb = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; - var v = c + g * (1.0 - c); - return [hcg[0], (v - c) * 100, (1 - v) * 100]; -}; - -convert.hwb.hcg = function (hwb) { - var w = hwb[1] / 100; - var b = hwb[2] / 100; - var v = 1 - b; - var c = v - w; - var g = 0; - - if (c < 1) { - g = (v - c) / (1 - c); - } - - return [hwb[0], c * 100, g * 100]; -}; - -convert.apple.rgb = function (apple) { - return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; -}; - -convert.rgb.apple = function (rgb) { - return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; -}; - -convert.gray.rgb = function (args) { - return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; -}; - -convert.gray.hsl = convert.gray.hsv = function (args) { - return [0, 0, args[0]]; -}; - -convert.gray.hwb = function (gray) { - return [0, 100, gray[0]]; -}; - -convert.gray.cmyk = function (gray) { - return [0, 0, 0, gray[0]]; -}; - -convert.gray.lab = function (gray) { - return [gray[0], 0, 0]; -}; - -convert.gray.hex = function (gray) { - var val = Math.round(gray[0] / 100 * 255) & 0xFF; - var integer = (val << 16) + (val << 8) + val; - - var string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; -}; - -convert.rgb.gray = function (rgb) { - var val = (rgb[0] + rgb[1] + rgb[2]) / 3; - return [val / 255 * 100]; -}; -}); -var conversions_1 = conversions.rgb; -var conversions_2 = conversions.hsl; -var conversions_3 = conversions.hsv; -var conversions_4 = conversions.hwb; -var conversions_5 = conversions.cmyk; -var conversions_6 = conversions.xyz; -var conversions_7 = conversions.lab; -var conversions_8 = conversions.lch; -var conversions_9 = conversions.hex; -var conversions_10 = conversions.keyword; -var conversions_11 = conversions.ansi16; -var conversions_12 = conversions.ansi256; -var conversions_13 = conversions.hcg; -var conversions_14 = conversions.apple; -var conversions_15 = conversions.gray; - -/* - this function routes a model to all other models. - - all functions that are routed have a property `.conversion` attached - to the returned synthetic function. This property is an array - of strings, each with the steps in between the 'from' and 'to' - color models (inclusive). - - conversions that are not possible simply are not included. -*/ - -function buildGraph() { - var graph = {}; - // https://jsperf.com/object-keys-vs-for-in-with-closure/3 - var models = Object.keys(conversions); - - for (var len = models.length, i = 0; i < len; i++) { - graph[models[i]] = { - // http://jsperf.com/1-vs-infinity - // micro-opt, but this is simple. - distance: -1, - parent: null - }; - } - - return graph; -} - -// https://en.wikipedia.org/wiki/Breadth-first_search -function deriveBFS(fromModel) { - var graph = buildGraph(); - var queue = [fromModel]; // unshift -> queue -> pop - - graph[fromModel].distance = 0; - - while (queue.length) { - var current = queue.pop(); - var adjacents = Object.keys(conversions[current]); - - for (var len = adjacents.length, i = 0; i < len; i++) { - var adjacent = adjacents[i]; - var node = graph[adjacent]; - - if (node.distance === -1) { - node.distance = graph[current].distance + 1; - node.parent = current; - queue.unshift(adjacent); - } - } - } - - return graph; -} - -function link(from, to) { - return function (args) { - return to(from(args)); - }; -} - -function wrapConversion(toModel, graph) { - var path = [graph[toModel].parent, toModel]; - var fn = conversions[graph[toModel].parent][toModel]; - - var cur = graph[toModel].parent; - while (graph[cur].parent) { - path.unshift(graph[cur].parent); - fn = link(conversions[graph[cur].parent][cur], fn); - cur = graph[cur].parent; - } - - fn.conversion = path; - return fn; -} - -var route = function (fromModel) { - var graph = deriveBFS(fromModel); - var conversion = {}; - - var models = Object.keys(graph); - for (var len = models.length, i = 0; i < len; i++) { - var toModel = models[i]; - var node = graph[toModel]; - - if (node.parent === null) { - // no possible conversion, or this node is the source model. - continue; - } - - conversion[toModel] = wrapConversion(toModel, graph); - } - - return conversion; -}; - -var convert = {}; - -var models = Object.keys(conversions); - -function wrapRaw(fn) { - var wrappedFn = function (args) { - if (args === undefined || args === null) { - return args; - } - - if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } - - return fn(args); - }; - - // preserve .conversion property if there is one - if ('conversion' in fn) { - wrappedFn.conversion = fn.conversion; - } - - return wrappedFn; -} - -function wrapRounded(fn) { - var wrappedFn = function (args) { - if (args === undefined || args === null) { - return args; - } - - if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } - - var result = fn(args); - - // we're assuming the result is an array here. - // see notice in conversions.js; don't use box types - // in conversion functions. - if (typeof result === 'object') { - for (var len = result.length, i = 0; i < len; i++) { - result[i] = Math.round(result[i]); - } - } - - return result; - }; - - // preserve .conversion property if there is one - if ('conversion' in fn) { - wrappedFn.conversion = fn.conversion; - } - - return wrappedFn; -} - -models.forEach(function (fromModel) { - convert[fromModel] = {}; - - Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); - Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); - - var routes = route(fromModel); - var routeModels = Object.keys(routes); - - routeModels.forEach(function (toModel) { - var fn = routes[toModel]; - - convert[fromModel][toModel] = wrapRounded(fn); - convert[fromModel][toModel].raw = wrapRaw(fn); - }); -}); - -var colorConvert = convert; - -var colorName$1 = { - "aliceblue": [240, 248, 255], - "antiquewhite": [250, 235, 215], - "aqua": [0, 255, 255], - "aquamarine": [127, 255, 212], - "azure": [240, 255, 255], - "beige": [245, 245, 220], - "bisque": [255, 228, 196], - "black": [0, 0, 0], - "blanchedalmond": [255, 235, 205], - "blue": [0, 0, 255], - "blueviolet": [138, 43, 226], - "brown": [165, 42, 42], - "burlywood": [222, 184, 135], - "cadetblue": [95, 158, 160], - "chartreuse": [127, 255, 0], - "chocolate": [210, 105, 30], - "coral": [255, 127, 80], - "cornflowerblue": [100, 149, 237], - "cornsilk": [255, 248, 220], - "crimson": [220, 20, 60], - "cyan": [0, 255, 255], - "darkblue": [0, 0, 139], - "darkcyan": [0, 139, 139], - "darkgoldenrod": [184, 134, 11], - "darkgray": [169, 169, 169], - "darkgreen": [0, 100, 0], - "darkgrey": [169, 169, 169], - "darkkhaki": [189, 183, 107], - "darkmagenta": [139, 0, 139], - "darkolivegreen": [85, 107, 47], - "darkorange": [255, 140, 0], - "darkorchid": [153, 50, 204], - "darkred": [139, 0, 0], - "darksalmon": [233, 150, 122], - "darkseagreen": [143, 188, 143], - "darkslateblue": [72, 61, 139], - "darkslategray": [47, 79, 79], - "darkslategrey": [47, 79, 79], - "darkturquoise": [0, 206, 209], - "darkviolet": [148, 0, 211], - "deeppink": [255, 20, 147], - "deepskyblue": [0, 191, 255], - "dimgray": [105, 105, 105], - "dimgrey": [105, 105, 105], - "dodgerblue": [30, 144, 255], - "firebrick": [178, 34, 34], - "floralwhite": [255, 250, 240], - "forestgreen": [34, 139, 34], - "fuchsia": [255, 0, 255], - "gainsboro": [220, 220, 220], - "ghostwhite": [248, 248, 255], - "gold": [255, 215, 0], - "goldenrod": [218, 165, 32], - "gray": [128, 128, 128], - "green": [0, 128, 0], - "greenyellow": [173, 255, 47], - "grey": [128, 128, 128], - "honeydew": [240, 255, 240], - "hotpink": [255, 105, 180], - "indianred": [205, 92, 92], - "indigo": [75, 0, 130], - "ivory": [255, 255, 240], - "khaki": [240, 230, 140], - "lavender": [230, 230, 250], - "lavenderblush": [255, 240, 245], - "lawngreen": [124, 252, 0], - "lemonchiffon": [255, 250, 205], - "lightblue": [173, 216, 230], - "lightcoral": [240, 128, 128], - "lightcyan": [224, 255, 255], - "lightgoldenrodyellow": [250, 250, 210], - "lightgray": [211, 211, 211], - "lightgreen": [144, 238, 144], - "lightgrey": [211, 211, 211], - "lightpink": [255, 182, 193], - "lightsalmon": [255, 160, 122], - "lightseagreen": [32, 178, 170], - "lightskyblue": [135, 206, 250], - "lightslategray": [119, 136, 153], - "lightslategrey": [119, 136, 153], - "lightsteelblue": [176, 196, 222], - "lightyellow": [255, 255, 224], - "lime": [0, 255, 0], - "limegreen": [50, 205, 50], - "linen": [250, 240, 230], - "magenta": [255, 0, 255], - "maroon": [128, 0, 0], - "mediumaquamarine": [102, 205, 170], - "mediumblue": [0, 0, 205], - "mediumorchid": [186, 85, 211], - "mediumpurple": [147, 112, 219], - "mediumseagreen": [60, 179, 113], - "mediumslateblue": [123, 104, 238], - "mediumspringgreen": [0, 250, 154], - "mediumturquoise": [72, 209, 204], - "mediumvioletred": [199, 21, 133], - "midnightblue": [25, 25, 112], - "mintcream": [245, 255, 250], - "mistyrose": [255, 228, 225], - "moccasin": [255, 228, 181], - "navajowhite": [255, 222, 173], - "navy": [0, 0, 128], - "oldlace": [253, 245, 230], - "olive": [128, 128, 0], - "olivedrab": [107, 142, 35], - "orange": [255, 165, 0], - "orangered": [255, 69, 0], - "orchid": [218, 112, 214], - "palegoldenrod": [238, 232, 170], - "palegreen": [152, 251, 152], - "paleturquoise": [175, 238, 238], - "palevioletred": [219, 112, 147], - "papayawhip": [255, 239, 213], - "peachpuff": [255, 218, 185], - "peru": [205, 133, 63], - "pink": [255, 192, 203], - "plum": [221, 160, 221], - "powderblue": [176, 224, 230], - "purple": [128, 0, 128], - "rebeccapurple": [102, 51, 153], - "red": [255, 0, 0], - "rosybrown": [188, 143, 143], - "royalblue": [65, 105, 225], - "saddlebrown": [139, 69, 19], - "salmon": [250, 128, 114], - "sandybrown": [244, 164, 96], - "seagreen": [46, 139, 87], - "seashell": [255, 245, 238], - "sienna": [160, 82, 45], - "silver": [192, 192, 192], - "skyblue": [135, 206, 235], - "slateblue": [106, 90, 205], - "slategray": [112, 128, 144], - "slategrey": [112, 128, 144], - "snow": [255, 250, 250], - "springgreen": [0, 255, 127], - "steelblue": [70, 130, 180], - "tan": [210, 180, 140], - "teal": [0, 128, 128], - "thistle": [216, 191, 216], - "tomato": [255, 99, 71], - "turquoise": [64, 224, 208], - "violet": [238, 130, 238], - "wheat": [245, 222, 179], - "white": [255, 255, 255], - "whitesmoke": [245, 245, 245], - "yellow": [255, 255, 0], - "yellowgreen": [154, 205, 50] -}; - -/* MIT license */ - - -var colorString = { - getRgba: getRgba, - getHsla: getHsla, - getRgb: getRgb, - getHsl: getHsl, - getHwb: getHwb, - getAlpha: getAlpha, - - hexString: hexString, - rgbString: rgbString, - rgbaString: rgbaString, - percentString: percentString, - percentaString: percentaString, - hslString: hslString, - hslaString: hslaString, - hwbString: hwbString, - keyword: keyword -}; - -function getRgba(string) { - if (!string) { - return; - } - var abbr = /^#([a-fA-F0-9]{3,4})$/i, - hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, - rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, - per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, - keyword = /(\w+)/; - - var rgb = [0, 0, 0], - a = 1, - match = string.match(abbr), - hexAlpha = ""; - if (match) { - match = match[1]; - hexAlpha = match[3]; - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match[i] + match[i], 16); - } - if (hexAlpha) { - a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; - } - } - else if (match = string.match(hex)) { - hexAlpha = match[2]; - match = match[1]; - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); - } - if (hexAlpha) { - a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; - } - } - else if (match = string.match(rgba)) { - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match[i + 1]); - } - a = parseFloat(match[4]); - } - else if (match = string.match(per)) { - for (var i = 0; i < rgb.length; i++) { - rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); - } - a = parseFloat(match[4]); - } - else if (match = string.match(keyword)) { - if (match[1] == "transparent") { - return [0, 0, 0, 0]; - } - rgb = colorName$1[match[1]]; - if (!rgb) { - return; - } - } - - for (var i = 0; i < rgb.length; i++) { - rgb[i] = scale(rgb[i], 0, 255); - } - if (!a && a != 0) { - a = 1; - } - else { - a = scale(a, 0, 1); - } - rgb[3] = a; - return rgb; -} - -function getHsla(string) { - if (!string) { - return; - } - var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; - var match = string.match(hsl); - if (match) { - var alpha = parseFloat(match[4]); - var h = scale(parseInt(match[1]), 0, 360), - s = scale(parseFloat(match[2]), 0, 100), - l = scale(parseFloat(match[3]), 0, 100), - a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); - return [h, s, l, a]; - } -} - -function getHwb(string) { - if (!string) { - return; - } - var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; - var match = string.match(hwb); - if (match) { - var alpha = parseFloat(match[4]); - var h = scale(parseInt(match[1]), 0, 360), - w = scale(parseFloat(match[2]), 0, 100), - b = scale(parseFloat(match[3]), 0, 100), - a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); - return [h, w, b, a]; - } -} - -function getRgb(string) { - var rgba = getRgba(string); - return rgba && rgba.slice(0, 3); -} - -function getHsl(string) { - var hsla = getHsla(string); - return hsla && hsla.slice(0, 3); -} - -function getAlpha(string) { - var vals = getRgba(string); - if (vals) { - return vals[3]; - } - else if (vals = getHsla(string)) { - return vals[3]; - } - else if (vals = getHwb(string)) { - return vals[3]; - } -} - -// generators -function hexString(rgba, a) { - var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; - return "#" + hexDouble(rgba[0]) - + hexDouble(rgba[1]) - + hexDouble(rgba[2]) - + ( - (a >= 0 && a < 1) - ? hexDouble(Math.round(a * 255)) - : "" - ); -} - -function rgbString(rgba, alpha) { - if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { - return rgbaString(rgba, alpha); - } - return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; -} - -function rgbaString(rgba, alpha) { - if (alpha === undefined) { - alpha = (rgba[3] !== undefined ? rgba[3] : 1); - } - return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] - + ", " + alpha + ")"; -} - -function percentString(rgba, alpha) { - if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { - return percentaString(rgba, alpha); - } - var r = Math.round(rgba[0]/255 * 100), - g = Math.round(rgba[1]/255 * 100), - b = Math.round(rgba[2]/255 * 100); - - return "rgb(" + r + "%, " + g + "%, " + b + "%)"; -} - -function percentaString(rgba, alpha) { - var r = Math.round(rgba[0]/255 * 100), - g = Math.round(rgba[1]/255 * 100), - b = Math.round(rgba[2]/255 * 100); - return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; -} - -function hslString(hsla, alpha) { - if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { - return hslaString(hsla, alpha); - } - return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; -} - -function hslaString(hsla, alpha) { - if (alpha === undefined) { - alpha = (hsla[3] !== undefined ? hsla[3] : 1); - } - return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " - + alpha + ")"; -} - -// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax -// (hwb have alpha optional & 1 is default value) -function hwbString(hwb, alpha) { - if (alpha === undefined) { - alpha = (hwb[3] !== undefined ? hwb[3] : 1); - } - return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" - + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; -} - -function keyword(rgb) { - return reverseNames[rgb.slice(0, 3)]; -} - -// helpers -function scale(num, min, max) { - return Math.min(Math.max(min, num), max); -} - -function hexDouble(num) { - var str = num.toString(16).toUpperCase(); - return (str.length < 2) ? "0" + str : str; -} - - -//create a list of reverse color names -var reverseNames = {}; -for (var name in colorName$1) { - reverseNames[colorName$1[name]] = name; -} - -/* MIT license */ - - - -var Color = function (obj) { - if (obj instanceof Color) { - return obj; - } - if (!(this instanceof Color)) { - return new Color(obj); - } - - this.valid = false; - this.values = { - rgb: [0, 0, 0], - hsl: [0, 0, 0], - hsv: [0, 0, 0], - hwb: [0, 0, 0], - cmyk: [0, 0, 0, 0], - alpha: 1 - }; - - // parse Color() argument - var vals; - if (typeof obj === 'string') { - vals = colorString.getRgba(obj); - if (vals) { - this.setValues('rgb', vals); - } else if (vals = colorString.getHsla(obj)) { - this.setValues('hsl', vals); - } else if (vals = colorString.getHwb(obj)) { - this.setValues('hwb', vals); - } - } else if (typeof obj === 'object') { - vals = obj; - if (vals.r !== undefined || vals.red !== undefined) { - this.setValues('rgb', vals); - } else if (vals.l !== undefined || vals.lightness !== undefined) { - this.setValues('hsl', vals); - } else if (vals.v !== undefined || vals.value !== undefined) { - this.setValues('hsv', vals); - } else if (vals.w !== undefined || vals.whiteness !== undefined) { - this.setValues('hwb', vals); - } else if (vals.c !== undefined || vals.cyan !== undefined) { - this.setValues('cmyk', vals); - } - } -}; - -Color.prototype = { - isValid: function () { - return this.valid; - }, - rgb: function () { - return this.setSpace('rgb', arguments); - }, - hsl: function () { - return this.setSpace('hsl', arguments); - }, - hsv: function () { - return this.setSpace('hsv', arguments); - }, - hwb: function () { - return this.setSpace('hwb', arguments); - }, - cmyk: function () { - return this.setSpace('cmyk', arguments); - }, - - rgbArray: function () { - return this.values.rgb; - }, - hslArray: function () { - return this.values.hsl; - }, - hsvArray: function () { - return this.values.hsv; - }, - hwbArray: function () { - var values = this.values; - if (values.alpha !== 1) { - return values.hwb.concat([values.alpha]); - } - return values.hwb; - }, - cmykArray: function () { - return this.values.cmyk; - }, - rgbaArray: function () { - var values = this.values; - return values.rgb.concat([values.alpha]); - }, - hslaArray: function () { - var values = this.values; - return values.hsl.concat([values.alpha]); - }, - alpha: function (val) { - if (val === undefined) { - return this.values.alpha; - } - this.setValues('alpha', val); - return this; - }, - - red: function (val) { - return this.setChannel('rgb', 0, val); - }, - green: function (val) { - return this.setChannel('rgb', 1, val); - }, - blue: function (val) { - return this.setChannel('rgb', 2, val); - }, - hue: function (val) { - if (val) { - val %= 360; - val = val < 0 ? 360 + val : val; - } - return this.setChannel('hsl', 0, val); - }, - saturation: function (val) { - return this.setChannel('hsl', 1, val); - }, - lightness: function (val) { - return this.setChannel('hsl', 2, val); - }, - saturationv: function (val) { - return this.setChannel('hsv', 1, val); - }, - whiteness: function (val) { - return this.setChannel('hwb', 1, val); - }, - blackness: function (val) { - return this.setChannel('hwb', 2, val); - }, - value: function (val) { - return this.setChannel('hsv', 2, val); - }, - cyan: function (val) { - return this.setChannel('cmyk', 0, val); - }, - magenta: function (val) { - return this.setChannel('cmyk', 1, val); - }, - yellow: function (val) { - return this.setChannel('cmyk', 2, val); - }, - black: function (val) { - return this.setChannel('cmyk', 3, val); - }, - - hexString: function () { - return colorString.hexString(this.values.rgb); - }, - rgbString: function () { - return colorString.rgbString(this.values.rgb, this.values.alpha); - }, - rgbaString: function () { - return colorString.rgbaString(this.values.rgb, this.values.alpha); - }, - percentString: function () { - return colorString.percentString(this.values.rgb, this.values.alpha); - }, - hslString: function () { - return colorString.hslString(this.values.hsl, this.values.alpha); - }, - hslaString: function () { - return colorString.hslaString(this.values.hsl, this.values.alpha); - }, - hwbString: function () { - return colorString.hwbString(this.values.hwb, this.values.alpha); - }, - keyword: function () { - return colorString.keyword(this.values.rgb, this.values.alpha); - }, - - rgbNumber: function () { - var rgb = this.values.rgb; - return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; - }, - - luminosity: function () { - // http://www.w3.org/TR/WCAG20/#relativeluminancedef - var rgb = this.values.rgb; - var lum = []; - for (var i = 0; i < rgb.length; i++) { - var chan = rgb[i] / 255; - lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); - } - return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; - }, - - contrast: function (color2) { - // http://www.w3.org/TR/WCAG20/#contrast-ratiodef - var lum1 = this.luminosity(); - var lum2 = color2.luminosity(); - if (lum1 > lum2) { - return (lum1 + 0.05) / (lum2 + 0.05); - } - return (lum2 + 0.05) / (lum1 + 0.05); - }, - - level: function (color2) { - var contrastRatio = this.contrast(color2); - if (contrastRatio >= 7.1) { - return 'AAA'; - } - - return (contrastRatio >= 4.5) ? 'AA' : ''; - }, - - dark: function () { - // YIQ equation from http://24ways.org/2010/calculating-color-contrast - var rgb = this.values.rgb; - var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; - return yiq < 128; - }, - - light: function () { - return !this.dark(); - }, - - negate: function () { - var rgb = []; - for (var i = 0; i < 3; i++) { - rgb[i] = 255 - this.values.rgb[i]; - } - this.setValues('rgb', rgb); - return this; - }, - - lighten: function (ratio) { - var hsl = this.values.hsl; - hsl[2] += hsl[2] * ratio; - this.setValues('hsl', hsl); - return this; - }, - - darken: function (ratio) { - var hsl = this.values.hsl; - hsl[2] -= hsl[2] * ratio; - this.setValues('hsl', hsl); - return this; - }, - - saturate: function (ratio) { - var hsl = this.values.hsl; - hsl[1] += hsl[1] * ratio; - this.setValues('hsl', hsl); - return this; - }, - - desaturate: function (ratio) { - var hsl = this.values.hsl; - hsl[1] -= hsl[1] * ratio; - this.setValues('hsl', hsl); - return this; - }, - - whiten: function (ratio) { - var hwb = this.values.hwb; - hwb[1] += hwb[1] * ratio; - this.setValues('hwb', hwb); - return this; - }, - - blacken: function (ratio) { - var hwb = this.values.hwb; - hwb[2] += hwb[2] * ratio; - this.setValues('hwb', hwb); - return this; - }, - - greyscale: function () { - var rgb = this.values.rgb; - // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale - var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; - this.setValues('rgb', [val, val, val]); - return this; - }, - - clearer: function (ratio) { - var alpha = this.values.alpha; - this.setValues('alpha', alpha - (alpha * ratio)); - return this; - }, - - opaquer: function (ratio) { - var alpha = this.values.alpha; - this.setValues('alpha', alpha + (alpha * ratio)); - return this; - }, - - rotate: function (degrees) { - var hsl = this.values.hsl; - var hue = (hsl[0] + degrees) % 360; - hsl[0] = hue < 0 ? 360 + hue : hue; - this.setValues('hsl', hsl); - return this; - }, - - /** - * Ported from sass implementation in C - * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 - */ - mix: function (mixinColor, weight) { - var color1 = this; - var color2 = mixinColor; - var p = weight === undefined ? 0.5 : weight; - - var w = 2 * p - 1; - var a = color1.alpha() - color2.alpha(); - - var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; - var w2 = 1 - w1; - - return this - .rgb( - w1 * color1.red() + w2 * color2.red(), - w1 * color1.green() + w2 * color2.green(), - w1 * color1.blue() + w2 * color2.blue() - ) - .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); - }, - - toJSON: function () { - return this.rgb(); - }, - - clone: function () { - // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, - // making the final build way to big to embed in Chart.js. So let's do it manually, - // assuming that values to clone are 1 dimension arrays containing only numbers, - // except 'alpha' which is a number. - var result = new Color(); - var source = this.values; - var target = result.values; - var value, type; - - for (var prop in source) { - if (source.hasOwnProperty(prop)) { - value = source[prop]; - type = ({}).toString.call(value); - if (type === '[object Array]') { - target[prop] = value.slice(0); - } else if (type === '[object Number]') { - target[prop] = value; - } else { - console.error('unexpected color value:', value); - } - } - } - - return result; - } -}; - -Color.prototype.spaces = { - rgb: ['red', 'green', 'blue'], - hsl: ['hue', 'saturation', 'lightness'], - hsv: ['hue', 'saturation', 'value'], - hwb: ['hue', 'whiteness', 'blackness'], - cmyk: ['cyan', 'magenta', 'yellow', 'black'] -}; - -Color.prototype.maxes = { - rgb: [255, 255, 255], - hsl: [360, 100, 100], - hsv: [360, 100, 100], - hwb: [360, 100, 100], - cmyk: [100, 100, 100, 100] -}; - -Color.prototype.getValues = function (space) { - var values = this.values; - var vals = {}; - - for (var i = 0; i < space.length; i++) { - vals[space.charAt(i)] = values[space][i]; - } - - if (values.alpha !== 1) { - vals.a = values.alpha; - } - - // {r: 255, g: 255, b: 255, a: 0.4} - return vals; -}; - -Color.prototype.setValues = function (space, vals) { - var values = this.values; - var spaces = this.spaces; - var maxes = this.maxes; - var alpha = 1; - var i; - - this.valid = true; - - if (space === 'alpha') { - alpha = vals; - } else if (vals.length) { - // [10, 10, 10] - values[space] = vals.slice(0, space.length); - alpha = vals[space.length]; - } else if (vals[space.charAt(0)] !== undefined) { - // {r: 10, g: 10, b: 10} - for (i = 0; i < space.length; i++) { - values[space][i] = vals[space.charAt(i)]; - } - - alpha = vals.a; - } else if (vals[spaces[space][0]] !== undefined) { - // {red: 10, green: 10, blue: 10} - var chans = spaces[space]; - - for (i = 0; i < space.length; i++) { - values[space][i] = vals[chans[i]]; - } - - alpha = vals.alpha; - } - - values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); - - if (space === 'alpha') { - return false; - } - - var capped; - - // cap values of the space prior converting all values - for (i = 0; i < space.length; i++) { - capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); - values[space][i] = Math.round(capped); - } - - // convert to all the other color spaces - for (var sname in spaces) { - if (sname !== space) { - values[sname] = colorConvert[space][sname](values[space]); - } - } - - return true; -}; - -Color.prototype.setSpace = function (space, args) { - var vals = args[0]; - - if (vals === undefined) { - // color.rgb() - return this.getValues(space); - } - - // color.rgb(10, 10, 10) - if (typeof vals === 'number') { - vals = Array.prototype.slice.call(args); - } - - this.setValues(space, vals); - return this; -}; - -Color.prototype.setChannel = function (space, index, val) { - var svalues = this.values[space]; - if (val === undefined) { - // color.red() - return svalues[index]; - } else if (val === svalues[index]) { - // color.red(color.red()) - return this; - } - - // color.red(100) - svalues[index] = val; - this.setValues(space, svalues); - - return this; -}; - -if (typeof window !== 'undefined') { - window.Color = Color; -} - -var chartjsColor = Color; - -/** - * @namespace Chart.helpers - */ -var helpers = { - /** - * An empty function that can be used, for example, for optional callback. - */ - noop: function() {}, - - /** - * Returns a unique id, sequentially generated from a global variable. - * @returns {number} - * @function - */ - uid: (function() { - var id = 0; - return function() { - return id++; - }; - }()), - - /** - * Returns true if `value` is neither null nor undefined, else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @since 2.7.0 - */ - isNullOrUndef: function(value) { - return value === null || typeof value === 'undefined'; - }, - - /** - * Returns true if `value` is an array (including typed arrays), else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @function - */ - isArray: function(value) { - if (Array.isArray && Array.isArray(value)) { - return true; - } - var type = Object.prototype.toString.call(value); - if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { - return true; - } - return false; - }, - - /** - * Returns true if `value` is an object (excluding null), else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @since 2.7.0 - */ - isObject: function(value) { - return value !== null && Object.prototype.toString.call(value) === '[object Object]'; - }, - - /** - * Returns true if `value` is a finite number, else returns false - * @param {*} value - The value to test. - * @returns {boolean} - */ - isFinite: function(value) { - return (typeof value === 'number' || value instanceof Number) && isFinite(value); - }, - - /** - * Returns `value` if defined, else returns `defaultValue`. - * @param {*} value - The value to return if defined. - * @param {*} defaultValue - The value to return if `value` is undefined. - * @returns {*} - */ - valueOrDefault: function(value, defaultValue) { - return typeof value === 'undefined' ? defaultValue : value; - }, - - /** - * Returns value at the given `index` in array if defined, else returns `defaultValue`. - * @param {Array} value - The array to lookup for value at `index`. - * @param {number} index - The index in `value` to lookup for value. - * @param {*} defaultValue - The value to return if `value[index]` is undefined. - * @returns {*} - */ - valueAtIndexOrDefault: function(value, index, defaultValue) { - return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); - }, - - /** - * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the - * value returned by `fn`. If `fn` is not a function, this method returns undefined. - * @param {function} fn - The function to call. - * @param {Array|undefined|null} args - The arguments with which `fn` should be called. - * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. - * @returns {*} - */ - callback: function(fn, args, thisArg) { - if (fn && typeof fn.call === 'function') { - return fn.apply(thisArg, args); - } - }, - - /** - * Note(SB) for performance sake, this method should only be used when loopable type - * is unknown or in none intensive code (not called often and small loopable). Else - * it's preferable to use a regular for() loop and save extra function calls. - * @param {object|Array} loopable - The object or array to be iterated. - * @param {function} fn - The function to call for each item. - * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. - * @param {boolean} [reverse] - If true, iterates backward on the loopable. - */ - each: function(loopable, fn, thisArg, reverse) { - var i, len, keys; - if (helpers.isArray(loopable)) { - len = loopable.length; - if (reverse) { - for (i = len - 1; i >= 0; i--) { - fn.call(thisArg, loopable[i], i); - } - } else { - for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[i], i); - } - } - } else if (helpers.isObject(loopable)) { - keys = Object.keys(loopable); - len = keys.length; - for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[keys[i]], keys[i]); - } - } - }, - - /** - * Returns true if the `a0` and `a1` arrays have the same content, else returns false. - * @see https://stackoverflow.com/a/14853974 - * @param {Array} a0 - The array to compare - * @param {Array} a1 - The array to compare - * @returns {boolean} - */ - arrayEquals: function(a0, a1) { - var i, ilen, v0, v1; - - if (!a0 || !a1 || a0.length !== a1.length) { - return false; - } - - for (i = 0, ilen = a0.length; i < ilen; ++i) { - v0 = a0[i]; - v1 = a1[i]; - - if (v0 instanceof Array && v1 instanceof Array) { - if (!helpers.arrayEquals(v0, v1)) { - return false; - } - } else if (v0 !== v1) { - // NOTE: two different object instances will never be equal: {x:20} != {x:20} - return false; - } - } - - return true; - }, - - /** - * Returns a deep copy of `source` without keeping references on objects and arrays. - * @param {*} source - The value to clone. - * @returns {*} - */ - clone: function(source) { - if (helpers.isArray(source)) { - return source.map(helpers.clone); - } - - if (helpers.isObject(source)) { - var target = {}; - var keys = Object.keys(source); - var klen = keys.length; - var k = 0; - - for (; k < klen; ++k) { - target[keys[k]] = helpers.clone(source[keys[k]]); - } - - return target; - } - - return source; - }, - - /** - * The default merger when Chart.helpers.merge is called without merger option. - * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. - * @private - */ - _merger: function(key, target, source, options) { - var tval = target[key]; - var sval = source[key]; - - if (helpers.isObject(tval) && helpers.isObject(sval)) { - helpers.merge(tval, sval, options); - } else { - target[key] = helpers.clone(sval); - } - }, - - /** - * Merges source[key] in target[key] only if target[key] is undefined. - * @private - */ - _mergerIf: function(key, target, source) { - var tval = target[key]; - var sval = source[key]; - - if (helpers.isObject(tval) && helpers.isObject(sval)) { - helpers.mergeIf(tval, sval); - } else if (!target.hasOwnProperty(key)) { - target[key] = helpers.clone(sval); - } - }, - - /** - * Recursively deep copies `source` properties into `target` with the given `options`. - * IMPORTANT: `target` is not cloned and will be updated with `source` properties. - * @param {object} target - The target object in which all sources are merged into. - * @param {object|object[]} source - Object(s) to merge into `target`. - * @param {object} [options] - Merging options: - * @param {function} [options.merger] - The merge method (key, target, source, options) - * @returns {object} The `target` object. - */ - merge: function(target, source, options) { - var sources = helpers.isArray(source) ? source : [source]; - var ilen = sources.length; - var merge, i, keys, klen, k; - - if (!helpers.isObject(target)) { - return target; - } - - options = options || {}; - merge = options.merger || helpers._merger; - - for (i = 0; i < ilen; ++i) { - source = sources[i]; - if (!helpers.isObject(source)) { - continue; - } - - keys = Object.keys(source); - for (k = 0, klen = keys.length; k < klen; ++k) { - merge(keys[k], target, source, options); - } - } - - return target; - }, - - /** - * Recursively deep copies `source` properties into `target` *only* if not defined in target. - * IMPORTANT: `target` is not cloned and will be updated with `source` properties. - * @param {object} target - The target object in which all sources are merged into. - * @param {object|object[]} source - Object(s) to merge into `target`. - * @returns {object} The `target` object. - */ - mergeIf: function(target, source) { - return helpers.merge(target, source, {merger: helpers._mergerIf}); - }, - - /** - * Applies the contents of two or more objects together into the first object. - * @param {object} target - The target object in which all objects are merged into. - * @param {object} arg1 - Object containing additional properties to merge in target. - * @param {object} argN - Additional objects containing properties to merge in target. - * @returns {object} The `target` object. - */ - extend: Object.assign || function(target) { - return helpers.merge(target, [].slice.call(arguments, 1), { - merger: function(key, dst, src) { - dst[key] = src[key]; - } - }); - }, - - /** - * Basic javascript inheritance based on the model created in Backbone.js - */ - inherits: function(extensions) { - var me = this; - var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { - return me.apply(this, arguments); - }; - - var Surrogate = function() { - this.constructor = ChartElement; - }; - - Surrogate.prototype = me.prototype; - ChartElement.prototype = new Surrogate(); - ChartElement.extend = helpers.inherits; - - if (extensions) { - helpers.extend(ChartElement.prototype, extensions); - } - - ChartElement.__super__ = me.prototype; - return ChartElement; - }, - - _deprecated: function(scope, value, previous, current) { - if (value !== undefined) { - console.warn(scope + ': "' + previous + - '" is deprecated. Please use "' + current + '" instead'); - } - } -}; - -var helpers_core = helpers; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.helpers.callback instead. - * @function Chart.helpers.callCallback - * @deprecated since version 2.6.0 - * @todo remove at version 3 - * @private - */ -helpers.callCallback = helpers.callback; - -/** - * Provided for backward compatibility, use Array.prototype.indexOf instead. - * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ - * @function Chart.helpers.indexOf - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers.indexOf = function(array, item, fromIndex) { - return Array.prototype.indexOf.call(array, item, fromIndex); -}; - -/** - * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. - * @function Chart.helpers.getValueOrDefault - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers.getValueOrDefault = helpers.valueOrDefault; - -/** - * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. - * @function Chart.helpers.getValueAtIndexOrDefault - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; - -/** - * Easing functions adapted from Robert Penner's easing equations. - * @namespace Chart.helpers.easingEffects - * @see http://www.robertpenner.com/easing/ - */ -var effects = { - linear: function(t) { - return t; - }, - - easeInQuad: function(t) { - return t * t; - }, - - easeOutQuad: function(t) { - return -t * (t - 2); - }, - - easeInOutQuad: function(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t; - } - return -0.5 * ((--t) * (t - 2) - 1); - }, - - easeInCubic: function(t) { - return t * t * t; - }, - - easeOutCubic: function(t) { - return (t = t - 1) * t * t + 1; - }, - - easeInOutCubic: function(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t; - } - return 0.5 * ((t -= 2) * t * t + 2); - }, - - easeInQuart: function(t) { - return t * t * t * t; - }, - - easeOutQuart: function(t) { - return -((t = t - 1) * t * t * t - 1); - }, - - easeInOutQuart: function(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t * t; - } - return -0.5 * ((t -= 2) * t * t * t - 2); - }, - - easeInQuint: function(t) { - return t * t * t * t * t; - }, - - easeOutQuint: function(t) { - return (t = t - 1) * t * t * t * t + 1; - }, - - easeInOutQuint: function(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t * t * t; - } - return 0.5 * ((t -= 2) * t * t * t * t + 2); - }, - - easeInSine: function(t) { - return -Math.cos(t * (Math.PI / 2)) + 1; - }, - - easeOutSine: function(t) { - return Math.sin(t * (Math.PI / 2)); - }, - - easeInOutSine: function(t) { - return -0.5 * (Math.cos(Math.PI * t) - 1); - }, - - easeInExpo: function(t) { - return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); - }, - - easeOutExpo: function(t) { - return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; - }, - - easeInOutExpo: function(t) { - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if ((t /= 0.5) < 1) { - return 0.5 * Math.pow(2, 10 * (t - 1)); - } - return 0.5 * (-Math.pow(2, -10 * --t) + 2); - }, - - easeInCirc: function(t) { - if (t >= 1) { - return t; - } - return -(Math.sqrt(1 - t * t) - 1); - }, - - easeOutCirc: function(t) { - return Math.sqrt(1 - (t = t - 1) * t); - }, - - easeInOutCirc: function(t) { - if ((t /= 0.5) < 1) { - return -0.5 * (Math.sqrt(1 - t * t) - 1); - } - return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); - }, - - easeInElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if (!p) { - p = 0.3; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); - }, - - easeOutElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if (!p) { - p = 0.3; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; - }, - - easeInOutElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if ((t /= 0.5) === 2) { - return 1; - } - if (!p) { - p = 0.45; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - if (t < 1) { - return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); - } - return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; - }, - easeInBack: function(t) { - var s = 1.70158; - return t * t * ((s + 1) * t - s); - }, - - easeOutBack: function(t) { - var s = 1.70158; - return (t = t - 1) * t * ((s + 1) * t + s) + 1; - }, - - easeInOutBack: function(t) { - var s = 1.70158; - if ((t /= 0.5) < 1) { - return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); - } - return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); - }, - - easeInBounce: function(t) { - return 1 - effects.easeOutBounce(1 - t); - }, - - easeOutBounce: function(t) { - if (t < (1 / 2.75)) { - return 7.5625 * t * t; - } - if (t < (2 / 2.75)) { - return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; - } - if (t < (2.5 / 2.75)) { - return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; - } - return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; - }, - - easeInOutBounce: function(t) { - if (t < 0.5) { - return effects.easeInBounce(t * 2) * 0.5; - } - return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; - } -}; - -var helpers_easing = { - effects: effects -}; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.helpers.easing.effects instead. - * @function Chart.helpers.easingEffects - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers_core.easingEffects = effects; - -var PI = Math.PI; -var RAD_PER_DEG = PI / 180; -var DOUBLE_PI = PI * 2; -var HALF_PI = PI / 2; -var QUARTER_PI = PI / 4; -var TWO_THIRDS_PI = PI * 2 / 3; - -/** - * @namespace Chart.helpers.canvas - */ -var exports$1 = { - /** - * Clears the entire canvas associated to the given `chart`. - * @param {Chart} chart - The chart for which to clear the canvas. - */ - clear: function(chart) { - chart.ctx.clearRect(0, 0, chart.width, chart.height); - }, - - /** - * Creates a "path" for a rectangle with rounded corners at position (x, y) with a - * given size (width, height) and the same `radius` for all corners. - * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. - * @param {number} x - The x axis of the coordinate for the rectangle starting point. - * @param {number} y - The y axis of the coordinate for the rectangle starting point. - * @param {number} width - The rectangle's width. - * @param {number} height - The rectangle's height. - * @param {number} radius - The rounded amount (in pixels) for the four corners. - * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? - */ - roundedRect: function(ctx, x, y, width, height, radius) { - if (radius) { - var r = Math.min(radius, height / 2, width / 2); - var left = x + r; - var top = y + r; - var right = x + width - r; - var bottom = y + height - r; - - ctx.moveTo(x, top); - if (left < right && top < bottom) { - ctx.arc(left, top, r, -PI, -HALF_PI); - ctx.arc(right, top, r, -HALF_PI, 0); - ctx.arc(right, bottom, r, 0, HALF_PI); - ctx.arc(left, bottom, r, HALF_PI, PI); - } else if (left < right) { - ctx.moveTo(left, y); - ctx.arc(right, top, r, -HALF_PI, HALF_PI); - ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); - } else if (top < bottom) { - ctx.arc(left, top, r, -PI, 0); - ctx.arc(left, bottom, r, 0, PI); - } else { - ctx.arc(left, top, r, -PI, PI); - } - ctx.closePath(); - ctx.moveTo(x, y); - } else { - ctx.rect(x, y, width, height); - } - }, - - drawPoint: function(ctx, style, radius, x, y, rotation) { - var type, xOffset, yOffset, size, cornerRadius; - var rad = (rotation || 0) * RAD_PER_DEG; - - if (style && typeof style === 'object') { - type = style.toString(); - if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { - ctx.save(); - ctx.translate(x, y); - ctx.rotate(rad); - ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); - ctx.restore(); - return; - } - } - - if (isNaN(radius) || radius <= 0) { - return; - } - - ctx.beginPath(); - - switch (style) { - // Default includes circle - default: - ctx.arc(x, y, radius, 0, DOUBLE_PI); - ctx.closePath(); - break; - case 'triangle': - ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - rad += TWO_THIRDS_PI; - ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - rad += TWO_THIRDS_PI; - ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - ctx.closePath(); - break; - case 'rectRounded': - // NOTE: the rounded rect implementation changed to use `arc` instead of - // `quadraticCurveTo` since it generates better results when rect is - // almost a circle. 0.516 (instead of 0.5) produces results with visually - // closer proportion to the previous impl and it is inscribed in the - // circle with `radius`. For more details, see the following PRs: - // https://github.com/chartjs/Chart.js/issues/5597 - // https://github.com/chartjs/Chart.js/issues/5858 - cornerRadius = radius * 0.516; - size = radius - cornerRadius; - xOffset = Math.cos(rad + QUARTER_PI) * size; - yOffset = Math.sin(rad + QUARTER_PI) * size; - ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); - ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); - ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); - ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); - ctx.closePath(); - break; - case 'rect': - if (!rotation) { - size = Math.SQRT1_2 * radius; - ctx.rect(x - size, y - size, 2 * size, 2 * size); - break; - } - rad += QUARTER_PI; - /* falls through */ - case 'rectRot': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + yOffset, y - xOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.lineTo(x - yOffset, y + xOffset); - ctx.closePath(); - break; - case 'crossRot': - rad += QUARTER_PI; - /* falls through */ - case 'cross': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - break; - case 'star': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - rad += QUARTER_PI; - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - break; - case 'line': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - break; - case 'dash': - ctx.moveTo(x, y); - ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); - break; - } - - ctx.fill(); - ctx.stroke(); - }, - - /** - * Returns true if the point is inside the rectangle - * @param {object} point - The point to test - * @param {object} area - The rectangle - * @returns {boolean} - * @private - */ - _isPointInArea: function(point, area) { - var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. - - return point.x > area.left - epsilon && point.x < area.right + epsilon && - point.y > area.top - epsilon && point.y < area.bottom + epsilon; - }, - - clipArea: function(ctx, area) { - ctx.save(); - ctx.beginPath(); - ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); - ctx.clip(); - }, - - unclipArea: function(ctx) { - ctx.restore(); - }, - - lineTo: function(ctx, previous, target, flip) { - var stepped = target.steppedLine; - if (stepped) { - if (stepped === 'middle') { - var midpoint = (previous.x + target.x) / 2.0; - ctx.lineTo(midpoint, flip ? target.y : previous.y); - ctx.lineTo(midpoint, flip ? previous.y : target.y); - } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { - ctx.lineTo(previous.x, target.y); - } else { - ctx.lineTo(target.x, previous.y); - } - ctx.lineTo(target.x, target.y); - return; - } - - if (!target.tension) { - ctx.lineTo(target.x, target.y); - return; - } - - ctx.bezierCurveTo( - flip ? previous.controlPointPreviousX : previous.controlPointNextX, - flip ? previous.controlPointPreviousY : previous.controlPointNextY, - flip ? target.controlPointNextX : target.controlPointPreviousX, - flip ? target.controlPointNextY : target.controlPointPreviousY, - target.x, - target.y); - } -}; - -var helpers_canvas = exports$1; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. - * @namespace Chart.helpers.clear - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers_core.clear = exports$1.clear; - -/** - * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. - * @namespace Chart.helpers.drawRoundedRectangle - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers_core.drawRoundedRectangle = function(ctx) { - ctx.beginPath(); - exports$1.roundedRect.apply(exports$1, arguments); -}; - -var defaults = { - /** - * @private - */ - _set: function(scope, values) { - return helpers_core.merge(this[scope] || (this[scope] = {}), values); - } -}; - -// TODO(v3): remove 'global' from namespace. all default are global and -// there's inconsistency around which options are under 'global' -defaults._set('global', { - defaultColor: 'rgba(0,0,0,0.1)', - defaultFontColor: '#666', - defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - defaultFontSize: 12, - defaultFontStyle: 'normal', - defaultLineHeight: 1.2, - showLines: true -}); - -var core_defaults = defaults; - -var valueOrDefault = helpers_core.valueOrDefault; - -/** - * Converts the given font object into a CSS font string. - * @param {object} font - A font object. - * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font - * @private - */ -function toFontString(font) { - if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { - return null; - } - - return (font.style ? font.style + ' ' : '') - + (font.weight ? font.weight + ' ' : '') - + font.size + 'px ' - + font.family; -} - -/** - * @alias Chart.helpers.options - * @namespace - */ -var helpers_options = { - /** - * Converts the given line height `value` in pixels for a specific font `size`. - * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). - * @param {number} size - The font size (in pixels) used to resolve relative `value`. - * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height - * @since 2.7.0 - */ - toLineHeight: function(value, size) { - var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); - if (!matches || matches[1] === 'normal') { - return size * 1.2; - } - - value = +matches[2]; - - switch (matches[3]) { - case 'px': - return value; - case '%': - value /= 100; - break; - } - - return size * value; - }, - - /** - * Converts the given value into a padding object with pre-computed width/height. - * @param {number|object} value - If a number, set the value to all TRBL component, - * else, if and object, use defined properties and sets undefined ones to 0. - * @returns {object} The padding values (top, right, bottom, left, width, height) - * @since 2.7.0 - */ - toPadding: function(value) { - var t, r, b, l; - - if (helpers_core.isObject(value)) { - t = +value.top || 0; - r = +value.right || 0; - b = +value.bottom || 0; - l = +value.left || 0; - } else { - t = r = b = l = +value || 0; - } - - return { - top: t, - right: r, - bottom: b, - left: l, - height: t + b, - width: l + r - }; - }, - - /** - * Parses font options and returns the font object. - * @param {object} options - A object that contains font options to be parsed. - * @return {object} The font object. - * @todo Support font.* options and renamed to toFont(). - * @private - */ - _parseFont: function(options) { - var globalDefaults = core_defaults.global; - var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); - var font = { - family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), - lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), - size: size, - style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), - weight: null, - string: '' - }; - - font.string = toFontString(font); - return font; - }, - - /** - * Evaluates the given `inputs` sequentially and returns the first defined value. - * @param {Array} inputs - An array of values, falling back to the last value. - * @param {object} [context] - If defined and the current value is a function, the value - * is called with `context` as first argument and the result becomes the new input. - * @param {number} [index] - If defined and the current value is an array, the value - * at `index` become the new input. - * @param {object} [info] - object to return information about resolution in - * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. - * @since 2.7.0 - */ - resolve: function(inputs, context, index, info) { - var cacheable = true; - var i, ilen, value; - - for (i = 0, ilen = inputs.length; i < ilen; ++i) { - value = inputs[i]; - if (value === undefined) { - continue; - } - if (context !== undefined && typeof value === 'function') { - value = value(context); - cacheable = false; - } - if (index !== undefined && helpers_core.isArray(value)) { - value = value[index]; - cacheable = false; - } - if (value !== undefined) { - if (info && !cacheable) { - info.cacheable = false; - } - return value; - } - } - } -}; - -/** - * @alias Chart.helpers.math - * @namespace - */ -var exports$2 = { - /** - * Returns an array of factors sorted from 1 to sqrt(value) - * @private - */ - _factorize: function(value) { - var result = []; - var sqrt = Math.sqrt(value); - var i; - - for (i = 1; i < sqrt; i++) { - if (value % i === 0) { - result.push(i); - result.push(value / i); - } - } - if (sqrt === (sqrt | 0)) { // if value is a square number - result.push(sqrt); - } - - result.sort(function(a, b) { - return a - b; - }).pop(); - return result; - }, - - log10: Math.log10 || function(x) { - var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. - // Check for whole powers of 10, - // which due to floating point rounding error should be corrected. - var powerOf10 = Math.round(exponent); - var isPowerOf10 = x === Math.pow(10, powerOf10); - - return isPowerOf10 ? powerOf10 : exponent; - } -}; - -var helpers_math = exports$2; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.helpers.math.log10 instead. - * @namespace Chart.helpers.log10 - * @deprecated since version 2.9.0 - * @todo remove at version 3 - * @private - */ -helpers_core.log10 = exports$2.log10; - -var getRtlAdapter = function(rectX, width) { - return { - x: function(x) { - return rectX + rectX + width - x; - }, - setWidth: function(w) { - width = w; - }, - textAlign: function(align) { - if (align === 'center') { - return align; - } - return align === 'right' ? 'left' : 'right'; - }, - xPlus: function(x, value) { - return x - value; - }, - leftForLtr: function(x, itemWidth) { - return x - itemWidth; - }, - }; -}; - -var getLtrAdapter = function() { - return { - x: function(x) { - return x; - }, - setWidth: function(w) { // eslint-disable-line no-unused-vars - }, - textAlign: function(align) { - return align; - }, - xPlus: function(x, value) { - return x + value; - }, - leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars - return x; - }, - }; -}; - -var getAdapter = function(rtl, rectX, width) { - return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); -}; - -var overrideTextDirection = function(ctx, direction) { - var style, original; - if (direction === 'ltr' || direction === 'rtl') { - style = ctx.canvas.style; - original = [ - style.getPropertyValue('direction'), - style.getPropertyPriority('direction'), - ]; - - style.setProperty('direction', direction, 'important'); - ctx.prevTextDirection = original; - } -}; - -var restoreTextDirection = function(ctx) { - var original = ctx.prevTextDirection; - if (original !== undefined) { - delete ctx.prevTextDirection; - ctx.canvas.style.setProperty('direction', original[0], original[1]); - } -}; - -var helpers_rtl = { - getRtlAdapter: getAdapter, - overrideTextDirection: overrideTextDirection, - restoreTextDirection: restoreTextDirection, -}; - -var helpers$1 = helpers_core; -var easing = helpers_easing; -var canvas = helpers_canvas; -var options = helpers_options; -var math = helpers_math; -var rtl = helpers_rtl; -helpers$1.easing = easing; -helpers$1.canvas = canvas; -helpers$1.options = options; -helpers$1.math = math; -helpers$1.rtl = rtl; - -function interpolate(start, view, model, ease) { - var keys = Object.keys(model); - var i, ilen, key, actual, origin, target, type, c0, c1; - - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - - target = model[key]; - - // if a value is added to the model after pivot() has been called, the view - // doesn't contain it, so let's initialize the view to the target value. - if (!view.hasOwnProperty(key)) { - view[key] = target; - } - - actual = view[key]; - - if (actual === target || key[0] === '_') { - continue; - } - - if (!start.hasOwnProperty(key)) { - start[key] = actual; - } - - origin = start[key]; - - type = typeof target; - - if (type === typeof origin) { - if (type === 'string') { - c0 = chartjsColor(origin); - if (c0.valid) { - c1 = chartjsColor(target); - if (c1.valid) { - view[key] = c1.mix(c0, ease).rgbString(); - continue; - } - } - } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { - view[key] = origin + (target - origin) * ease; - continue; - } - } - - view[key] = target; - } -} - -var Element = function(configuration) { - helpers$1.extend(this, configuration); - this.initialize.apply(this, arguments); -}; - -helpers$1.extend(Element.prototype, { - _type: undefined, - - initialize: function() { - this.hidden = false; - }, - - pivot: function() { - var me = this; - if (!me._view) { - me._view = helpers$1.extend({}, me._model); - } - me._start = {}; - return me; - }, - - transition: function(ease) { - var me = this; - var model = me._model; - var start = me._start; - var view = me._view; - - // No animation -> No Transition - if (!model || ease === 1) { - me._view = helpers$1.extend({}, model); - me._start = null; - return me; - } - - if (!view) { - view = me._view = {}; - } - - if (!start) { - start = me._start = {}; - } - - interpolate(start, view, model, ease); - - return me; - }, - - tooltipPosition: function() { - return { - x: this._model.x, - y: this._model.y - }; - }, - - hasValue: function() { - return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); - } -}); - -Element.extend = helpers$1.inherits; - -var core_element = Element; - -var exports$3 = core_element.extend({ - chart: null, // the animation associated chart instance - currentStep: 0, // the current animation step - numSteps: 60, // default number of steps - easing: '', // the easing to use for this animation - render: null, // render function used by the animation service - - onAnimationProgress: null, // user specified callback to fire on each step of the animation - onAnimationComplete: null, // user specified callback to fire when the animation finishes -}); - -var core_animation = exports$3; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.Animation instead - * @prop Chart.Animation#animationObject - * @deprecated since version 2.6.0 - * @todo remove at version 3 - */ -Object.defineProperty(exports$3.prototype, 'animationObject', { - get: function() { - return this; - } -}); - -/** - * Provided for backward compatibility, use Chart.Animation#chart instead - * @prop Chart.Animation#chartInstance - * @deprecated since version 2.6.0 - * @todo remove at version 3 - */ -Object.defineProperty(exports$3.prototype, 'chartInstance', { - get: function() { - return this.chart; - }, - set: function(value) { - this.chart = value; - } -}); - -core_defaults._set('global', { - animation: { - duration: 1000, - easing: 'easeOutQuart', - onProgress: helpers$1.noop, - onComplete: helpers$1.noop - } -}); - -var core_animations = { - animations: [], - request: null, - - /** - * @param {Chart} chart - The chart to animate. - * @param {Chart.Animation} animation - The animation that we will animate. - * @param {number} duration - The animation duration in ms. - * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions - */ - addAnimation: function(chart, animation, duration, lazy) { - var animations = this.animations; - var i, ilen; - - animation.chart = chart; - animation.startTime = Date.now(); - animation.duration = duration; - - if (!lazy) { - chart.animating = true; - } - - for (i = 0, ilen = animations.length; i < ilen; ++i) { - if (animations[i].chart === chart) { - animations[i] = animation; - return; - } - } - - animations.push(animation); - - // If there are no animations queued, manually kickstart a digest, for lack of a better word - if (animations.length === 1) { - this.requestAnimationFrame(); - } - }, - - cancelAnimation: function(chart) { - var index = helpers$1.findIndex(this.animations, function(animation) { - return animation.chart === chart; - }); - - if (index !== -1) { - this.animations.splice(index, 1); - chart.animating = false; - } - }, - - requestAnimationFrame: function() { - var me = this; - if (me.request === null) { - // Skip animation frame requests until the active one is executed. - // This can happen when processing mouse events, e.g. 'mousemove' - // and 'mouseout' events will trigger multiple renders. - me.request = helpers$1.requestAnimFrame.call(window, function() { - me.request = null; - me.startDigest(); - }); - } - }, - - /** - * @private - */ - startDigest: function() { - var me = this; - - me.advance(); - - // Do we have more stuff to animate? - if (me.animations.length > 0) { - me.requestAnimationFrame(); - } - }, - - /** - * @private - */ - advance: function() { - var animations = this.animations; - var animation, chart, numSteps, nextStep; - var i = 0; - - // 1 animation per chart, so we are looping charts here - while (i < animations.length) { - animation = animations[i]; - chart = animation.chart; - numSteps = animation.numSteps; - - // Make sure that currentStep starts at 1 - // https://github.com/chartjs/Chart.js/issues/6104 - nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; - animation.currentStep = Math.min(nextStep, numSteps); - - helpers$1.callback(animation.render, [chart, animation], chart); - helpers$1.callback(animation.onAnimationProgress, [animation], chart); - - if (animation.currentStep >= numSteps) { - helpers$1.callback(animation.onAnimationComplete, [animation], chart); - chart.animating = false; - animations.splice(i, 1); - } else { - ++i; - } - } - } -}; - -var resolve = helpers$1.options.resolve; - -var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; - -/** - * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', - * 'unshift') and notify the listener AFTER the array has been altered. Listeners are - * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. - */ -function listenArrayEvents(array, listener) { - if (array._chartjs) { - array._chartjs.listeners.push(listener); - return; - } - - Object.defineProperty(array, '_chartjs', { - configurable: true, - enumerable: false, - value: { - listeners: [listener] - } - }); - - arrayEvents.forEach(function(key) { - var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); - var base = array[key]; - - Object.defineProperty(array, key, { - configurable: true, - enumerable: false, - value: function() { - var args = Array.prototype.slice.call(arguments); - var res = base.apply(this, args); - - helpers$1.each(array._chartjs.listeners, function(object) { - if (typeof object[method] === 'function') { - object[method].apply(object, args); - } - }); - - return res; - } - }); - }); -} - -/** - * Removes the given array event listener and cleanup extra attached properties (such as - * the _chartjs stub and overridden methods) if array doesn't have any more listeners. - */ -function unlistenArrayEvents(array, listener) { - var stub = array._chartjs; - if (!stub) { - return; - } - - var listeners = stub.listeners; - var index = listeners.indexOf(listener); - if (index !== -1) { - listeners.splice(index, 1); - } - - if (listeners.length > 0) { - return; - } - - arrayEvents.forEach(function(key) { - delete array[key]; - }); - - delete array._chartjs; -} - -// Base class for all dataset controllers (line, bar, etc) -var DatasetController = function(chart, datasetIndex) { - this.initialize(chart, datasetIndex); -}; - -helpers$1.extend(DatasetController.prototype, { - - /** - * Element type used to generate a meta dataset (e.g. Chart.element.Line). - * @type {Chart.core.element} - */ - datasetElementType: null, - - /** - * Element type used to generate a meta data (e.g. Chart.element.Point). - * @type {Chart.core.element} - */ - dataElementType: null, - - /** - * Dataset element option keys to be resolved in _resolveDatasetElementOptions. - * A derived controller may override this to resolve controller-specific options. - * The keys defined here are for backward compatibility for legend styles. - * @private - */ - _datasetElementOptions: [ - 'backgroundColor', - 'borderCapStyle', - 'borderColor', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'borderWidth' - ], - - /** - * Data element option keys to be resolved in _resolveDataElementOptions. - * A derived controller may override this to resolve controller-specific options. - * The keys defined here are for backward compatibility for legend styles. - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'pointStyle' - ], - - initialize: function(chart, datasetIndex) { - var me = this; - me.chart = chart; - me.index = datasetIndex; - me.linkScales(); - me.addElements(); - me._type = me.getMeta().type; - }, - - updateIndex: function(datasetIndex) { - this.index = datasetIndex; - }, - - linkScales: function() { - var me = this; - var meta = me.getMeta(); - var chart = me.chart; - var scales = chart.scales; - var dataset = me.getDataset(); - var scalesOpts = chart.options.scales; - - if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) { - meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id; - } - if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) { - meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id; - } - }, - - getDataset: function() { - return this.chart.data.datasets[this.index]; - }, - - getMeta: function() { - return this.chart.getDatasetMeta(this.index); - }, - - getScaleForId: function(scaleID) { - return this.chart.scales[scaleID]; - }, - - /** - * @private - */ - _getValueScaleId: function() { - return this.getMeta().yAxisID; - }, - - /** - * @private - */ - _getIndexScaleId: function() { - return this.getMeta().xAxisID; - }, - - /** - * @private - */ - _getValueScale: function() { - return this.getScaleForId(this._getValueScaleId()); - }, - - /** - * @private - */ - _getIndexScale: function() { - return this.getScaleForId(this._getIndexScaleId()); - }, - - reset: function() { - this._update(true); - }, - - /** - * @private - */ - destroy: function() { - if (this._data) { - unlistenArrayEvents(this._data, this); - } - }, - - createMetaDataset: function() { - var me = this; - var type = me.datasetElementType; - return type && new type({ - _chart: me.chart, - _datasetIndex: me.index - }); - }, - - createMetaData: function(index) { - var me = this; - var type = me.dataElementType; - return type && new type({ - _chart: me.chart, - _datasetIndex: me.index, - _index: index - }); - }, - - addElements: function() { - var me = this; - var meta = me.getMeta(); - var data = me.getDataset().data || []; - var metaData = meta.data; - var i, ilen; - - for (i = 0, ilen = data.length; i < ilen; ++i) { - metaData[i] = metaData[i] || me.createMetaData(i); - } - - meta.dataset = meta.dataset || me.createMetaDataset(); - }, - - addElementAndReset: function(index) { - var element = this.createMetaData(index); - this.getMeta().data.splice(index, 0, element); - this.updateElement(element, index, true); - }, - - buildOrUpdateElements: function() { - var me = this; - var dataset = me.getDataset(); - var data = dataset.data || (dataset.data = []); - - // In order to correctly handle data addition/deletion animation (an thus simulate - // real-time charts), we need to monitor these data modifications and synchronize - // the internal meta data accordingly. - if (me._data !== data) { - if (me._data) { - // This case happens when the user replaced the data array instance. - unlistenArrayEvents(me._data, me); - } - - if (data && Object.isExtensible(data)) { - listenArrayEvents(data, me); - } - me._data = data; - } - - // Re-sync meta data in case the user replaced the data array or if we missed - // any updates and so make sure that we handle number of datapoints changing. - me.resyncElements(); - }, - - /** - * Returns the merged user-supplied and default dataset-level options - * @private - */ - _configure: function() { - var me = this; - me._config = helpers$1.merge({}, [ - me.chart.options.datasets[me._type], - me.getDataset(), - ], { - merger: function(key, target, source) { - if (key !== '_meta' && key !== 'data') { - helpers$1._merger(key, target, source); - } - } - }); - }, - - _update: function(reset) { - var me = this; - me._configure(); - me._cachedDataOpts = null; - me.update(reset); - }, - - update: helpers$1.noop, - - transition: function(easingValue) { - var meta = this.getMeta(); - var elements = meta.data || []; - var ilen = elements.length; - var i = 0; - - for (; i < ilen; ++i) { - elements[i].transition(easingValue); - } - - if (meta.dataset) { - meta.dataset.transition(easingValue); - } - }, - - draw: function() { - var meta = this.getMeta(); - var elements = meta.data || []; - var ilen = elements.length; - var i = 0; - - if (meta.dataset) { - meta.dataset.draw(); - } - - for (; i < ilen; ++i) { - elements[i].draw(); - } - }, - - /** - * Returns a set of predefined style properties that should be used to represent the dataset - * or the data if the index is specified - * @param {number} index - data index - * @return {IStyleInterface} style object - */ - getStyle: function(index) { - var me = this; - var meta = me.getMeta(); - var dataset = meta.dataset; - var style; - - me._configure(); - if (dataset && index === undefined) { - style = me._resolveDatasetElementOptions(dataset || {}); - } else { - index = index || 0; - style = me._resolveDataElementOptions(meta.data[index] || {}, index); - } - - if (style.fill === false || style.fill === null) { - style.backgroundColor = style.borderColor; - } - - return style; - }, - - /** - * @private - */ - _resolveDatasetElementOptions: function(element, hover) { - var me = this; - var chart = me.chart; - var datasetOpts = me._config; - var custom = element.custom || {}; - var options = chart.options.elements[me.datasetElementType.prototype._type] || {}; - var elementOptions = me._datasetElementOptions; - var values = {}; - var i, ilen, key, readKey; - - // Scriptable options - var context = { - chart: chart, - dataset: me.getDataset(), - datasetIndex: me.index, - hover: hover - }; - - for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { - key = elementOptions[i]; - readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key; - values[key] = resolve([ - custom[readKey], - datasetOpts[readKey], - options[readKey] - ], context); - } - - return values; - }, - - /** - * @private - */ - _resolveDataElementOptions: function(element, index) { - var me = this; - var custom = element && element.custom; - var cached = me._cachedDataOpts; - if (cached && !custom) { - return cached; - } - var chart = me.chart; - var datasetOpts = me._config; - var options = chart.options.elements[me.dataElementType.prototype._type] || {}; - var elementOptions = me._dataElementOptions; - var values = {}; - - // Scriptable options - var context = { - chart: chart, - dataIndex: index, - dataset: me.getDataset(), - datasetIndex: me.index - }; - - // `resolve` sets cacheable to `false` if any option is indexed or scripted - var info = {cacheable: !custom}; - - var keys, i, ilen, key; - - custom = custom || {}; - - if (helpers$1.isArray(elementOptions)) { - for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { - key = elementOptions[i]; - values[key] = resolve([ - custom[key], - datasetOpts[key], - options[key] - ], context, index, info); - } - } else { - keys = Object.keys(elementOptions); - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - values[key] = resolve([ - custom[key], - datasetOpts[elementOptions[key]], - datasetOpts[key], - options[key] - ], context, index, info); - } - } - - if (info.cacheable) { - me._cachedDataOpts = Object.freeze(values); - } - - return values; - }, - - removeHoverStyle: function(element) { - helpers$1.merge(element._model, element.$previousStyle || {}); - delete element.$previousStyle; - }, - - setHoverStyle: function(element) { - var dataset = this.chart.data.datasets[element._datasetIndex]; - var index = element._index; - var custom = element.custom || {}; - var model = element._model; - var getHoverColor = helpers$1.getHoverColor; - - element.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth - }; - - model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); - model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); - model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); - }, - - /** - * @private - */ - _removeDatasetHoverStyle: function() { - var element = this.getMeta().dataset; - - if (element) { - this.removeHoverStyle(element); - } - }, - - /** - * @private - */ - _setDatasetHoverStyle: function() { - var element = this.getMeta().dataset; - var prev = {}; - var i, ilen, key, keys, hoverOptions, model; - - if (!element) { - return; - } - - model = element._model; - hoverOptions = this._resolveDatasetElementOptions(element, true); - - keys = Object.keys(hoverOptions); - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - prev[key] = model[key]; - model[key] = hoverOptions[key]; - } - - element.$previousStyle = prev; - }, - - /** - * @private - */ - resyncElements: function() { - var me = this; - var meta = me.getMeta(); - var data = me.getDataset().data; - var numMeta = meta.data.length; - var numData = data.length; - - if (numData < numMeta) { - meta.data.splice(numData, numMeta - numData); - } else if (numData > numMeta) { - me.insertElements(numMeta, numData - numMeta); - } - }, - - /** - * @private - */ - insertElements: function(start, count) { - for (var i = 0; i < count; ++i) { - this.addElementAndReset(start + i); - } - }, - - /** - * @private - */ - onDataPush: function() { - var count = arguments.length; - this.insertElements(this.getDataset().data.length - count, count); - }, - - /** - * @private - */ - onDataPop: function() { - this.getMeta().data.pop(); - }, - - /** - * @private - */ - onDataShift: function() { - this.getMeta().data.shift(); - }, - - /** - * @private - */ - onDataSplice: function(start, count) { - this.getMeta().data.splice(start, count); - this.insertElements(start, arguments.length - 2); - }, - - /** - * @private - */ - onDataUnshift: function() { - this.insertElements(0, arguments.length); - } -}); - -DatasetController.extend = helpers$1.inherits; - -var core_datasetController = DatasetController; - -var TAU = Math.PI * 2; - -core_defaults._set('global', { - elements: { - arc: { - backgroundColor: core_defaults.global.defaultColor, - borderColor: '#fff', - borderWidth: 2, - borderAlign: 'center' - } - } -}); - -function clipArc(ctx, arc) { - var startAngle = arc.startAngle; - var endAngle = arc.endAngle; - var pixelMargin = arc.pixelMargin; - var angleMargin = pixelMargin / arc.outerRadius; - var x = arc.x; - var y = arc.y; - - // Draw an inner border by cliping the arc and drawing a double-width border - // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders - ctx.beginPath(); - ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin); - if (arc.innerRadius > pixelMargin) { - angleMargin = pixelMargin / arc.innerRadius; - ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true); - } else { - ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2); - } - ctx.closePath(); - ctx.clip(); -} - -function drawFullCircleBorders(ctx, vm, arc, inner) { - var endAngle = arc.endAngle; - var i; - - if (inner) { - arc.endAngle = arc.startAngle + TAU; - clipArc(ctx, arc); - arc.endAngle = endAngle; - if (arc.endAngle === arc.startAngle && arc.fullCircles) { - arc.endAngle += TAU; - arc.fullCircles--; - } - } - - ctx.beginPath(); - ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true); - for (i = 0; i < arc.fullCircles; ++i) { - ctx.stroke(); - } - - ctx.beginPath(); - ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU); - for (i = 0; i < arc.fullCircles; ++i) { - ctx.stroke(); - } -} - -function drawBorder(ctx, vm, arc) { - var inner = vm.borderAlign === 'inner'; - - if (inner) { - ctx.lineWidth = vm.borderWidth * 2; - ctx.lineJoin = 'round'; - } else { - ctx.lineWidth = vm.borderWidth; - ctx.lineJoin = 'bevel'; - } - - if (arc.fullCircles) { - drawFullCircleBorders(ctx, vm, arc, inner); - } - - if (inner) { - clipArc(ctx, arc); - } - - ctx.beginPath(); - ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle); - ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); - ctx.closePath(); - ctx.stroke(); -} - -var element_arc = core_element.extend({ - _type: 'arc', - - inLabelRange: function(mouseX) { - var vm = this._view; - - if (vm) { - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); - } - return false; - }, - - inRange: function(chartX, chartY) { - var vm = this._view; - - if (vm) { - var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); - var angle = pointRelativePosition.angle; - var distance = pointRelativePosition.distance; - - // Sanitise angle range - var startAngle = vm.startAngle; - var endAngle = vm.endAngle; - while (endAngle < startAngle) { - endAngle += TAU; - } - while (angle > endAngle) { - angle -= TAU; - } - while (angle < startAngle) { - angle += TAU; - } - - // Check if within the range of the open/close angle - var betweenAngles = (angle >= startAngle && angle <= endAngle); - var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); - - return (betweenAngles && withinRadius); - } - return false; - }, - - getCenterPoint: function() { - var vm = this._view; - var halfAngle = (vm.startAngle + vm.endAngle) / 2; - var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; - return { - x: vm.x + Math.cos(halfAngle) * halfRadius, - y: vm.y + Math.sin(halfAngle) * halfRadius - }; - }, - - getArea: function() { - var vm = this._view; - return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); - }, - - tooltipPosition: function() { - var vm = this._view; - var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); - var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; - - return { - x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), - y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) - }; - }, - - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; - var arc = { - x: vm.x, - y: vm.y, - innerRadius: vm.innerRadius, - outerRadius: Math.max(vm.outerRadius - pixelMargin, 0), - pixelMargin: pixelMargin, - startAngle: vm.startAngle, - endAngle: vm.endAngle, - fullCircles: Math.floor(vm.circumference / TAU) - }; - var i; - - ctx.save(); - - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - - if (arc.fullCircles) { - arc.endAngle = arc.startAngle + TAU; - ctx.beginPath(); - ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); - ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); - ctx.closePath(); - for (i = 0; i < arc.fullCircles; ++i) { - ctx.fill(); - } - arc.endAngle = arc.startAngle + vm.circumference % TAU; - } - - ctx.beginPath(); - ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); - ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); - ctx.closePath(); - ctx.fill(); - - if (vm.borderWidth) { - drawBorder(ctx, vm, arc); - } - - ctx.restore(); - } -}); - -var valueOrDefault$1 = helpers$1.valueOrDefault; - -var defaultColor = core_defaults.global.defaultColor; - -core_defaults._set('global', { - elements: { - line: { - tension: 0.4, - backgroundColor: defaultColor, - borderWidth: 3, - borderColor: defaultColor, - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0.0, - borderJoinStyle: 'miter', - capBezierPoints: true, - fill: true, // do we fill in the area between the line and its base axis - } - } -}); - -var element_line = core_element.extend({ - _type: 'line', - - draw: function() { - var me = this; - var vm = me._view; - var ctx = me._chart.ctx; - var spanGaps = vm.spanGaps; - var points = me._children.slice(); // clone array - var globalDefaults = core_defaults.global; - var globalOptionLineElements = globalDefaults.elements.line; - var lastDrawnIndex = -1; - var closePath = me._loop; - var index, previous, currentVM; - - if (!points.length) { - return; - } - - if (me._loop) { - for (index = 0; index < points.length; ++index) { - previous = helpers$1.previousItem(points, index); - // If the line has an open path, shift the point array - if (!points[index]._view.skip && previous._view.skip) { - points = points.slice(index).concat(points.slice(0, index)); - closePath = spanGaps; - break; - } - } - // If the line has a close path, add the first point again - if (closePath) { - points.push(points[0]); - } - } - - ctx.save(); - - // Stroke Line Options - ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; - - // IE 9 and 10 do not support line dash - if (ctx.setLineDash) { - ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); - } - - ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); - ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; - ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); - ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; - - // Stroke Line - ctx.beginPath(); - - // First point moves to it's starting position no matter what - currentVM = points[0]._view; - if (!currentVM.skip) { - ctx.moveTo(currentVM.x, currentVM.y); - lastDrawnIndex = 0; - } - - for (index = 1; index < points.length; ++index) { - currentVM = points[index]._view; - previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex]; - - if (!currentVM.skip) { - if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { - // There was a gap and this is the first point after the gap - ctx.moveTo(currentVM.x, currentVM.y); - } else { - // Line to next point - helpers$1.canvas.lineTo(ctx, previous._view, currentVM); - } - lastDrawnIndex = index; - } - } - - if (closePath) { - ctx.closePath(); - } - - ctx.stroke(); - ctx.restore(); - } -}); - -var valueOrDefault$2 = helpers$1.valueOrDefault; - -var defaultColor$1 = core_defaults.global.defaultColor; - -core_defaults._set('global', { - elements: { - point: { - radius: 3, - pointStyle: 'circle', - backgroundColor: defaultColor$1, - borderColor: defaultColor$1, - borderWidth: 1, - // Hover - hitRadius: 1, - hoverRadius: 4, - hoverBorderWidth: 1 - } - } -}); - -function xRange(mouseX) { - var vm = this._view; - return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; -} - -function yRange(mouseY) { - var vm = this._view; - return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; -} - -var element_point = core_element.extend({ - _type: 'point', - - inRange: function(mouseX, mouseY) { - var vm = this._view; - return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; - }, - - inLabelRange: xRange, - inXRange: xRange, - inYRange: yRange, - - getCenterPoint: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y - }; - }, - - getArea: function() { - return Math.PI * Math.pow(this._view.radius, 2); - }, - - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y, - padding: vm.radius + vm.borderWidth - }; - }, - - draw: function(chartArea) { - var vm = this._view; - var ctx = this._chart.ctx; - var pointStyle = vm.pointStyle; - var rotation = vm.rotation; - var radius = vm.radius; - var x = vm.x; - var y = vm.y; - var globalDefaults = core_defaults.global; - var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow - - if (vm.skip) { - return; - } - - // Clipping for Points. - if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { - ctx.strokeStyle = vm.borderColor || defaultColor; - ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); - ctx.fillStyle = vm.backgroundColor || defaultColor; - helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); - } - } -}); - -var defaultColor$2 = core_defaults.global.defaultColor; - -core_defaults._set('global', { - elements: { - rectangle: { - backgroundColor: defaultColor$2, - borderColor: defaultColor$2, - borderSkipped: 'bottom', - borderWidth: 0 - } - } -}); - -function isVertical(vm) { - return vm && vm.width !== undefined; -} - -/** - * Helper function to get the bounds of the bar regardless of the orientation - * @param bar {Chart.Element.Rectangle} the bar - * @return {Bounds} bounds of the bar - * @private - */ -function getBarBounds(vm) { - var x1, x2, y1, y2, half; - - if (isVertical(vm)) { - half = vm.width / 2; - x1 = vm.x - half; - x2 = vm.x + half; - y1 = Math.min(vm.y, vm.base); - y2 = Math.max(vm.y, vm.base); - } else { - half = vm.height / 2; - x1 = Math.min(vm.x, vm.base); - x2 = Math.max(vm.x, vm.base); - y1 = vm.y - half; - y2 = vm.y + half; - } - - return { - left: x1, - top: y1, - right: x2, - bottom: y2 - }; -} - -function swap(orig, v1, v2) { - return orig === v1 ? v2 : orig === v2 ? v1 : orig; -} - -function parseBorderSkipped(vm) { - var edge = vm.borderSkipped; - var res = {}; - - if (!edge) { - return res; - } - - if (vm.horizontal) { - if (vm.base > vm.x) { - edge = swap(edge, 'left', 'right'); - } - } else if (vm.base < vm.y) { - edge = swap(edge, 'bottom', 'top'); - } - - res[edge] = true; - return res; -} - -function parseBorderWidth(vm, maxW, maxH) { - var value = vm.borderWidth; - var skip = parseBorderSkipped(vm); - var t, r, b, l; - - if (helpers$1.isObject(value)) { - t = +value.top || 0; - r = +value.right || 0; - b = +value.bottom || 0; - l = +value.left || 0; - } else { - t = r = b = l = +value || 0; - } - - return { - t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, - r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, - b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, - l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l - }; -} - -function boundingRects(vm) { - var bounds = getBarBounds(vm); - var width = bounds.right - bounds.left; - var height = bounds.bottom - bounds.top; - var border = parseBorderWidth(vm, width / 2, height / 2); - - return { - outer: { - x: bounds.left, - y: bounds.top, - w: width, - h: height - }, - inner: { - x: bounds.left + border.l, - y: bounds.top + border.t, - w: width - border.l - border.r, - h: height - border.t - border.b - } - }; -} - -function inRange(vm, x, y) { - var skipX = x === null; - var skipY = y === null; - var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); - - return bounds - && (skipX || x >= bounds.left && x <= bounds.right) - && (skipY || y >= bounds.top && y <= bounds.bottom); -} - -var element_rectangle = core_element.extend({ - _type: 'rectangle', - - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - var rects = boundingRects(vm); - var outer = rects.outer; - var inner = rects.inner; - - ctx.fillStyle = vm.backgroundColor; - ctx.fillRect(outer.x, outer.y, outer.w, outer.h); - - if (outer.w === inner.w && outer.h === inner.h) { - return; - } - - ctx.save(); - ctx.beginPath(); - ctx.rect(outer.x, outer.y, outer.w, outer.h); - ctx.clip(); - ctx.fillStyle = vm.borderColor; - ctx.rect(inner.x, inner.y, inner.w, inner.h); - ctx.fill('evenodd'); - ctx.restore(); - }, - - height: function() { - var vm = this._view; - return vm.base - vm.y; - }, - - inRange: function(mouseX, mouseY) { - return inRange(this._view, mouseX, mouseY); - }, - - inLabelRange: function(mouseX, mouseY) { - var vm = this._view; - return isVertical(vm) - ? inRange(vm, mouseX, null) - : inRange(vm, null, mouseY); - }, - - inXRange: function(mouseX) { - return inRange(this._view, mouseX, null); - }, - - inYRange: function(mouseY) { - return inRange(this._view, null, mouseY); - }, - - getCenterPoint: function() { - var vm = this._view; - var x, y; - if (isVertical(vm)) { - x = vm.x; - y = (vm.y + vm.base) / 2; - } else { - x = (vm.x + vm.base) / 2; - y = vm.y; - } - - return {x: x, y: y}; - }, - - getArea: function() { - var vm = this._view; - - return isVertical(vm) - ? vm.width * Math.abs(vm.y - vm.base) - : vm.height * Math.abs(vm.x - vm.base); - }, - - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y - }; - } -}); - -var elements = {}; -var Arc = element_arc; -var Line = element_line; -var Point = element_point; -var Rectangle = element_rectangle; -elements.Arc = Arc; -elements.Line = Line; -elements.Point = Point; -elements.Rectangle = Rectangle; - -var deprecated = helpers$1._deprecated; -var valueOrDefault$3 = helpers$1.valueOrDefault; - -core_defaults._set('bar', { - hover: { - mode: 'label' - }, - - scales: { - xAxes: [{ - type: 'category', - offset: true, - gridLines: { - offsetGridLines: true - } - }], - - yAxes: [{ - type: 'linear' - }] - } -}); - -core_defaults._set('global', { - datasets: { - bar: { - categoryPercentage: 0.8, - barPercentage: 0.9 - } - } -}); - -/** - * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. - * @private - */ -function computeMinSampleSize(scale, pixels) { - var min = scale._length; - var prev, curr, i, ilen; - - for (i = 1, ilen = pixels.length; i < ilen; ++i) { - min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); - } - - for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { - curr = scale.getPixelForTick(i); - min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; - prev = curr; - } - - return min; -} - -/** - * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, - * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This - * mode currently always generates bars equally sized (until we introduce scriptable options?). - * @private - */ -function computeFitCategoryTraits(index, ruler, options) { - var thickness = options.barThickness; - var count = ruler.stackCount; - var curr = ruler.pixels[index]; - var min = helpers$1.isNullOrUndef(thickness) - ? computeMinSampleSize(ruler.scale, ruler.pixels) - : -1; - var size, ratio; - - if (helpers$1.isNullOrUndef(thickness)) { - size = min * options.categoryPercentage; - ratio = options.barPercentage; - } else { - // When bar thickness is enforced, category and bar percentages are ignored. - // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') - // and deprecate barPercentage since this value is ignored when thickness is absolute. - size = thickness * count; - ratio = 1; - } - - return { - chunk: size / count, - ratio: ratio, - start: curr - (size / 2) - }; -} - -/** - * Computes an "optimal" category that globally arranges bars side by side (no gap when - * percentage options are 1), based on the previous and following categories. This mode - * generates bars with different widths when data are not evenly spaced. - * @private - */ -function computeFlexCategoryTraits(index, ruler, options) { - var pixels = ruler.pixels; - var curr = pixels[index]; - var prev = index > 0 ? pixels[index - 1] : null; - var next = index < pixels.length - 1 ? pixels[index + 1] : null; - var percent = options.categoryPercentage; - var start, size; - - if (prev === null) { - // first data: its size is double based on the next point or, - // if it's also the last data, we use the scale size. - prev = curr - (next === null ? ruler.end - ruler.start : next - curr); - } - - if (next === null) { - // last data: its size is also double based on the previous point. - next = curr + curr - prev; - } - - start = curr - (curr - Math.min(prev, next)) / 2 * percent; - size = Math.abs(next - prev) / 2 * percent; - - return { - chunk: size / ruler.stackCount, - ratio: options.barPercentage, - start: start - }; -} - -var controller_bar = core_datasetController.extend({ - - dataElementType: elements.Rectangle, - - /** - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderSkipped', - 'borderWidth', - 'barPercentage', - 'barThickness', - 'categoryPercentage', - 'maxBarThickness', - 'minBarLength' - ], - - initialize: function() { - var me = this; - var meta, scaleOpts; - - core_datasetController.prototype.initialize.apply(me, arguments); - - meta = me.getMeta(); - meta.stack = me.getDataset().stack; - meta.bar = true; - - scaleOpts = me._getIndexScale().options; - deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage'); - deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness'); - deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage'); - deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength'); - deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness'); - }, - - update: function(reset) { - var me = this; - var rects = me.getMeta().data; - var i, ilen; - - me._ruler = me.getRuler(); - - for (i = 0, ilen = rects.length; i < ilen; ++i) { - me.updateElement(rects[i], i, reset); - } - }, - - updateElement: function(rectangle, index, reset) { - var me = this; - var meta = me.getMeta(); - var dataset = me.getDataset(); - var options = me._resolveDataElementOptions(rectangle, index); - - rectangle._xScale = me.getScaleForId(meta.xAxisID); - rectangle._yScale = me.getScaleForId(meta.yAxisID); - rectangle._datasetIndex = me.index; - rectangle._index = index; - rectangle._model = { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderSkipped: options.borderSkipped, - borderWidth: options.borderWidth, - datasetLabel: dataset.label, - label: me.chart.data.labels[index] - }; - - if (helpers$1.isArray(dataset.data[index])) { - rectangle._model.borderSkipped = null; - } - - me._updateElementGeometry(rectangle, index, reset, options); - - rectangle.pivot(); - }, - - /** - * @private - */ - _updateElementGeometry: function(rectangle, index, reset, options) { - var me = this; - var model = rectangle._model; - var vscale = me._getValueScale(); - var base = vscale.getBasePixel(); - var horizontal = vscale.isHorizontal(); - var ruler = me._ruler || me.getRuler(); - var vpixels = me.calculateBarValuePixels(me.index, index, options); - var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options); - - model.horizontal = horizontal; - model.base = reset ? base : vpixels.base; - model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; - model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; - model.height = horizontal ? ipixels.size : undefined; - model.width = horizontal ? undefined : ipixels.size; - }, - - /** - * Returns the stacks based on groups and bar visibility. - * @param {number} [last] - The dataset index - * @returns {string[]} The list of stack IDs - * @private - */ - _getStacks: function(last) { - var me = this; - var scale = me._getIndexScale(); - var metasets = scale._getMatchingVisibleMetas(me._type); - var stacked = scale.options.stacked; - var ilen = metasets.length; - var stacks = []; - var i, meta; - - for (i = 0; i < ilen; ++i) { - meta = metasets[i]; - // stacked | meta.stack - // | found | not found | undefined - // false | x | x | x - // true | | x | - // undefined | | x | x - if (stacked === false || stacks.indexOf(meta.stack) === -1 || - (stacked === undefined && meta.stack === undefined)) { - stacks.push(meta.stack); - } - if (meta.index === last) { - break; - } - } - - return stacks; - }, - - /** - * Returns the effective number of stacks based on groups and bar visibility. - * @private - */ - getStackCount: function() { - return this._getStacks().length; - }, - - /** - * Returns the stack index for the given dataset based on groups and bar visibility. - * @param {number} [datasetIndex] - The dataset index - * @param {string} [name] - The stack name to find - * @returns {number} The stack index - * @private - */ - getStackIndex: function(datasetIndex, name) { - var stacks = this._getStacks(datasetIndex); - var index = (name !== undefined) - ? stacks.indexOf(name) - : -1; // indexOf returns -1 if element is not present - - return (index === -1) - ? stacks.length - 1 - : index; - }, - - /** - * @private - */ - getRuler: function() { - var me = this; - var scale = me._getIndexScale(); - var pixels = []; - var i, ilen; - - for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { - pixels.push(scale.getPixelForValue(null, i, me.index)); - } - - return { - pixels: pixels, - start: scale._startPixel, - end: scale._endPixel, - stackCount: me.getStackCount(), - scale: scale - }; - }, - - /** - * Note: pixel values are not clamped to the scale area. - * @private - */ - calculateBarValuePixels: function(datasetIndex, index, options) { - var me = this; - var chart = me.chart; - var scale = me._getValueScale(); - var isHorizontal = scale.isHorizontal(); - var datasets = chart.data.datasets; - var metasets = scale._getMatchingVisibleMetas(me._type); - var value = scale._parseValue(datasets[datasetIndex].data[index]); - var minBarLength = options.minBarLength; - var stacked = scale.options.stacked; - var stack = me.getMeta().stack; - var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; - var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; - var ilen = metasets.length; - var i, imeta, ivalue, base, head, size, stackLength; - - if (stacked || (stacked === undefined && stack !== undefined)) { - for (i = 0; i < ilen; ++i) { - imeta = metasets[i]; - - if (imeta.index === datasetIndex) { - break; - } - - if (imeta.stack === stack) { - stackLength = scale._parseValue(datasets[imeta.index].data[index]); - ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; - - if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { - start += ivalue; - } - } - } - } - - base = scale.getPixelForValue(start); - head = scale.getPixelForValue(start + length); - size = head - base; - - if (minBarLength !== undefined && Math.abs(size) < minBarLength) { - size = minBarLength; - if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) { - head = base - minBarLength; - } else { - head = base + minBarLength; - } - } - - return { - size: size, - base: base, - head: head, - center: head + size / 2 - }; - }, - - /** - * @private - */ - calculateBarIndexPixels: function(datasetIndex, index, ruler, options) { - var me = this; - var range = options.barThickness === 'flex' - ? computeFlexCategoryTraits(index, ruler, options) - : computeFitCategoryTraits(index, ruler, options); - - var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); - var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); - var size = Math.min( - valueOrDefault$3(options.maxBarThickness, Infinity), - range.chunk * range.ratio); - - return { - base: center - size / 2, - head: center + size / 2, - center: center, - size: size - }; - }, - - draw: function() { - var me = this; - var chart = me.chart; - var scale = me._getValueScale(); - var rects = me.getMeta().data; - var dataset = me.getDataset(); - var ilen = rects.length; - var i = 0; - - helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); - - for (; i < ilen; ++i) { - var val = scale._parseValue(dataset.data[i]); - if (!isNaN(val.min) && !isNaN(val.max)) { - rects[i].draw(); - } - } - - helpers$1.canvas.unclipArea(chart.ctx); - }, - - /** - * @private - */ - _resolveDataElementOptions: function() { - var me = this; - var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments)); - var indexOpts = me._getIndexScale().options; - var valueOpts = me._getValueScale().options; - - values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage); - values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness); - values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage); - values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness); - values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength); - - return values; - } - -}); - -var valueOrDefault$4 = helpers$1.valueOrDefault; -var resolve$1 = helpers$1.options.resolve; - -core_defaults._set('bubble', { - hover: { - mode: 'single' - }, - - scales: { - xAxes: [{ - type: 'linear', // bubble should probably use a linear scale by default - position: 'bottom', - id: 'x-axis-0' // need an ID so datasets can reference the scale - }], - yAxes: [{ - type: 'linear', - position: 'left', - id: 'y-axis-0' - }] - }, - - tooltips: { - callbacks: { - title: function() { - // Title doesn't make sense for scatter since we format the data as a point - return ''; - }, - label: function(item, data) { - var datasetLabel = data.datasets[item.datasetIndex].label || ''; - var dataPoint = data.datasets[item.datasetIndex].data[item.index]; - return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; - } - } - } -}); - -var controller_bubble = core_datasetController.extend({ - /** - * @protected - */ - dataElementType: elements.Point, - - /** - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'hoverBackgroundColor', - 'hoverBorderColor', - 'hoverBorderWidth', - 'hoverRadius', - 'hitRadius', - 'pointStyle', - 'rotation' - ], - - /** - * @protected - */ - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var points = meta.data; - - // Update Points - helpers$1.each(points, function(point, index) { - me.updateElement(point, index, reset); - }); - }, - - /** - * @protected - */ - updateElement: function(point, index, reset) { - var me = this; - var meta = me.getMeta(); - var custom = point.custom || {}; - var xScale = me.getScaleForId(meta.xAxisID); - var yScale = me.getScaleForId(meta.yAxisID); - var options = me._resolveDataElementOptions(point, index); - var data = me.getDataset().data[index]; - var dsIndex = me.index; - - var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); - var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); - - point._xScale = xScale; - point._yScale = yScale; - point._options = options; - point._datasetIndex = dsIndex; - point._index = index; - point._model = { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - hitRadius: options.hitRadius, - pointStyle: options.pointStyle, - rotation: options.rotation, - radius: reset ? 0 : options.radius, - skip: custom.skip || isNaN(x) || isNaN(y), - x: x, - y: y, - }; - - point.pivot(); - }, - - /** - * @protected - */ - setHoverStyle: function(point) { - var model = point._model; - var options = point._options; - var getHoverColor = helpers$1.getHoverColor; - - point.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; - - model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); - model.radius = options.radius + options.hoverRadius; - }, - - /** - * @private - */ - _resolveDataElementOptions: function(point, index) { - var me = this; - var chart = me.chart; - var dataset = me.getDataset(); - var custom = point.custom || {}; - var data = dataset.data[index] || {}; - var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); - - // Scriptable options - var context = { - chart: chart, - dataIndex: index, - dataset: dataset, - datasetIndex: me.index - }; - - // In case values were cached (and thus frozen), we need to clone the values - if (me._cachedDataOpts === values) { - values = helpers$1.extend({}, values); - } - - // Custom radius resolution - values.radius = resolve$1([ - custom.radius, - data.r, - me._config.radius, - chart.options.elements.point.radius - ], context, index); - - return values; - } -}); - -var valueOrDefault$5 = helpers$1.valueOrDefault; - -var PI$1 = Math.PI; -var DOUBLE_PI$1 = PI$1 * 2; -var HALF_PI$1 = PI$1 / 2; - -core_defaults._set('doughnut', { - animation: { - // Boolean - Whether we animate the rotation of the Doughnut - animateRotate: true, - // Boolean - Whether we animate scaling the Doughnut from the centre - animateScale: false - }, - hover: { - mode: 'single' - }, - legendCallback: function(chart) { - var list = document.createElement('ul'); - var data = chart.data; - var datasets = data.datasets; - var labels = data.labels; - var i, ilen, listItem, listItemSpan; - - list.setAttribute('class', chart.id + '-legend'); - if (datasets.length) { - for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { - listItem = list.appendChild(document.createElement('li')); - listItemSpan = listItem.appendChild(document.createElement('span')); - listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; - if (labels[i]) { - listItem.appendChild(document.createTextNode(labels[i])); - } - } - } - - return list.outerHTML; - }, - legend: { - labels: { - generateLabels: function(chart) { - var data = chart.data; - if (data.labels.length && data.datasets.length) { - return data.labels.map(function(label, i) { - var meta = chart.getDatasetMeta(0); - var style = meta.controller.getStyle(i); - - return { - text: label, - fillStyle: style.backgroundColor, - strokeStyle: style.borderColor, - lineWidth: style.borderWidth, - hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, - - // Extra data used for toggling the correct item - index: i - }; - }); - } - return []; - } - }, - - onClick: function(e, legendItem) { - var index = legendItem.index; - var chart = this.chart; - var i, ilen, meta; - - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - // toggle visibility of index if exists - if (meta.data[index]) { - meta.data[index].hidden = !meta.data[index].hidden; - } - } - - chart.update(); - } - }, - - // The percentage of the chart that we cut out of the middle. - cutoutPercentage: 50, - - // The rotation of the chart, where the first data arc begins. - rotation: -HALF_PI$1, - - // The total circumference of the chart. - circumference: DOUBLE_PI$1, - - // Need to override these to give a nice default - tooltips: { - callbacks: { - title: function() { - return ''; - }, - label: function(tooltipItem, data) { - var dataLabel = data.labels[tooltipItem.index]; - var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; - - if (helpers$1.isArray(dataLabel)) { - // show value on first line of multiline label - // need to clone because we are changing the value - dataLabel = dataLabel.slice(); - dataLabel[0] += value; - } else { - dataLabel += value; - } - - return dataLabel; - } - } - } -}); - -var controller_doughnut = core_datasetController.extend({ - - dataElementType: elements.Arc, - - linkScales: helpers$1.noop, - - /** - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'borderAlign', - 'hoverBackgroundColor', - 'hoverBorderColor', - 'hoverBorderWidth', - ], - - // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly - getRingIndex: function(datasetIndex) { - var ringIndex = 0; - - for (var j = 0; j < datasetIndex; ++j) { - if (this.chart.isDatasetVisible(j)) { - ++ringIndex; - } - } - - return ringIndex; - }, - - update: function(reset) { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var ratioX = 1; - var ratioY = 1; - var offsetX = 0; - var offsetY = 0; - var meta = me.getMeta(); - var arcs = meta.data; - var cutout = opts.cutoutPercentage / 100 || 0; - var circumference = opts.circumference; - var chartWeight = me._getRingWeight(me.index); - var maxWidth, maxHeight, i, ilen; - - // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc - if (circumference < DOUBLE_PI$1) { - var startAngle = opts.rotation % DOUBLE_PI$1; - startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0; - var endAngle = startAngle + circumference; - var startX = Math.cos(startAngle); - var startY = Math.sin(startAngle); - var endX = Math.cos(endAngle); - var endY = Math.sin(endAngle); - var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1; - var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1; - var contains180 = startAngle === -PI$1 || endAngle >= PI$1; - var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1; - var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); - var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); - var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); - var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); - ratioX = (maxX - minX) / 2; - ratioY = (maxY - minY) / 2; - offsetX = -(maxX + minX) / 2; - offsetY = -(maxY + minY) / 2; - } - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); - } - - chart.borderWidth = me.getMaxBorderWidth(); - maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; - maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; - chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); - chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); - chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); - chart.offsetX = offsetX * chart.outerRadius; - chart.offsetY = offsetY * chart.outerRadius; - - meta.total = me.calculateTotal(); - - me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); - me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - me.updateElement(arcs[i], i, reset); - } - }, - - updateElement: function(arc, index, reset) { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var animationOpts = opts.animation; - var centerX = (chartArea.left + chartArea.right) / 2; - var centerY = (chartArea.top + chartArea.bottom) / 2; - var startAngle = opts.rotation; // non reset case handled later - var endAngle = opts.rotation; // non reset case handled later - var dataset = me.getDataset(); - var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1); - var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; - var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; - var options = arc._options || {}; - - helpers$1.extend(arc, { - // Utility - _datasetIndex: me.index, - _index: index, - - // Desired view properties - _model: { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - borderAlign: options.borderAlign, - x: centerX + chart.offsetX, - y: centerY + chart.offsetY, - startAngle: startAngle, - endAngle: endAngle, - circumference: circumference, - outerRadius: outerRadius, - innerRadius: innerRadius, - label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) - } - }); - - var model = arc._model; - - // Set correct angles if not resetting - if (!reset || !animationOpts.animateRotate) { - if (index === 0) { - model.startAngle = opts.rotation; - } else { - model.startAngle = me.getMeta().data[index - 1]._model.endAngle; - } - - model.endAngle = model.startAngle + model.circumference; - } - - arc.pivot(); - }, - - calculateTotal: function() { - var dataset = this.getDataset(); - var meta = this.getMeta(); - var total = 0; - var value; - - helpers$1.each(meta.data, function(element, index) { - value = dataset.data[index]; - if (!isNaN(value) && !element.hidden) { - total += Math.abs(value); - } - }); - - /* if (total === 0) { - total = NaN; - }*/ - - return total; - }, - - calculateCircumference: function(value) { - var total = this.getMeta().total; - if (total > 0 && !isNaN(value)) { - return DOUBLE_PI$1 * (Math.abs(value) / total); - } - return 0; - }, - - // gets the max border or hover width to properly scale pie charts - getMaxBorderWidth: function(arcs) { - var me = this; - var max = 0; - var chart = me.chart; - var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; - - if (!arcs) { - // Find the outmost visible dataset - for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { - if (chart.isDatasetVisible(i)) { - meta = chart.getDatasetMeta(i); - arcs = meta.data; - if (i !== me.index) { - controller = meta.controller; - } - break; - } - } - } - - if (!arcs) { - return 0; - } - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - arc = arcs[i]; - if (controller) { - controller._configure(); - options = controller._resolveDataElementOptions(arc, i); - } else { - options = arc._options; - } - if (options.borderAlign !== 'inner') { - borderWidth = options.borderWidth; - hoverWidth = options.hoverBorderWidth; - - max = borderWidth > max ? borderWidth : max; - max = hoverWidth > max ? hoverWidth : max; - } - } - return max; - }, - - /** - * @protected - */ - setHoverStyle: function(arc) { - var model = arc._model; - var options = arc._options; - var getHoverColor = helpers$1.getHoverColor; - - arc.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - }; - - model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); - }, - - /** - * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly - * @private - */ - _getRingWeightOffset: function(datasetIndex) { - var ringWeightOffset = 0; - - for (var i = 0; i < datasetIndex; ++i) { - if (this.chart.isDatasetVisible(i)) { - ringWeightOffset += this._getRingWeight(i); - } - } - - return ringWeightOffset; - }, - - /** - * @private - */ - _getRingWeight: function(dataSetIndex) { - return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0); - }, - - /** - * Returns the sum of all visibile data set weights. This value can be 0. - * @private - */ - _getVisibleDatasetWeightTotal: function() { - return this._getRingWeightOffset(this.chart.data.datasets.length); - } -}); - -core_defaults._set('horizontalBar', { - hover: { - mode: 'index', - axis: 'y' - }, - - scales: { - xAxes: [{ - type: 'linear', - position: 'bottom' - }], - - yAxes: [{ - type: 'category', - position: 'left', - offset: true, - gridLines: { - offsetGridLines: true - } - }] - }, - - elements: { - rectangle: { - borderSkipped: 'left' - } - }, - - tooltips: { - mode: 'index', - axis: 'y' - } -}); - -core_defaults._set('global', { - datasets: { - horizontalBar: { - categoryPercentage: 0.8, - barPercentage: 0.9 - } - } -}); - -var controller_horizontalBar = controller_bar.extend({ - /** - * @private - */ - _getValueScaleId: function() { - return this.getMeta().xAxisID; - }, - - /** - * @private - */ - _getIndexScaleId: function() { - return this.getMeta().yAxisID; - } -}); - -var valueOrDefault$6 = helpers$1.valueOrDefault; -var resolve$2 = helpers$1.options.resolve; -var isPointInArea = helpers$1.canvas._isPointInArea; - -core_defaults._set('line', { - showLines: true, - spanGaps: false, - - hover: { - mode: 'label' - }, - - scales: { - xAxes: [{ - type: 'category', - id: 'x-axis-0' - }], - yAxes: [{ - type: 'linear', - id: 'y-axis-0' - }] - } -}); - -function scaleClip(scale, halfBorderWidth) { - var tickOpts = scale && scale.options.ticks || {}; - var reverse = tickOpts.reverse; - var min = tickOpts.min === undefined ? halfBorderWidth : 0; - var max = tickOpts.max === undefined ? halfBorderWidth : 0; - return { - start: reverse ? max : min, - end: reverse ? min : max - }; -} - -function defaultClip(xScale, yScale, borderWidth) { - var halfBorderWidth = borderWidth / 2; - var x = scaleClip(xScale, halfBorderWidth); - var y = scaleClip(yScale, halfBorderWidth); - - return { - top: y.end, - right: x.end, - bottom: y.start, - left: x.start - }; -} - -function toClip(value) { - var t, r, b, l; - - if (helpers$1.isObject(value)) { - t = value.top; - r = value.right; - b = value.bottom; - l = value.left; - } else { - t = r = b = l = value; - } - - return { - top: t, - right: r, - bottom: b, - left: l - }; -} - - -var controller_line = core_datasetController.extend({ - - datasetElementType: elements.Line, - - dataElementType: elements.Point, - - /** - * @private - */ - _datasetElementOptions: [ - 'backgroundColor', - 'borderCapStyle', - 'borderColor', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'borderWidth', - 'cubicInterpolationMode', - 'fill' - ], - - /** - * @private - */ - _dataElementOptions: { - backgroundColor: 'pointBackgroundColor', - borderColor: 'pointBorderColor', - borderWidth: 'pointBorderWidth', - hitRadius: 'pointHitRadius', - hoverBackgroundColor: 'pointHoverBackgroundColor', - hoverBorderColor: 'pointHoverBorderColor', - hoverBorderWidth: 'pointHoverBorderWidth', - hoverRadius: 'pointHoverRadius', - pointStyle: 'pointStyle', - radius: 'pointRadius', - rotation: 'pointRotation' - }, - - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var line = meta.dataset; - var points = meta.data || []; - var options = me.chart.options; - var config = me._config; - var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines); - var i, ilen; - - me._xScale = me.getScaleForId(meta.xAxisID); - me._yScale = me.getScaleForId(meta.yAxisID); - - // Update Line - if (showLine) { - // Compatibility: If the properties are defined with only the old name, use those values - if (config.tension !== undefined && config.lineTension === undefined) { - config.lineTension = config.tension; - } - - // Utility - line._scale = me._yScale; - line._datasetIndex = me.index; - // Data - line._children = points; - // Model - line._model = me._resolveDatasetElementOptions(line); - - line.pivot(); - } - - // Update Points - for (i = 0, ilen = points.length; i < ilen; ++i) { - me.updateElement(points[i], i, reset); - } - - if (showLine && line._model.tension !== 0) { - me.updateBezierControlPoints(); - } - - // Now pivot the point for animation - for (i = 0, ilen = points.length; i < ilen; ++i) { - points[i].pivot(); - } - }, - - updateElement: function(point, index, reset) { - var me = this; - var meta = me.getMeta(); - var custom = point.custom || {}; - var dataset = me.getDataset(); - var datasetIndex = me.index; - var value = dataset.data[index]; - var xScale = me._xScale; - var yScale = me._yScale; - var lineModel = meta.dataset._model; - var x, y; - - var options = me._resolveDataElementOptions(point, index); - - x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); - y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); - - // Utility - point._xScale = xScale; - point._yScale = yScale; - point._options = options; - point._datasetIndex = datasetIndex; - point._index = index; - - // Desired view properties - point._model = { - x: x, - y: y, - skip: custom.skip || isNaN(x) || isNaN(y), - // Appearance - radius: options.radius, - pointStyle: options.pointStyle, - rotation: options.rotation, - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), - steppedLine: lineModel ? lineModel.steppedLine : false, - // Tooltip - hitRadius: options.hitRadius - }; - }, - - /** - * @private - */ - _resolveDatasetElementOptions: function(element) { - var me = this; - var config = me._config; - var custom = element.custom || {}; - var options = me.chart.options; - var lineOptions = options.elements.line; - var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); - - // The default behavior of lines is to break at null values, according - // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 - // This option gives lines the ability to span gaps - values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps); - values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension); - values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]); - values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth))); - - return values; - }, - - calculatePointY: function(value, index, datasetIndex) { - var me = this; - var chart = me.chart; - var yScale = me._yScale; - var sumPos = 0; - var sumNeg = 0; - var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen; - - if (yScale.options.stacked) { - rightValue = +yScale.getRightValue(value); - metasets = chart._getSortedVisibleDatasetMetas(); - ilen = metasets.length; - - for (i = 0; i < ilen; ++i) { - dsMeta = metasets[i]; - if (dsMeta.index === datasetIndex) { - break; - } - - ds = chart.data.datasets[dsMeta.index]; - if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { - stackedRightValue = +yScale.getRightValue(ds.data[index]); - if (stackedRightValue < 0) { - sumNeg += stackedRightValue || 0; - } else { - sumPos += stackedRightValue || 0; - } - } - } - - if (rightValue < 0) { - return yScale.getPixelForValue(sumNeg + rightValue); - } - return yScale.getPixelForValue(sumPos + rightValue); - } - return yScale.getPixelForValue(value); - }, - - updateBezierControlPoints: function() { - var me = this; - var chart = me.chart; - var meta = me.getMeta(); - var lineModel = meta.dataset._model; - var area = chart.chartArea; - var points = meta.data || []; - var i, ilen, model, controlPoints; - - // Only consider points that are drawn in case the spanGaps option is used - if (lineModel.spanGaps) { - points = points.filter(function(pt) { - return !pt._model.skip; - }); - } - - function capControlPoint(pt, min, max) { - return Math.max(Math.min(pt, max), min); - } - - if (lineModel.cubicInterpolationMode === 'monotone') { - helpers$1.splineCurveMonotone(points); - } else { - for (i = 0, ilen = points.length; i < ilen; ++i) { - model = points[i]._model; - controlPoints = helpers$1.splineCurve( - helpers$1.previousItem(points, i)._model, - model, - helpers$1.nextItem(points, i)._model, - lineModel.tension - ); - model.controlPointPreviousX = controlPoints.previous.x; - model.controlPointPreviousY = controlPoints.previous.y; - model.controlPointNextX = controlPoints.next.x; - model.controlPointNextY = controlPoints.next.y; - } - } - - if (chart.options.elements.line.capBezierPoints) { - for (i = 0, ilen = points.length; i < ilen; ++i) { - model = points[i]._model; - if (isPointInArea(model, area)) { - if (i > 0 && isPointInArea(points[i - 1]._model, area)) { - model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); - model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); - } - if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { - model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); - model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); - } - } - } - } - }, - - draw: function() { - var me = this; - var chart = me.chart; - var meta = me.getMeta(); - var points = meta.data || []; - var area = chart.chartArea; - var canvas = chart.canvas; - var i = 0; - var ilen = points.length; - var clip; - - if (me._showLine) { - clip = meta.dataset._model.clip; - - helpers$1.canvas.clipArea(chart.ctx, { - left: clip.left === false ? 0 : area.left - clip.left, - right: clip.right === false ? canvas.width : area.right + clip.right, - top: clip.top === false ? 0 : area.top - clip.top, - bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom - }); - - meta.dataset.draw(); - - helpers$1.canvas.unclipArea(chart.ctx); - } - - // Draw the points - for (; i < ilen; ++i) { - points[i].draw(area); - } - }, - - /** - * @protected - */ - setHoverStyle: function(point) { - var model = point._model; - var options = point._options; - var getHoverColor = helpers$1.getHoverColor; - - point.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; - - model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); - model.radius = valueOrDefault$6(options.hoverRadius, options.radius); - }, -}); - -var resolve$3 = helpers$1.options.resolve; - -core_defaults._set('polarArea', { - scale: { - type: 'radialLinear', - angleLines: { - display: false - }, - gridLines: { - circular: true - }, - pointLabels: { - display: false - }, - ticks: { - beginAtZero: true - } - }, - - // Boolean - Whether to animate the rotation of the chart - animation: { - animateRotate: true, - animateScale: true - }, - - startAngle: -0.5 * Math.PI, - legendCallback: function(chart) { - var list = document.createElement('ul'); - var data = chart.data; - var datasets = data.datasets; - var labels = data.labels; - var i, ilen, listItem, listItemSpan; - - list.setAttribute('class', chart.id + '-legend'); - if (datasets.length) { - for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { - listItem = list.appendChild(document.createElement('li')); - listItemSpan = listItem.appendChild(document.createElement('span')); - listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; - if (labels[i]) { - listItem.appendChild(document.createTextNode(labels[i])); - } - } - } - - return list.outerHTML; - }, - legend: { - labels: { - generateLabels: function(chart) { - var data = chart.data; - if (data.labels.length && data.datasets.length) { - return data.labels.map(function(label, i) { - var meta = chart.getDatasetMeta(0); - var style = meta.controller.getStyle(i); - - return { - text: label, - fillStyle: style.backgroundColor, - strokeStyle: style.borderColor, - lineWidth: style.borderWidth, - hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, - - // Extra data used for toggling the correct item - index: i - }; - }); - } - return []; - } - }, - - onClick: function(e, legendItem) { - var index = legendItem.index; - var chart = this.chart; - var i, ilen, meta; - - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - meta.data[index].hidden = !meta.data[index].hidden; - } - - chart.update(); - } - }, - - // Need to override these to give a nice default - tooltips: { - callbacks: { - title: function() { - return ''; - }, - label: function(item, data) { - return data.labels[item.index] + ': ' + item.yLabel; - } - } - } -}); - -var controller_polarArea = core_datasetController.extend({ - - dataElementType: elements.Arc, - - linkScales: helpers$1.noop, - - /** - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'borderAlign', - 'hoverBackgroundColor', - 'hoverBorderColor', - 'hoverBorderWidth', - ], - - /** - * @private - */ - _getIndexScaleId: function() { - return this.chart.scale.id; - }, - - /** - * @private - */ - _getValueScaleId: function() { - return this.chart.scale.id; - }, - - update: function(reset) { - var me = this; - var dataset = me.getDataset(); - var meta = me.getMeta(); - var start = me.chart.options.startAngle || 0; - var starts = me._starts = []; - var angles = me._angles = []; - var arcs = meta.data; - var i, ilen, angle; - - me._updateRadius(); - - meta.count = me.countVisibleElements(); - - for (i = 0, ilen = dataset.data.length; i < ilen; i++) { - starts[i] = start; - angle = me._computeAngle(i); - angles[i] = angle; - start += angle; - } - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); - me.updateElement(arcs[i], i, reset); - } - }, - - /** - * @private - */ - _updateRadius: function() { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); - - chart.outerRadius = Math.max(minSize / 2, 0); - chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); - chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); - - me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); - me.innerRadius = me.outerRadius - chart.radiusLength; - }, - - updateElement: function(arc, index, reset) { - var me = this; - var chart = me.chart; - var dataset = me.getDataset(); - var opts = chart.options; - var animationOpts = opts.animation; - var scale = chart.scale; - var labels = chart.data.labels; - - var centerX = scale.xCenter; - var centerY = scale.yCenter; - - // var negHalfPI = -0.5 * Math.PI; - var datasetStartAngle = opts.startAngle; - var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); - var startAngle = me._starts[index]; - var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); - - var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); - var options = arc._options || {}; - - helpers$1.extend(arc, { - // Utility - _datasetIndex: me.index, - _index: index, - _scale: scale, - - // Desired view properties - _model: { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - borderAlign: options.borderAlign, - x: centerX, - y: centerY, - innerRadius: 0, - outerRadius: reset ? resetRadius : distance, - startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, - endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, - label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) - } - }); - - arc.pivot(); - }, - - countVisibleElements: function() { - var dataset = this.getDataset(); - var meta = this.getMeta(); - var count = 0; - - helpers$1.each(meta.data, function(element, index) { - if (!isNaN(dataset.data[index]) && !element.hidden) { - count++; - } - }); - - return count; - }, - - /** - * @protected - */ - setHoverStyle: function(arc) { - var model = arc._model; - var options = arc._options; - var getHoverColor = helpers$1.getHoverColor; - var valueOrDefault = helpers$1.valueOrDefault; - - arc.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - }; - - model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); - }, - - /** - * @private - */ - _computeAngle: function(index) { - var me = this; - var count = this.getMeta().count; - var dataset = me.getDataset(); - var meta = me.getMeta(); - - if (isNaN(dataset.data[index]) || meta.data[index].hidden) { - return 0; - } - - // Scriptable options - var context = { - chart: me.chart, - dataIndex: index, - dataset: dataset, - datasetIndex: me.index - }; - - return resolve$3([ - me.chart.options.elements.arc.angle, - (2 * Math.PI) / count - ], context, index); - } -}); - -core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); -core_defaults._set('pie', { - cutoutPercentage: 0 -}); - -// Pie charts are Doughnut chart with different defaults -var controller_pie = controller_doughnut; - -var valueOrDefault$7 = helpers$1.valueOrDefault; - -core_defaults._set('radar', { - spanGaps: false, - scale: { - type: 'radialLinear' - }, - elements: { - line: { - fill: 'start', - tension: 0 // no bezier in radar - } - } -}); - -var controller_radar = core_datasetController.extend({ - datasetElementType: elements.Line, - - dataElementType: elements.Point, - - linkScales: helpers$1.noop, - - /** - * @private - */ - _datasetElementOptions: [ - 'backgroundColor', - 'borderWidth', - 'borderColor', - 'borderCapStyle', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'fill' - ], - - /** - * @private - */ - _dataElementOptions: { - backgroundColor: 'pointBackgroundColor', - borderColor: 'pointBorderColor', - borderWidth: 'pointBorderWidth', - hitRadius: 'pointHitRadius', - hoverBackgroundColor: 'pointHoverBackgroundColor', - hoverBorderColor: 'pointHoverBorderColor', - hoverBorderWidth: 'pointHoverBorderWidth', - hoverRadius: 'pointHoverRadius', - pointStyle: 'pointStyle', - radius: 'pointRadius', - rotation: 'pointRotation' - }, - - /** - * @private - */ - _getIndexScaleId: function() { - return this.chart.scale.id; - }, - - /** - * @private - */ - _getValueScaleId: function() { - return this.chart.scale.id; - }, - - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var line = meta.dataset; - var points = meta.data || []; - var scale = me.chart.scale; - var config = me._config; - var i, ilen; - - // Compatibility: If the properties are defined with only the old name, use those values - if (config.tension !== undefined && config.lineTension === undefined) { - config.lineTension = config.tension; - } - - // Utility - line._scale = scale; - line._datasetIndex = me.index; - // Data - line._children = points; - line._loop = true; - // Model - line._model = me._resolveDatasetElementOptions(line); - - line.pivot(); - - // Update Points - for (i = 0, ilen = points.length; i < ilen; ++i) { - me.updateElement(points[i], i, reset); - } - - // Update bezier control points - me.updateBezierControlPoints(); - - // Now pivot the point for animation - for (i = 0, ilen = points.length; i < ilen; ++i) { - points[i].pivot(); - } - }, - - updateElement: function(point, index, reset) { - var me = this; - var custom = point.custom || {}; - var dataset = me.getDataset(); - var scale = me.chart.scale; - var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); - var options = me._resolveDataElementOptions(point, index); - var lineModel = me.getMeta().dataset._model; - var x = reset ? scale.xCenter : pointPosition.x; - var y = reset ? scale.yCenter : pointPosition.y; - - // Utility - point._scale = scale; - point._options = options; - point._datasetIndex = me.index; - point._index = index; - - // Desired view properties - point._model = { - x: x, // value not used in dataset scale, but we want a consistent API between scales - y: y, - skip: custom.skip || isNaN(x) || isNaN(y), - // Appearance - radius: options.radius, - pointStyle: options.pointStyle, - rotation: options.rotation, - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0), - - // Tooltip - hitRadius: options.hitRadius - }; - }, - - /** - * @private - */ - _resolveDatasetElementOptions: function() { - var me = this; - var config = me._config; - var options = me.chart.options; - var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); - - values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps); - values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension); - - return values; - }, - - updateBezierControlPoints: function() { - var me = this; - var meta = me.getMeta(); - var area = me.chart.chartArea; - var points = meta.data || []; - var i, ilen, model, controlPoints; - - // Only consider points that are drawn in case the spanGaps option is used - if (meta.dataset._model.spanGaps) { - points = points.filter(function(pt) { - return !pt._model.skip; - }); - } - - function capControlPoint(pt, min, max) { - return Math.max(Math.min(pt, max), min); - } - - for (i = 0, ilen = points.length; i < ilen; ++i) { - model = points[i]._model; - controlPoints = helpers$1.splineCurve( - helpers$1.previousItem(points, i, true)._model, - model, - helpers$1.nextItem(points, i, true)._model, - model.tension - ); - - // Prevent the bezier going outside of the bounds of the graph - model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); - model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); - model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); - model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); - } - }, - - setHoverStyle: function(point) { - var model = point._model; - var options = point._options; - var getHoverColor = helpers$1.getHoverColor; - - point.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; - - model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth); - model.radius = valueOrDefault$7(options.hoverRadius, options.radius); - } -}); - -core_defaults._set('scatter', { - hover: { - mode: 'single' - }, - - scales: { - xAxes: [{ - id: 'x-axis-1', // need an ID so datasets can reference the scale - type: 'linear', // scatter should not use a category axis - position: 'bottom' - }], - yAxes: [{ - id: 'y-axis-1', - type: 'linear', - position: 'left' - }] - }, - - tooltips: { - callbacks: { - title: function() { - return ''; // doesn't make sense for scatter since data are formatted as a point - }, - label: function(item) { - return '(' + item.xLabel + ', ' + item.yLabel + ')'; - } - } - } -}); - -core_defaults._set('global', { - datasets: { - scatter: { - showLine: false - } - } -}); - -// Scatter charts use line controllers -var controller_scatter = controller_line; - -// NOTE export a map in which the key represents the controller type, not -// the class, and so must be CamelCase in order to be correctly retrieved -// by the controller in core.controller.js (`controllers[meta.type]`). - -var controllers = { - bar: controller_bar, - bubble: controller_bubble, - doughnut: controller_doughnut, - horizontalBar: controller_horizontalBar, - line: controller_line, - polarArea: controller_polarArea, - pie: controller_pie, - radar: controller_radar, - scatter: controller_scatter -}; - -/** - * Helper function to get relative position for an event - * @param {Event|IEvent} event - The event to get the position for - * @param {Chart} chart - The chart - * @returns {object} the event position - */ -function getRelativePosition(e, chart) { - if (e.native) { - return { - x: e.x, - y: e.y - }; - } - - return helpers$1.getRelativePosition(e, chart); -} - -/** - * Helper function to traverse all of the visible elements in the chart - * @param {Chart} chart - the chart - * @param {function} handler - the callback to execute for each visible item - */ -function parseVisibleItems(chart, handler) { - var metasets = chart._getSortedVisibleDatasetMetas(); - var metadata, i, j, ilen, jlen, element; - - for (i = 0, ilen = metasets.length; i < ilen; ++i) { - metadata = metasets[i].data; - for (j = 0, jlen = metadata.length; j < jlen; ++j) { - element = metadata[j]; - if (!element._view.skip) { - handler(element); - } - } - } -} - -/** - * Helper function to get the items that intersect the event position - * @param {ChartElement[]} items - elements to filter - * @param {object} position - the point to be nearest to - * @return {ChartElement[]} the nearest items - */ -function getIntersectItems(chart, position) { - var elements = []; - - parseVisibleItems(chart, function(element) { - if (element.inRange(position.x, position.y)) { - elements.push(element); - } - }); - - return elements; -} - -/** - * Helper function to get the items nearest to the event position considering all visible items in teh chart - * @param {Chart} chart - the chart to look at elements from - * @param {object} position - the point to be nearest to - * @param {boolean} intersect - if true, only consider items that intersect the position - * @param {function} distanceMetric - function to provide the distance between points - * @return {ChartElement[]} the nearest items - */ -function getNearestItems(chart, position, intersect, distanceMetric) { - var minDistance = Number.POSITIVE_INFINITY; - var nearestItems = []; - - parseVisibleItems(chart, function(element) { - if (intersect && !element.inRange(position.x, position.y)) { - return; - } - - var center = element.getCenterPoint(); - var distance = distanceMetric(position, center); - if (distance < minDistance) { - nearestItems = [element]; - minDistance = distance; - } else if (distance === minDistance) { - // Can have multiple items at the same distance in which case we sort by size - nearestItems.push(element); - } - }); - - return nearestItems; -} - -/** - * Get a distance metric function for two points based on the - * axis mode setting - * @param {string} axis - the axis mode. x|y|xy - */ -function getDistanceMetricForAxis(axis) { - var useX = axis.indexOf('x') !== -1; - var useY = axis.indexOf('y') !== -1; - - return function(pt1, pt2) { - var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; - var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; - return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); - }; -} - -function indexMode(chart, e, options) { - var position = getRelativePosition(e, chart); - // Default axis for index mode is 'x' to match old behaviour - options.axis = options.axis || 'x'; - var distanceMetric = getDistanceMetricForAxis(options.axis); - var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); - var elements = []; - - if (!items.length) { - return []; - } - - chart._getSortedVisibleDatasetMetas().forEach(function(meta) { - var element = meta.data[items[0]._index]; - - // don't count items that are skipped (null data) - if (element && !element._view.skip) { - elements.push(element); - } - }); - - return elements; -} - -/** - * @interface IInteractionOptions - */ -/** - * If true, only consider items that intersect the point - * @name IInterfaceOptions#boolean - * @type Boolean - */ - -/** - * Contains interaction related functions - * @namespace Chart.Interaction - */ -var core_interaction = { - // Helper function for different modes - modes: { - single: function(chart, e) { - var position = getRelativePosition(e, chart); - var elements = []; - - parseVisibleItems(chart, function(element) { - if (element.inRange(position.x, position.y)) { - elements.push(element); - return elements; - } - }); - - return elements.slice(0, 1); - }, - - /** - * @function Chart.Interaction.modes.label - * @deprecated since version 2.4.0 - * @todo remove at version 3 - * @private - */ - label: indexMode, - - /** - * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something - * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item - * @function Chart.Interaction.modes.index - * @since v2.4.0 - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use during interaction - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - index: indexMode, - - /** - * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something - * If the options.intersect is false, we find the nearest item and return the items in that dataset - * @function Chart.Interaction.modes.dataset - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use during interaction - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - dataset: function(chart, e, options) { - var position = getRelativePosition(e, chart); - options.axis = options.axis || 'xy'; - var distanceMetric = getDistanceMetricForAxis(options.axis); - var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); - - if (items.length > 0) { - items = chart.getDatasetMeta(items[0]._datasetIndex).data; - } - - return items; - }, - - /** - * @function Chart.Interaction.modes.x-axis - * @deprecated since version 2.4.0. Use index mode and intersect == true - * @todo remove at version 3 - * @private - */ - 'x-axis': function(chart, e) { - return indexMode(chart, e, {intersect: false}); - }, - - /** - * Point mode returns all elements that hit test based on the event position - * of the event - * @function Chart.Interaction.modes.intersect - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - point: function(chart, e) { - var position = getRelativePosition(e, chart); - return getIntersectItems(chart, position); - }, - - /** - * nearest mode returns the element closest to the point - * @function Chart.Interaction.modes.intersect - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - nearest: function(chart, e, options) { - var position = getRelativePosition(e, chart); - options.axis = options.axis || 'xy'; - var distanceMetric = getDistanceMetricForAxis(options.axis); - return getNearestItems(chart, position, options.intersect, distanceMetric); - }, - - /** - * x mode returns the elements that hit-test at the current x coordinate - * @function Chart.Interaction.modes.x - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - x: function(chart, e, options) { - var position = getRelativePosition(e, chart); - var items = []; - var intersectsItem = false; - - parseVisibleItems(chart, function(element) { - if (element.inXRange(position.x)) { - items.push(element); - } - - if (element.inRange(position.x, position.y)) { - intersectsItem = true; - } - }); - - // If we want to trigger on an intersect and we don't have any items - // that intersect the position, return nothing - if (options.intersect && !intersectsItem) { - items = []; - } - return items; - }, - - /** - * y mode returns the elements that hit-test at the current y coordinate - * @function Chart.Interaction.modes.y - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - y: function(chart, e, options) { - var position = getRelativePosition(e, chart); - var items = []; - var intersectsItem = false; - - parseVisibleItems(chart, function(element) { - if (element.inYRange(position.y)) { - items.push(element); - } - - if (element.inRange(position.x, position.y)) { - intersectsItem = true; - } - }); - - // If we want to trigger on an intersect and we don't have any items - // that intersect the position, return nothing - if (options.intersect && !intersectsItem) { - items = []; - } - return items; - } - } -}; - -var extend = helpers$1.extend; - -function filterByPosition(array, position) { - return helpers$1.where(array, function(v) { - return v.pos === position; - }); -} - -function sortByWeight(array, reverse) { - return array.sort(function(a, b) { - var v0 = reverse ? b : a; - var v1 = reverse ? a : b; - return v0.weight === v1.weight ? - v0.index - v1.index : - v0.weight - v1.weight; - }); -} - -function wrapBoxes(boxes) { - var layoutBoxes = []; - var i, ilen, box; - - for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { - box = boxes[i]; - layoutBoxes.push({ - index: i, - box: box, - pos: box.position, - horizontal: box.isHorizontal(), - weight: box.weight - }); - } - return layoutBoxes; -} - -function setLayoutDims(layouts, params) { - var i, ilen, layout; - for (i = 0, ilen = layouts.length; i < ilen; ++i) { - layout = layouts[i]; - // store width used instead of chartArea.w in fitBoxes - layout.width = layout.horizontal - ? layout.box.fullWidth && params.availableWidth - : params.vBoxMaxWidth; - // store height used instead of chartArea.h in fitBoxes - layout.height = layout.horizontal && params.hBoxMaxHeight; - } -} - -function buildLayoutBoxes(boxes) { - var layoutBoxes = wrapBoxes(boxes); - var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); - var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); - var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); - var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); - - return { - leftAndTop: left.concat(top), - rightAndBottom: right.concat(bottom), - chartArea: filterByPosition(layoutBoxes, 'chartArea'), - vertical: left.concat(right), - horizontal: top.concat(bottom) - }; -} - -function getCombinedMax(maxPadding, chartArea, a, b) { - return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); -} - -function updateDims(chartArea, params, layout) { - var box = layout.box; - var maxPadding = chartArea.maxPadding; - var newWidth, newHeight; - - if (layout.size) { - // this layout was already counted for, lets first reduce old size - chartArea[layout.pos] -= layout.size; - } - layout.size = layout.horizontal ? box.height : box.width; - chartArea[layout.pos] += layout.size; - - if (box.getPadding) { - var boxPadding = box.getPadding(); - maxPadding.top = Math.max(maxPadding.top, boxPadding.top); - maxPadding.left = Math.max(maxPadding.left, boxPadding.left); - maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); - maxPadding.right = Math.max(maxPadding.right, boxPadding.right); - } - - newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); - newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); - - if (newWidth !== chartArea.w || newHeight !== chartArea.h) { - chartArea.w = newWidth; - chartArea.h = newHeight; - - // return true if chart area changed in layout's direction - return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; - } -} - -function handleMaxPadding(chartArea) { - var maxPadding = chartArea.maxPadding; - - function updatePos(pos) { - var change = Math.max(maxPadding[pos] - chartArea[pos], 0); - chartArea[pos] += change; - return change; - } - chartArea.y += updatePos('top'); - chartArea.x += updatePos('left'); - updatePos('right'); - updatePos('bottom'); -} - -function getMargins(horizontal, chartArea) { - var maxPadding = chartArea.maxPadding; - - function marginForPositions(positions) { - var margin = {left: 0, top: 0, right: 0, bottom: 0}; - positions.forEach(function(pos) { - margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); - }); - return margin; - } - - return horizontal - ? marginForPositions(['left', 'right']) - : marginForPositions(['top', 'bottom']); -} - -function fitBoxes(boxes, chartArea, params) { - var refitBoxes = []; - var i, ilen, layout, box, refit, changed; - - for (i = 0, ilen = boxes.length; i < ilen; ++i) { - layout = boxes[i]; - box = layout.box; - - box.update( - layout.width || chartArea.w, - layout.height || chartArea.h, - getMargins(layout.horizontal, chartArea) - ); - if (updateDims(chartArea, params, layout)) { - changed = true; - if (refitBoxes.length) { - // Dimensions changed and there were non full width boxes before this - // -> we have to refit those - refit = true; - } - } - if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case - refitBoxes.push(layout); - } - } - - return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; -} - -function placeBoxes(boxes, chartArea, params) { - var userPadding = params.padding; - var x = chartArea.x; - var y = chartArea.y; - var i, ilen, layout, box; - - for (i = 0, ilen = boxes.length; i < ilen; ++i) { - layout = boxes[i]; - box = layout.box; - if (layout.horizontal) { - box.left = box.fullWidth ? userPadding.left : chartArea.left; - box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; - box.top = y; - box.bottom = y + box.height; - box.width = box.right - box.left; - y = box.bottom; - } else { - box.left = x; - box.right = x + box.width; - box.top = chartArea.top; - box.bottom = chartArea.top + chartArea.h; - box.height = box.bottom - box.top; - x = box.right; - } - } - - chartArea.x = x; - chartArea.y = y; -} - -core_defaults._set('global', { - layout: { - padding: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - } -}); - -/** - * @interface ILayoutItem - * @prop {string} position - The position of the item in the chart layout. Possible values are - * 'left', 'top', 'right', 'bottom', and 'chartArea' - * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area - * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down - * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) - * @prop {function} update - Takes two parameters: width and height. Returns size of item - * @prop {function} getPadding - Returns an object with padding on the edges - * @prop {number} width - Width of item. Must be valid after update() - * @prop {number} height - Height of item. Must be valid after update() - * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update - * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update - * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update - * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update - */ - -// The layout service is very self explanatory. It's responsible for the layout within a chart. -// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need -// It is this service's responsibility of carrying out that layout. -var core_layouts = { - defaults: {}, - - /** - * Register a box to a chart. - * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. - * @param {Chart} chart - the chart to use - * @param {ILayoutItem} item - the item to add to be layed out - */ - addBox: function(chart, item) { - if (!chart.boxes) { - chart.boxes = []; - } - - // initialize item with default values - item.fullWidth = item.fullWidth || false; - item.position = item.position || 'top'; - item.weight = item.weight || 0; - item._layers = item._layers || function() { - return [{ - z: 0, - draw: function() { - item.draw.apply(item, arguments); - } - }]; - }; - - chart.boxes.push(item); - }, - - /** - * Remove a layoutItem from a chart - * @param {Chart} chart - the chart to remove the box from - * @param {ILayoutItem} layoutItem - the item to remove from the layout - */ - removeBox: function(chart, layoutItem) { - var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; - if (index !== -1) { - chart.boxes.splice(index, 1); - } - }, - - /** - * Sets (or updates) options on the given `item`. - * @param {Chart} chart - the chart in which the item lives (or will be added to) - * @param {ILayoutItem} item - the item to configure with the given options - * @param {object} options - the new item options. - */ - configure: function(chart, item, options) { - var props = ['fullWidth', 'position', 'weight']; - var ilen = props.length; - var i = 0; - var prop; - - for (; i < ilen; ++i) { - prop = props[i]; - if (options.hasOwnProperty(prop)) { - item[prop] = options[prop]; - } - } - }, - - /** - * Fits boxes of the given chart into the given size by having each box measure itself - * then running a fitting algorithm - * @param {Chart} chart - the chart - * @param {number} width - the width to fit into - * @param {number} height - the height to fit into - */ - update: function(chart, width, height) { - if (!chart) { - return; - } - - var layoutOptions = chart.options.layout || {}; - var padding = helpers$1.options.toPadding(layoutOptions.padding); - - var availableWidth = width - padding.width; - var availableHeight = height - padding.height; - var boxes = buildLayoutBoxes(chart.boxes); - var verticalBoxes = boxes.vertical; - var horizontalBoxes = boxes.horizontal; - - // Essentially we now have any number of boxes on each of the 4 sides. - // Our canvas looks like the following. - // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and - // B1 is the bottom axis - // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays - // These locations are single-box locations only, when trying to register a chartArea location that is already taken, - // an error will be thrown. - // - // |----------------------------------------------------| - // | T1 (Full Width) | - // |----------------------------------------------------| - // | | | T2 | | - // | |----|-------------------------------------|----| - // | | | C1 | | C2 | | - // | | |----| |----| | - // | | | | | - // | L1 | L2 | ChartArea (C0) | R1 | - // | | | | | - // | | |----| |----| | - // | | | C3 | | C4 | | - // | |----|-------------------------------------|----| - // | | | B1 | | - // |----------------------------------------------------| - // | B2 (Full Width) | - // |----------------------------------------------------| - // - - var params = Object.freeze({ - outerWidth: width, - outerHeight: height, - padding: padding, - availableWidth: availableWidth, - vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, - hBoxMaxHeight: availableHeight / 2 - }); - var chartArea = extend({ - maxPadding: extend({}, padding), - w: availableWidth, - h: availableHeight, - x: padding.left, - y: padding.top - }, padding); - - setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); - - // First fit vertical boxes - fitBoxes(verticalBoxes, chartArea, params); - - // Then fit horizontal boxes - if (fitBoxes(horizontalBoxes, chartArea, params)) { - // if the area changed, re-fit vertical boxes - fitBoxes(verticalBoxes, chartArea, params); - } - - handleMaxPadding(chartArea); - - // Finally place the boxes to correct coordinates - placeBoxes(boxes.leftAndTop, chartArea, params); - - // Move to opposite side of chart - chartArea.x += chartArea.w; - chartArea.y += chartArea.h; - - placeBoxes(boxes.rightAndBottom, chartArea, params); - - chart.chartArea = { - left: chartArea.left, - top: chartArea.top, - right: chartArea.left + chartArea.w, - bottom: chartArea.top + chartArea.h - }; - - // Finally update boxes in chartArea (radial scale for example) - helpers$1.each(boxes.chartArea, function(layout) { - var box = layout.box; - extend(box, chart.chartArea); - box.update(chartArea.w, chartArea.h); - }); - } -}; - -/** - * Platform fallback implementation (minimal). - * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 - */ - -var platform_basic = { - acquireContext: function(item) { - if (item && item.canvas) { - // Support for any object associated to a canvas (including a context2d) - item = item.canvas; - } - - return item && item.getContext('2d') || null; - } -}; - -var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n"; - -var platform_dom$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -'default': platform_dom -}); - -var stylesheet = getCjsExportFromNamespace(platform_dom$1); - -var EXPANDO_KEY = '$chartjs'; -var CSS_PREFIX = 'chartjs-'; -var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; -var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; -var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; -var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; - -/** - * DOM event types -> Chart.js event types. - * Note: only events with different types are mapped. - * @see https://developer.mozilla.org/en-US/docs/Web/Events - */ -var EVENT_TYPES = { - touchstart: 'mousedown', - touchmove: 'mousemove', - touchend: 'mouseup', - pointerenter: 'mouseenter', - pointerdown: 'mousedown', - pointermove: 'mousemove', - pointerup: 'mouseup', - pointerleave: 'mouseout', - pointerout: 'mouseout' -}; - -/** - * The "used" size is the final value of a dimension property after all calculations have - * been performed. This method uses the computed style of `element` but returns undefined - * if the computed style is not expressed in pixels. That can happen in some cases where - * `element` has a size relative to its parent and this last one is not yet displayed, - * for example because of `display: none` on a parent node. - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value - * @returns {number} Size in pixels or undefined if unknown. - */ -function readUsedSize(element, property) { - var value = helpers$1.getStyle(element, property); - var matches = value && value.match(/^(\d+)(\.\d+)?px$/); - return matches ? Number(matches[1]) : undefined; -} - -/** - * Initializes the canvas style and render size without modifying the canvas display size, - * since responsiveness is handled by the controller.resize() method. The config is used - * to determine the aspect ratio to apply in case no explicit height has been specified. - */ -function initCanvas(canvas, config) { - var style = canvas.style; - - // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it - // returns null or '' if no explicit value has been set to the canvas attribute. - var renderHeight = canvas.getAttribute('height'); - var renderWidth = canvas.getAttribute('width'); - - // Chart.js modifies some canvas values that we want to restore on destroy - canvas[EXPANDO_KEY] = { - initial: { - height: renderHeight, - width: renderWidth, - style: { - display: style.display, - height: style.height, - width: style.width - } - } - }; - - // Force canvas to display as block to avoid extra space caused by inline - // elements, which would interfere with the responsive resize process. - // https://github.com/chartjs/Chart.js/issues/2538 - style.display = style.display || 'block'; - - if (renderWidth === null || renderWidth === '') { - var displayWidth = readUsedSize(canvas, 'width'); - if (displayWidth !== undefined) { - canvas.width = displayWidth; - } - } - - if (renderHeight === null || renderHeight === '') { - if (canvas.style.height === '') { - // If no explicit render height and style height, let's apply the aspect ratio, - // which one can be specified by the user but also by charts as default option - // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. - canvas.height = canvas.width / (config.options.aspectRatio || 2); - } else { - var displayHeight = readUsedSize(canvas, 'height'); - if (displayWidth !== undefined) { - canvas.height = displayHeight; - } - } - } - - return canvas; -} - -/** - * Detects support for options object argument in addEventListener. - * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support - * @private - */ -var supportsEventListenerOptions = (function() { - var supports = false; - try { - var options = Object.defineProperty({}, 'passive', { - // eslint-disable-next-line getter-return - get: function() { - supports = true; - } - }); - window.addEventListener('e', null, options); - } catch (e) { - // continue regardless of error - } - return supports; -}()); - -// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. -// https://github.com/chartjs/Chart.js/issues/4287 -var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; - -function addListener(node, type, listener) { - node.addEventListener(type, listener, eventListenerOptions); -} - -function removeListener(node, type, listener) { - node.removeEventListener(type, listener, eventListenerOptions); -} - -function createEvent(type, chart, x, y, nativeEvent) { - return { - type: type, - chart: chart, - native: nativeEvent || null, - x: x !== undefined ? x : null, - y: y !== undefined ? y : null, - }; -} - -function fromNativeEvent(event, chart) { - var type = EVENT_TYPES[event.type] || event.type; - var pos = helpers$1.getRelativePosition(event, chart); - return createEvent(type, chart, pos.x, pos.y, event); -} - -function throttled(fn, thisArg) { - var ticking = false; - var args = []; - - return function() { - args = Array.prototype.slice.call(arguments); - thisArg = thisArg || this; - - if (!ticking) { - ticking = true; - helpers$1.requestAnimFrame.call(window, function() { - ticking = false; - fn.apply(thisArg, args); - }); - } - }; -} - -function createDiv(cls) { - var el = document.createElement('div'); - el.className = cls || ''; - return el; -} - -// Implementation based on https://github.com/marcj/css-element-queries -function createResizer(handler) { - var maxSize = 1000000; - - // NOTE(SB) Don't use innerHTML because it could be considered unsafe. - // https://github.com/chartjs/Chart.js/issues/5902 - var resizer = createDiv(CSS_SIZE_MONITOR); - var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); - var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); - - expand.appendChild(createDiv()); - shrink.appendChild(createDiv()); - - resizer.appendChild(expand); - resizer.appendChild(shrink); - resizer._reset = function() { - expand.scrollLeft = maxSize; - expand.scrollTop = maxSize; - shrink.scrollLeft = maxSize; - shrink.scrollTop = maxSize; - }; - - var onScroll = function() { - resizer._reset(); - handler(); - }; - - addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); - addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); - - return resizer; -} - -// https://davidwalsh.name/detect-node-insertion -function watchForRender(node, handler) { - var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); - var proxy = expando.renderProxy = function(e) { - if (e.animationName === CSS_RENDER_ANIMATION) { - handler(); - } - }; - - helpers$1.each(ANIMATION_START_EVENTS, function(type) { - addListener(node, type, proxy); - }); - - // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class - // is removed then added back immediately (same animation frame?). Accessing the - // `offsetParent` property will force a reflow and re-evaluate the CSS animation. - // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics - // https://github.com/chartjs/Chart.js/issues/4737 - expando.reflow = !!node.offsetParent; - - node.classList.add(CSS_RENDER_MONITOR); -} - -function unwatchForRender(node) { - var expando = node[EXPANDO_KEY] || {}; - var proxy = expando.renderProxy; - - if (proxy) { - helpers$1.each(ANIMATION_START_EVENTS, function(type) { - removeListener(node, type, proxy); - }); - - delete expando.renderProxy; - } - - node.classList.remove(CSS_RENDER_MONITOR); -} - -function addResizeListener(node, listener, chart) { - var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); - - // Let's keep track of this added resizer and thus avoid DOM query when removing it. - var resizer = expando.resizer = createResizer(throttled(function() { - if (expando.resizer) { - var container = chart.options.maintainAspectRatio && node.parentNode; - var w = container ? container.clientWidth : 0; - listener(createEvent('resize', chart)); - if (container && container.clientWidth < w && chart.canvas) { - // If the container size shrank during chart resize, let's assume - // scrollbar appeared. So we resize again with the scrollbar visible - - // effectively making chart smaller and the scrollbar hidden again. - // Because we are inside `throttled`, and currently `ticking`, scroll - // events are ignored during this whole 2 resize process. - // If we assumed wrong and something else happened, we are resizing - // twice in a frame (potential performance issue) - listener(createEvent('resize', chart)); - } - } - })); - - // The resizer needs to be attached to the node parent, so we first need to be - // sure that `node` is attached to the DOM before injecting the resizer element. - watchForRender(node, function() { - if (expando.resizer) { - var container = node.parentNode; - if (container && container !== resizer.parentNode) { - container.insertBefore(resizer, container.firstChild); - } - - // The container size might have changed, let's reset the resizer state. - resizer._reset(); - } - }); -} - -function removeResizeListener(node) { - var expando = node[EXPANDO_KEY] || {}; - var resizer = expando.resizer; - - delete expando.resizer; - unwatchForRender(node); - - if (resizer && resizer.parentNode) { - resizer.parentNode.removeChild(resizer); - } -} - -/** - * Injects CSS styles inline if the styles are not already present. - * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>. - * @param {string} css - the CSS to be injected. - */ -function injectCSS(rootNode, css) { - // https://stackoverflow.com/q/3922139 - var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {}); - if (!expando.containsStyles) { - expando.containsStyles = true; - css = '/* Chart.js */\n' + css; - var style = document.createElement('style'); - style.setAttribute('type', 'text/css'); - style.appendChild(document.createTextNode(css)); - rootNode.appendChild(style); - } -} - -var platform_dom$2 = { - /** - * When `true`, prevents the automatic injection of the stylesheet required to - * correctly detect when the chart is added to the DOM and then resized. This - * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`) - * to be manually imported to make this library compatible with any CSP. - * See https://github.com/chartjs/Chart.js/issues/5208 - */ - disableCSSInjection: false, - - /** - * This property holds whether this platform is enabled for the current environment. - * Currently used by platform.js to select the proper implementation. - * @private - */ - _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', - - /** - * Initializes resources that depend on platform options. - * @param {HTMLCanvasElement} canvas - The Canvas element. - * @private - */ - _ensureLoaded: function(canvas) { - if (!this.disableCSSInjection) { - // If the canvas is in a shadow DOM, then the styles must also be inserted - // into the same shadow DOM. - // https://github.com/chartjs/Chart.js/issues/5763 - var root = canvas.getRootNode ? canvas.getRootNode() : document; - var targetNode = root.host ? root : document.head; - injectCSS(targetNode, stylesheet); - } - }, - - acquireContext: function(item, config) { - if (typeof item === 'string') { - item = document.getElementById(item); - } else if (item.length) { - // Support for array based queries (such as jQuery) - item = item[0]; - } - - if (item && item.canvas) { - // Support for any object associated to a canvas (including a context2d) - item = item.canvas; - } - - // To prevent canvas fingerprinting, some add-ons undefine the getContext - // method, for example: https://github.com/kkapsner/CanvasBlocker - // https://github.com/chartjs/Chart.js/issues/2807 - var context = item && item.getContext && item.getContext('2d'); - - // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is - // inside an iframe or when running in a protected environment. We could guess the - // types from their toString() value but let's keep things flexible and assume it's - // a sufficient condition if the item has a context2D which has item as `canvas`. - // https://github.com/chartjs/Chart.js/issues/3887 - // https://github.com/chartjs/Chart.js/issues/4102 - // https://github.com/chartjs/Chart.js/issues/4152 - if (context && context.canvas === item) { - // Load platform resources on first chart creation, to make it possible to - // import the library before setting platform options. - this._ensureLoaded(item); - initCanvas(item, config); - return context; - } - - return null; - }, - - releaseContext: function(context) { - var canvas = context.canvas; - if (!canvas[EXPANDO_KEY]) { - return; - } - - var initial = canvas[EXPANDO_KEY].initial; - ['height', 'width'].forEach(function(prop) { - var value = initial[prop]; - if (helpers$1.isNullOrUndef(value)) { - canvas.removeAttribute(prop); - } else { - canvas.setAttribute(prop, value); - } - }); - - helpers$1.each(initial.style || {}, function(value, key) { - canvas.style[key] = value; - }); - - // The canvas render size might have been changed (and thus the state stack discarded), - // we can't use save() and restore() to restore the initial state. So make sure that at - // least the canvas context is reset to the default state by setting the canvas width. - // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html - // eslint-disable-next-line no-self-assign - canvas.width = canvas.width; - - delete canvas[EXPANDO_KEY]; - }, - - addEventListener: function(chart, type, listener) { - var canvas = chart.canvas; - if (type === 'resize') { - // Note: the resize event is not supported on all browsers. - addResizeListener(canvas, listener, chart); - return; - } - - var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); - var proxies = expando.proxies || (expando.proxies = {}); - var proxy = proxies[chart.id + '_' + type] = function(event) { - listener(fromNativeEvent(event, chart)); - }; - - addListener(canvas, type, proxy); - }, - - removeEventListener: function(chart, type, listener) { - var canvas = chart.canvas; - if (type === 'resize') { - // Note: the resize event is not supported on all browsers. - removeResizeListener(canvas); - return; - } - - var expando = listener[EXPANDO_KEY] || {}; - var proxies = expando.proxies || {}; - var proxy = proxies[chart.id + '_' + type]; - if (!proxy) { - return; - } - - removeListener(canvas, type, proxy); - } -}; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use EventTarget.addEventListener instead. - * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - * @function Chart.helpers.addEvent - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers$1.addEvent = addListener; - -/** - * Provided for backward compatibility, use EventTarget.removeEventListener instead. - * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener - * @function Chart.helpers.removeEvent - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers$1.removeEvent = removeListener; - -// @TODO Make possible to select another platform at build time. -var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic; - -/** - * @namespace Chart.platform - * @see https://chartjs.gitbooks.io/proposals/content/Platform.html - * @since 2.4.0 - */ -var platform = helpers$1.extend({ - /** - * @since 2.7.0 - */ - initialize: function() {}, - - /** - * Called at chart construction time, returns a context2d instance implementing - * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. - * @param {*} item - The native item from which to acquire context (platform specific) - * @param {object} options - The chart options - * @returns {CanvasRenderingContext2D} context2d instance - */ - acquireContext: function() {}, - - /** - * Called at chart destruction time, releases any resources associated to the context - * previously returned by the acquireContext() method. - * @param {CanvasRenderingContext2D} context - The context2d instance - * @returns {boolean} true if the method succeeded, else false - */ - releaseContext: function() {}, - - /** - * Registers the specified listener on the given chart. - * @param {Chart} chart - Chart from which to listen for event - * @param {string} type - The ({@link IEvent}) type to listen for - * @param {function} listener - Receives a notification (an object that implements - * the {@link IEvent} interface) when an event of the specified type occurs. - */ - addEventListener: function() {}, - - /** - * Removes the specified listener previously registered with addEventListener. - * @param {Chart} chart - Chart from which to remove the listener - * @param {string} type - The ({@link IEvent}) type to remove - * @param {function} listener - The listener function to remove from the event target. - */ - removeEventListener: function() {} - -}, implementation); - -core_defaults._set('global', { - plugins: {} -}); - -/** - * The plugin service singleton - * @namespace Chart.plugins - * @since 2.1.0 - */ -var core_plugins = { - /** - * Globally registered plugins. - * @private - */ - _plugins: [], - - /** - * This identifier is used to invalidate the descriptors cache attached to each chart - * when a global plugin is registered or unregistered. In this case, the cache ID is - * incremented and descriptors are regenerated during following API calls. - * @private - */ - _cacheId: 0, - - /** - * Registers the given plugin(s) if not already registered. - * @param {IPlugin[]|IPlugin} plugins plugin instance(s). - */ - register: function(plugins) { - var p = this._plugins; - ([]).concat(plugins).forEach(function(plugin) { - if (p.indexOf(plugin) === -1) { - p.push(plugin); - } - }); - - this._cacheId++; - }, - - /** - * Unregisters the given plugin(s) only if registered. - * @param {IPlugin[]|IPlugin} plugins plugin instance(s). - */ - unregister: function(plugins) { - var p = this._plugins; - ([]).concat(plugins).forEach(function(plugin) { - var idx = p.indexOf(plugin); - if (idx !== -1) { - p.splice(idx, 1); - } - }); - - this._cacheId++; - }, - - /** - * Remove all registered plugins. - * @since 2.1.5 - */ - clear: function() { - this._plugins = []; - this._cacheId++; - }, - - /** - * Returns the number of registered plugins? - * @returns {number} - * @since 2.1.5 - */ - count: function() { - return this._plugins.length; - }, - - /** - * Returns all registered plugin instances. - * @returns {IPlugin[]} array of plugin objects. - * @since 2.1.5 - */ - getAll: function() { - return this._plugins; - }, - - /** - * Calls enabled plugins for `chart` on the specified hook and with the given args. - * This method immediately returns as soon as a plugin explicitly returns false. The - * returned value can be used, for instance, to interrupt the current action. - * @param {Chart} chart - The chart instance for which plugins should be called. - * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). - * @param {Array} [args] - Extra arguments to apply to the hook call. - * @returns {boolean} false if any of the plugins return false, else returns true. - */ - notify: function(chart, hook, args) { - var descriptors = this.descriptors(chart); - var ilen = descriptors.length; - var i, descriptor, plugin, params, method; - - for (i = 0; i < ilen; ++i) { - descriptor = descriptors[i]; - plugin = descriptor.plugin; - method = plugin[hook]; - if (typeof method === 'function') { - params = [chart].concat(args || []); - params.push(descriptor.options); - if (method.apply(plugin, params) === false) { - return false; - } - } - } - - return true; - }, - - /** - * Returns descriptors of enabled plugins for the given chart. - * @returns {object[]} [{ plugin, options }] - * @private - */ - descriptors: function(chart) { - var cache = chart.$plugins || (chart.$plugins = {}); - if (cache.id === this._cacheId) { - return cache.descriptors; - } - - var plugins = []; - var descriptors = []; - var config = (chart && chart.config) || {}; - var options = (config.options && config.options.plugins) || {}; - - this._plugins.concat(config.plugins || []).forEach(function(plugin) { - var idx = plugins.indexOf(plugin); - if (idx !== -1) { - return; - } - - var id = plugin.id; - var opts = options[id]; - if (opts === false) { - return; - } - - if (opts === true) { - opts = helpers$1.clone(core_defaults.global.plugins[id]); - } - - plugins.push(plugin); - descriptors.push({ - plugin: plugin, - options: opts || {} - }); - }); - - cache.descriptors = descriptors; - cache.id = this._cacheId; - return descriptors; - }, - - /** - * Invalidates cache for the given chart: descriptors hold a reference on plugin option, - * but in some cases, this reference can be changed by the user when updating options. - * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 - * @private - */ - _invalidate: function(chart) { - delete chart.$plugins; - } -}; - -var core_scaleService = { - // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then - // use the new chart options to grab the correct scale - constructors: {}, - // Use a registration function so that we can move to an ES6 map when we no longer need to support - // old browsers - - // Scale config defaults - defaults: {}, - registerScaleType: function(type, scaleConstructor, scaleDefaults) { - this.constructors[type] = scaleConstructor; - this.defaults[type] = helpers$1.clone(scaleDefaults); - }, - getScaleConstructor: function(type) { - return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; - }, - getScaleDefaults: function(type) { - // Return the scale defaults merged with the global settings so that we always use the latest ones - return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {}; - }, - updateScaleDefaults: function(type, additions) { - var me = this; - if (me.defaults.hasOwnProperty(type)) { - me.defaults[type] = helpers$1.extend(me.defaults[type], additions); - } - }, - addScalesToLayout: function(chart) { - // Adds each scale to the chart.boxes array to be sized accordingly - helpers$1.each(chart.scales, function(scale) { - // Set ILayoutItem parameters for backwards compatibility - scale.fullWidth = scale.options.fullWidth; - scale.position = scale.options.position; - scale.weight = scale.options.weight; - core_layouts.addBox(chart, scale); - }); - } -}; - -var valueOrDefault$8 = helpers$1.valueOrDefault; -var getRtlHelper = helpers$1.rtl.getRtlAdapter; - -core_defaults._set('global', { - tooltips: { - enabled: true, - custom: null, - mode: 'nearest', - position: 'average', - intersect: true, - backgroundColor: 'rgba(0,0,0,0.8)', - titleFontStyle: 'bold', - titleSpacing: 2, - titleMarginBottom: 6, - titleFontColor: '#fff', - titleAlign: 'left', - bodySpacing: 2, - bodyFontColor: '#fff', - bodyAlign: 'left', - footerFontStyle: 'bold', - footerSpacing: 2, - footerMarginTop: 6, - footerFontColor: '#fff', - footerAlign: 'left', - yPadding: 6, - xPadding: 6, - caretPadding: 2, - caretSize: 5, - cornerRadius: 6, - multiKeyBackground: '#fff', - displayColors: true, - borderColor: 'rgba(0,0,0,0)', - borderWidth: 0, - callbacks: { - // Args are: (tooltipItems, data) - beforeTitle: helpers$1.noop, - title: function(tooltipItems, data) { - var title = ''; - var labels = data.labels; - var labelCount = labels ? labels.length : 0; - - if (tooltipItems.length > 0) { - var item = tooltipItems[0]; - if (item.label) { - title = item.label; - } else if (item.xLabel) { - title = item.xLabel; - } else if (labelCount > 0 && item.index < labelCount) { - title = labels[item.index]; - } - } - - return title; - }, - afterTitle: helpers$1.noop, - - // Args are: (tooltipItems, data) - beforeBody: helpers$1.noop, - - // Args are: (tooltipItem, data) - beforeLabel: helpers$1.noop, - label: function(tooltipItem, data) { - var label = data.datasets[tooltipItem.datasetIndex].label || ''; - - if (label) { - label += ': '; - } - if (!helpers$1.isNullOrUndef(tooltipItem.value)) { - label += tooltipItem.value; - } else { - label += tooltipItem.yLabel; - } - return label; - }, - labelColor: function(tooltipItem, chart) { - var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); - var activeElement = meta.data[tooltipItem.index]; - var view = activeElement._view; - return { - borderColor: view.borderColor, - backgroundColor: view.backgroundColor - }; - }, - labelTextColor: function() { - return this._options.bodyFontColor; - }, - afterLabel: helpers$1.noop, - - // Args are: (tooltipItems, data) - afterBody: helpers$1.noop, - - // Args are: (tooltipItems, data) - beforeFooter: helpers$1.noop, - footer: helpers$1.noop, - afterFooter: helpers$1.noop - } - } -}); - -var positioners = { - /** - * Average mode places the tooltip at the average position of the elements shown - * @function Chart.Tooltip.positioners.average - * @param elements {ChartElement[]} the elements being displayed in the tooltip - * @returns {object} tooltip position - */ - average: function(elements) { - if (!elements.length) { - return false; - } - - var i, len; - var x = 0; - var y = 0; - var count = 0; - - for (i = 0, len = elements.length; i < len; ++i) { - var el = elements[i]; - if (el && el.hasValue()) { - var pos = el.tooltipPosition(); - x += pos.x; - y += pos.y; - ++count; - } - } - - return { - x: x / count, - y: y / count - }; - }, - - /** - * Gets the tooltip position nearest of the item nearest to the event position - * @function Chart.Tooltip.positioners.nearest - * @param elements {Chart.Element[]} the tooltip elements - * @param eventPosition {object} the position of the event in canvas coordinates - * @returns {object} the tooltip position - */ - nearest: function(elements, eventPosition) { - var x = eventPosition.x; - var y = eventPosition.y; - var minDistance = Number.POSITIVE_INFINITY; - var i, len, nearestElement; - - for (i = 0, len = elements.length; i < len; ++i) { - var el = elements[i]; - if (el && el.hasValue()) { - var center = el.getCenterPoint(); - var d = helpers$1.distanceBetweenPoints(eventPosition, center); - - if (d < minDistance) { - minDistance = d; - nearestElement = el; - } - } - } - - if (nearestElement) { - var tp = nearestElement.tooltipPosition(); - x = tp.x; - y = tp.y; - } - - return { - x: x, - y: y - }; - } -}; - -// Helper to push or concat based on if the 2nd parameter is an array or not -function pushOrConcat(base, toPush) { - if (toPush) { - if (helpers$1.isArray(toPush)) { - // base = base.concat(toPush); - Array.prototype.push.apply(base, toPush); - } else { - base.push(toPush); - } - } - - return base; -} - -/** - * Returns array of strings split by newline - * @param {string} value - The value to split by newline. - * @returns {string[]} value if newline present - Returned from String split() method - * @function - */ -function splitNewlines(str) { - if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { - return str.split('\n'); - } - return str; -} - - -/** - * Private helper to create a tooltip item model - * @param element - the chart element (point, arc, bar) to create the tooltip item for - * @return new tooltip item - */ -function createTooltipItem(element) { - var xScale = element._xScale; - var yScale = element._yScale || element._scale; // handle radar || polarArea charts - var index = element._index; - var datasetIndex = element._datasetIndex; - var controller = element._chart.getDatasetMeta(datasetIndex).controller; - var indexScale = controller._getIndexScale(); - var valueScale = controller._getValueScale(); - - return { - xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', - yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', - label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '', - value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '', - index: index, - datasetIndex: datasetIndex, - x: element._model.x, - y: element._model.y - }; -} - -/** - * Helper to get the reset model for the tooltip - * @param tooltipOpts {object} the tooltip options - */ -function getBaseModel(tooltipOpts) { - var globalDefaults = core_defaults.global; - - return { - // Positioning - xPadding: tooltipOpts.xPadding, - yPadding: tooltipOpts.yPadding, - xAlign: tooltipOpts.xAlign, - yAlign: tooltipOpts.yAlign, - - // Drawing direction and text direction - rtl: tooltipOpts.rtl, - textDirection: tooltipOpts.textDirection, - - // Body - bodyFontColor: tooltipOpts.bodyFontColor, - _bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), - _bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), - _bodyAlign: tooltipOpts.bodyAlign, - bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), - bodySpacing: tooltipOpts.bodySpacing, - - // Title - titleFontColor: tooltipOpts.titleFontColor, - _titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), - _titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), - titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), - _titleAlign: tooltipOpts.titleAlign, - titleSpacing: tooltipOpts.titleSpacing, - titleMarginBottom: tooltipOpts.titleMarginBottom, - - // Footer - footerFontColor: tooltipOpts.footerFontColor, - _footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), - _footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), - footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), - _footerAlign: tooltipOpts.footerAlign, - footerSpacing: tooltipOpts.footerSpacing, - footerMarginTop: tooltipOpts.footerMarginTop, - - // Appearance - caretSize: tooltipOpts.caretSize, - cornerRadius: tooltipOpts.cornerRadius, - backgroundColor: tooltipOpts.backgroundColor, - opacity: 0, - legendColorBackground: tooltipOpts.multiKeyBackground, - displayColors: tooltipOpts.displayColors, - borderColor: tooltipOpts.borderColor, - borderWidth: tooltipOpts.borderWidth - }; -} - -/** - * Get the size of the tooltip - */ -function getTooltipSize(tooltip, model) { - var ctx = tooltip._chart.ctx; - - var height = model.yPadding * 2; // Tooltip Padding - var width = 0; - - // Count of all lines in the body - var body = model.body; - var combinedBodyLength = body.reduce(function(count, bodyItem) { - return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; - }, 0); - combinedBodyLength += model.beforeBody.length + model.afterBody.length; - - var titleLineCount = model.title.length; - var footerLineCount = model.footer.length; - var titleFontSize = model.titleFontSize; - var bodyFontSize = model.bodyFontSize; - var footerFontSize = model.footerFontSize; - - height += titleLineCount * titleFontSize; // Title Lines - height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing - height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin - height += combinedBodyLength * bodyFontSize; // Body Lines - height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing - height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin - height += footerLineCount * (footerFontSize); // Footer Lines - height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing - - // Title width - var widthPadding = 0; - var maxLineWidth = function(line) { - width = Math.max(width, ctx.measureText(line).width + widthPadding); - }; - - ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); - helpers$1.each(model.title, maxLineWidth); - - // Body width - ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); - helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth); - - // Body lines may include some extra width due to the color box - widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; - helpers$1.each(body, function(bodyItem) { - helpers$1.each(bodyItem.before, maxLineWidth); - helpers$1.each(bodyItem.lines, maxLineWidth); - helpers$1.each(bodyItem.after, maxLineWidth); - }); - - // Reset back to 0 - widthPadding = 0; - - // Footer width - ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); - helpers$1.each(model.footer, maxLineWidth); - - // Add padding - width += 2 * model.xPadding; - - return { - width: width, - height: height - }; -} - -/** - * Helper to get the alignment of a tooltip given the size - */ -function determineAlignment(tooltip, size) { - var model = tooltip._model; - var chart = tooltip._chart; - var chartArea = tooltip._chart.chartArea; - var xAlign = 'center'; - var yAlign = 'center'; - - if (model.y < size.height) { - yAlign = 'top'; - } else if (model.y > (chart.height - size.height)) { - yAlign = 'bottom'; - } - - var lf, rf; // functions to determine left, right alignment - var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart - var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges - var midX = (chartArea.left + chartArea.right) / 2; - var midY = (chartArea.top + chartArea.bottom) / 2; - - if (yAlign === 'center') { - lf = function(x) { - return x <= midX; - }; - rf = function(x) { - return x > midX; - }; - } else { - lf = function(x) { - return x <= (size.width / 2); - }; - rf = function(x) { - return x >= (chart.width - (size.width / 2)); - }; - } - - olf = function(x) { - return x + size.width + model.caretSize + model.caretPadding > chart.width; - }; - orf = function(x) { - return x - size.width - model.caretSize - model.caretPadding < 0; - }; - yf = function(y) { - return y <= midY ? 'top' : 'bottom'; - }; - - if (lf(model.x)) { - xAlign = 'left'; - - // Is tooltip too wide and goes over the right side of the chart.? - if (olf(model.x)) { - xAlign = 'center'; - yAlign = yf(model.y); - } - } else if (rf(model.x)) { - xAlign = 'right'; - - // Is tooltip too wide and goes outside left edge of canvas? - if (orf(model.x)) { - xAlign = 'center'; - yAlign = yf(model.y); - } - } - - var opts = tooltip._options; - return { - xAlign: opts.xAlign ? opts.xAlign : xAlign, - yAlign: opts.yAlign ? opts.yAlign : yAlign - }; -} - -/** - * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment - */ -function getBackgroundPoint(vm, size, alignment, chart) { - // Background Position - var x = vm.x; - var y = vm.y; - - var caretSize = vm.caretSize; - var caretPadding = vm.caretPadding; - var cornerRadius = vm.cornerRadius; - var xAlign = alignment.xAlign; - var yAlign = alignment.yAlign; - var paddingAndSize = caretSize + caretPadding; - var radiusAndPadding = cornerRadius + caretPadding; - - if (xAlign === 'right') { - x -= size.width; - } else if (xAlign === 'center') { - x -= (size.width / 2); - if (x + size.width > chart.width) { - x = chart.width - size.width; - } - if (x < 0) { - x = 0; - } - } - - if (yAlign === 'top') { - y += paddingAndSize; - } else if (yAlign === 'bottom') { - y -= size.height + paddingAndSize; - } else { - y -= (size.height / 2); - } - - if (yAlign === 'center') { - if (xAlign === 'left') { - x += paddingAndSize; - } else if (xAlign === 'right') { - x -= paddingAndSize; - } - } else if (xAlign === 'left') { - x -= radiusAndPadding; - } else if (xAlign === 'right') { - x += radiusAndPadding; - } - - return { - x: x, - y: y - }; -} - -function getAlignedX(vm, align) { - return align === 'center' - ? vm.x + vm.width / 2 - : align === 'right' - ? vm.x + vm.width - vm.xPadding - : vm.x + vm.xPadding; -} - -/** - * Helper to build before and after body lines - */ -function getBeforeAfterBodyLines(callback) { - return pushOrConcat([], splitNewlines(callback)); -} - -var exports$4 = core_element.extend({ - initialize: function() { - this._model = getBaseModel(this._options); - this._lastActive = []; - }, - - // Get the title - // Args are: (tooltipItem, data) - getTitle: function() { - var me = this; - var opts = me._options; - var callbacks = opts.callbacks; - - var beforeTitle = callbacks.beforeTitle.apply(me, arguments); - var title = callbacks.title.apply(me, arguments); - var afterTitle = callbacks.afterTitle.apply(me, arguments); - - var lines = []; - lines = pushOrConcat(lines, splitNewlines(beforeTitle)); - lines = pushOrConcat(lines, splitNewlines(title)); - lines = pushOrConcat(lines, splitNewlines(afterTitle)); - - return lines; - }, - - // Args are: (tooltipItem, data) - getBeforeBody: function() { - return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); - }, - - // Args are: (tooltipItem, data) - getBody: function(tooltipItems, data) { - var me = this; - var callbacks = me._options.callbacks; - var bodyItems = []; - - helpers$1.each(tooltipItems, function(tooltipItem) { - var bodyItem = { - before: [], - lines: [], - after: [] - }; - pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); - pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); - pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); - - bodyItems.push(bodyItem); - }); - - return bodyItems; - }, - - // Args are: (tooltipItem, data) - getAfterBody: function() { - return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); - }, - - // Get the footer and beforeFooter and afterFooter lines - // Args are: (tooltipItem, data) - getFooter: function() { - var me = this; - var callbacks = me._options.callbacks; - - var beforeFooter = callbacks.beforeFooter.apply(me, arguments); - var footer = callbacks.footer.apply(me, arguments); - var afterFooter = callbacks.afterFooter.apply(me, arguments); - - var lines = []; - lines = pushOrConcat(lines, splitNewlines(beforeFooter)); - lines = pushOrConcat(lines, splitNewlines(footer)); - lines = pushOrConcat(lines, splitNewlines(afterFooter)); - - return lines; - }, - - update: function(changed) { - var me = this; - var opts = me._options; - - // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition - // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time - // which breaks any animations. - var existingModel = me._model; - var model = me._model = getBaseModel(opts); - var active = me._active; - - var data = me._data; - - // In the case where active.length === 0 we need to keep these at existing values for good animations - var alignment = { - xAlign: existingModel.xAlign, - yAlign: existingModel.yAlign - }; - var backgroundPoint = { - x: existingModel.x, - y: existingModel.y - }; - var tooltipSize = { - width: existingModel.width, - height: existingModel.height - }; - var tooltipPosition = { - x: existingModel.caretX, - y: existingModel.caretY - }; - - var i, len; - - if (active.length) { - model.opacity = 1; - - var labelColors = []; - var labelTextColors = []; - tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); - - var tooltipItems = []; - for (i = 0, len = active.length; i < len; ++i) { - tooltipItems.push(createTooltipItem(active[i])); - } - - // If the user provided a filter function, use it to modify the tooltip items - if (opts.filter) { - tooltipItems = tooltipItems.filter(function(a) { - return opts.filter(a, data); - }); - } - - // If the user provided a sorting function, use it to modify the tooltip items - if (opts.itemSort) { - tooltipItems = tooltipItems.sort(function(a, b) { - return opts.itemSort(a, b, data); - }); - } - - // Determine colors for boxes - helpers$1.each(tooltipItems, function(tooltipItem) { - labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); - labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); - }); - - - // Build the Text Lines - model.title = me.getTitle(tooltipItems, data); - model.beforeBody = me.getBeforeBody(tooltipItems, data); - model.body = me.getBody(tooltipItems, data); - model.afterBody = me.getAfterBody(tooltipItems, data); - model.footer = me.getFooter(tooltipItems, data); - - // Initial positioning and colors - model.x = tooltipPosition.x; - model.y = tooltipPosition.y; - model.caretPadding = opts.caretPadding; - model.labelColors = labelColors; - model.labelTextColors = labelTextColors; - - // data points - model.dataPoints = tooltipItems; - - // We need to determine alignment of the tooltip - tooltipSize = getTooltipSize(this, model); - alignment = determineAlignment(this, tooltipSize); - // Final Size and Position - backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); - } else { - model.opacity = 0; - } - - model.xAlign = alignment.xAlign; - model.yAlign = alignment.yAlign; - model.x = backgroundPoint.x; - model.y = backgroundPoint.y; - model.width = tooltipSize.width; - model.height = tooltipSize.height; - - // Point where the caret on the tooltip points to - model.caretX = tooltipPosition.x; - model.caretY = tooltipPosition.y; - - me._model = model; - - if (changed && opts.custom) { - opts.custom.call(me, model); - } - - return me; - }, - - drawCaret: function(tooltipPoint, size) { - var ctx = this._chart.ctx; - var vm = this._view; - var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); - - ctx.lineTo(caretPosition.x1, caretPosition.y1); - ctx.lineTo(caretPosition.x2, caretPosition.y2); - ctx.lineTo(caretPosition.x3, caretPosition.y3); - }, - getCaretPosition: function(tooltipPoint, size, vm) { - var x1, x2, x3, y1, y2, y3; - var caretSize = vm.caretSize; - var cornerRadius = vm.cornerRadius; - var xAlign = vm.xAlign; - var yAlign = vm.yAlign; - var ptX = tooltipPoint.x; - var ptY = tooltipPoint.y; - var width = size.width; - var height = size.height; - - if (yAlign === 'center') { - y2 = ptY + (height / 2); - - if (xAlign === 'left') { - x1 = ptX; - x2 = x1 - caretSize; - x3 = x1; - - y1 = y2 + caretSize; - y3 = y2 - caretSize; - } else { - x1 = ptX + width; - x2 = x1 + caretSize; - x3 = x1; - - y1 = y2 - caretSize; - y3 = y2 + caretSize; - } - } else { - if (xAlign === 'left') { - x2 = ptX + cornerRadius + (caretSize); - x1 = x2 - caretSize; - x3 = x2 + caretSize; - } else if (xAlign === 'right') { - x2 = ptX + width - cornerRadius - caretSize; - x1 = x2 - caretSize; - x3 = x2 + caretSize; - } else { - x2 = vm.caretX; - x1 = x2 - caretSize; - x3 = x2 + caretSize; - } - if (yAlign === 'top') { - y1 = ptY; - y2 = y1 - caretSize; - y3 = y1; - } else { - y1 = ptY + height; - y2 = y1 + caretSize; - y3 = y1; - // invert drawing order - var tmp = x3; - x3 = x1; - x1 = tmp; - } - } - return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; - }, - - drawTitle: function(pt, vm, ctx) { - var title = vm.title; - var length = title.length; - var titleFontSize, titleSpacing, i; - - if (length) { - var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); - - pt.x = getAlignedX(vm, vm._titleAlign); - - ctx.textAlign = rtlHelper.textAlign(vm._titleAlign); - ctx.textBaseline = 'middle'; - - titleFontSize = vm.titleFontSize; - titleSpacing = vm.titleSpacing; - - ctx.fillStyle = vm.titleFontColor; - ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); - - for (i = 0; i < length; ++i) { - ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2); - pt.y += titleFontSize + titleSpacing; // Line Height and spacing - - if (i + 1 === length) { - pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing - } - } - } - }, - - drawBody: function(pt, vm, ctx) { - var bodyFontSize = vm.bodyFontSize; - var bodySpacing = vm.bodySpacing; - var bodyAlign = vm._bodyAlign; - var body = vm.body; - var drawColorBoxes = vm.displayColors; - var xLinePadding = 0; - var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0; - - var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); - - var fillLineOfText = function(line) { - ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2); - pt.y += bodyFontSize + bodySpacing; - }; - - var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen; - var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); - - ctx.textAlign = bodyAlign; - ctx.textBaseline = 'middle'; - ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); - - pt.x = getAlignedX(vm, bodyAlignForCalculation); - - // Before body lines - ctx.fillStyle = vm.bodyFontColor; - helpers$1.each(vm.beforeBody, fillLineOfText); - - xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right' - ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2) - : 0; - - // Draw body lines now - for (i = 0, ilen = body.length; i < ilen; ++i) { - bodyItem = body[i]; - textColor = vm.labelTextColors[i]; - labelColors = vm.labelColors[i]; - - ctx.fillStyle = textColor; - helpers$1.each(bodyItem.before, fillLineOfText); - - lines = bodyItem.lines; - for (j = 0, jlen = lines.length; j < jlen; ++j) { - // Draw Legend-like boxes if needed - if (drawColorBoxes) { - var rtlColorX = rtlHelper.x(colorX); - - // Fill a white rect so that colours merge nicely if the opacity is < 1 - ctx.fillStyle = vm.legendColorBackground; - ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); - - // Border - ctx.lineWidth = 1; - ctx.strokeStyle = labelColors.borderColor; - ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); - - // Inner square - ctx.fillStyle = labelColors.backgroundColor; - ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); - ctx.fillStyle = textColor; - } - - fillLineOfText(lines[j]); - } - - helpers$1.each(bodyItem.after, fillLineOfText); - } - - // Reset back to 0 for after body - xLinePadding = 0; - - // After body lines - helpers$1.each(vm.afterBody, fillLineOfText); - pt.y -= bodySpacing; // Remove last body spacing - }, - - drawFooter: function(pt, vm, ctx) { - var footer = vm.footer; - var length = footer.length; - var footerFontSize, i; - - if (length) { - var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); - - pt.x = getAlignedX(vm, vm._footerAlign); - pt.y += vm.footerMarginTop; - - ctx.textAlign = rtlHelper.textAlign(vm._footerAlign); - ctx.textBaseline = 'middle'; - - footerFontSize = vm.footerFontSize; - - ctx.fillStyle = vm.footerFontColor; - ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); - - for (i = 0; i < length; ++i) { - ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2); - pt.y += footerFontSize + vm.footerSpacing; - } - } - }, - - drawBackground: function(pt, vm, ctx, tooltipSize) { - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; - var xAlign = vm.xAlign; - var yAlign = vm.yAlign; - var x = pt.x; - var y = pt.y; - var width = tooltipSize.width; - var height = tooltipSize.height; - var radius = vm.cornerRadius; - - ctx.beginPath(); - ctx.moveTo(x + radius, y); - if (yAlign === 'top') { - this.drawCaret(pt, tooltipSize); - } - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - if (yAlign === 'center' && xAlign === 'right') { - this.drawCaret(pt, tooltipSize); - } - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - if (yAlign === 'bottom') { - this.drawCaret(pt, tooltipSize); - } - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - if (yAlign === 'center' && xAlign === 'left') { - this.drawCaret(pt, tooltipSize); - } - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - - ctx.fill(); - - if (vm.borderWidth > 0) { - ctx.stroke(); - } - }, - - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - - if (vm.opacity === 0) { - return; - } - - var tooltipSize = { - width: vm.width, - height: vm.height - }; - var pt = { - x: vm.x, - y: vm.y - }; - - // IE11/Edge does not like very small opacities, so snap to 0 - var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; - - // Truthy/falsey value for empty tooltip - var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; - - if (this._options.enabled && hasTooltipContent) { - ctx.save(); - ctx.globalAlpha = opacity; - - // Draw Background - this.drawBackground(pt, vm, ctx, tooltipSize); - - // Draw Title, Body, and Footer - pt.y += vm.yPadding; - - helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection); - - // Titles - this.drawTitle(pt, vm, ctx); - - // Body - this.drawBody(pt, vm, ctx); - - // Footer - this.drawFooter(pt, vm, ctx); - - helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection); - - ctx.restore(); - } - }, - - /** - * Handle an event - * @private - * @param {IEvent} event - The event to handle - * @returns {boolean} true if the tooltip changed - */ - handleEvent: function(e) { - var me = this; - var options = me._options; - var changed = false; - - me._lastActive = me._lastActive || []; - - // Find Active Elements for tooltips - if (e.type === 'mouseout') { - me._active = []; - } else { - me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); - if (options.reverse) { - me._active.reverse(); - } - } - - // Remember Last Actives - changed = !helpers$1.arrayEquals(me._active, me._lastActive); - - // Only handle target event on tooltip change - if (changed) { - me._lastActive = me._active; - - if (options.enabled || options.custom) { - me._eventPosition = { - x: e.x, - y: e.y - }; - - me.update(true); - me.pivot(); - } - } - - return changed; - } -}); - -/** - * @namespace Chart.Tooltip.positioners - */ -var positioners_1 = positioners; - -var core_tooltip = exports$4; -core_tooltip.positioners = positioners_1; - -var valueOrDefault$9 = helpers$1.valueOrDefault; - -core_defaults._set('global', { - elements: {}, - events: [ - 'mousemove', - 'mouseout', - 'click', - 'touchstart', - 'touchmove' - ], - hover: { - onHover: null, - mode: 'nearest', - intersect: true, - animationDuration: 400 - }, - onClick: null, - maintainAspectRatio: true, - responsive: true, - responsiveAnimationDuration: 0 -}); - -/** - * Recursively merge the given config objects representing the `scales` option - * by incorporating scale defaults in `xAxes` and `yAxes` array items, then - * returns a deep copy of the result, thus doesn't alter inputs. - */ -function mergeScaleConfig(/* config objects ... */) { - return helpers$1.merge({}, [].slice.call(arguments), { - merger: function(key, target, source, options) { - if (key === 'xAxes' || key === 'yAxes') { - var slen = source[key].length; - var i, type, scale; - - if (!target[key]) { - target[key] = []; - } - - for (i = 0; i < slen; ++i) { - scale = source[key][i]; - type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear'); - - if (i >= target[key].length) { - target[key].push({}); - } - - if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { - // new/untyped scale or type changed: let's apply the new defaults - // then merge source scale to correctly overwrite the defaults. - helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]); - } else { - // scales type are the same - helpers$1.merge(target[key][i], scale); - } - } - } else { - helpers$1._merger(key, target, source, options); - } - } - }); -} - -/** - * Recursively merge the given config objects as the root options by handling - * default scale options for the `scales` and `scale` properties, then returns - * a deep copy of the result, thus doesn't alter inputs. - */ -function mergeConfig(/* config objects ... */) { - return helpers$1.merge({}, [].slice.call(arguments), { - merger: function(key, target, source, options) { - var tval = target[key] || {}; - var sval = source[key]; - - if (key === 'scales') { - // scale config merging is complex. Add our own function here for that - target[key] = mergeScaleConfig(tval, sval); - } else if (key === 'scale') { - // used in polar area & radar charts since there is only one scale - target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]); - } else { - helpers$1._merger(key, target, source, options); - } - } - }); -} - -function initConfig(config) { - config = config || {}; - - // Do NOT use mergeConfig for the data object because this method merges arrays - // and so would change references to labels and datasets, preventing data updates. - var data = config.data = config.data || {}; - data.datasets = data.datasets || []; - data.labels = data.labels || []; - - config.options = mergeConfig( - core_defaults.global, - core_defaults[config.type], - config.options || {}); - - return config; -} - -function updateConfig(chart) { - var newOptions = chart.options; - - helpers$1.each(chart.scales, function(scale) { - core_layouts.removeBox(chart, scale); - }); - - newOptions = mergeConfig( - core_defaults.global, - core_defaults[chart.config.type], - newOptions); - - chart.options = chart.config.options = newOptions; - chart.ensureScalesHaveIDs(); - chart.buildOrUpdateScales(); - - // Tooltip - chart.tooltip._options = newOptions.tooltips; - chart.tooltip.initialize(); -} - -function nextAvailableScaleId(axesOpts, prefix, index) { - var id; - var hasId = function(obj) { - return obj.id === id; - }; - - do { - id = prefix + index++; - } while (helpers$1.findIndex(axesOpts, hasId) >= 0); - - return id; -} - -function positionIsHorizontal(position) { - return position === 'top' || position === 'bottom'; -} - -function compare2Level(l1, l2) { - return function(a, b) { - return a[l1] === b[l1] - ? a[l2] - b[l2] - : a[l1] - b[l1]; - }; -} - -var Chart = function(item, config) { - this.construct(item, config); - return this; -}; - -helpers$1.extend(Chart.prototype, /** @lends Chart */ { - /** - * @private - */ - construct: function(item, config) { - var me = this; - - config = initConfig(config); - - var context = platform.acquireContext(item, config); - var canvas = context && context.canvas; - var height = canvas && canvas.height; - var width = canvas && canvas.width; - - me.id = helpers$1.uid(); - me.ctx = context; - me.canvas = canvas; - me.config = config; - me.width = width; - me.height = height; - me.aspectRatio = height ? width / height : null; - me.options = config.options; - me._bufferedRender = false; - me._layers = []; - - /** - * Provided for backward compatibility, Chart and Chart.Controller have been merged, - * the "instance" still need to be defined since it might be called from plugins. - * @prop Chart#chart - * @deprecated since version 2.6.0 - * @todo remove at version 3 - * @private - */ - me.chart = me; - me.controller = me; // chart.chart.controller #inception - - // Add the chart instance to the global namespace - Chart.instances[me.id] = me; - - // Define alias to the config data: `chart.data === chart.config.data` - Object.defineProperty(me, 'data', { - get: function() { - return me.config.data; - }, - set: function(value) { - me.config.data = value; - } - }); - - if (!context || !canvas) { - // The given item is not a compatible context2d element, let's return before finalizing - // the chart initialization but after setting basic chart / controller properties that - // can help to figure out that the chart is not valid (e.g chart.canvas !== null); - // https://github.com/chartjs/Chart.js/issues/2807 - console.error("Failed to create chart: can't acquire context from the given item"); - return; - } - - me.initialize(); - me.update(); - }, - - /** - * @private - */ - initialize: function() { - var me = this; - - // Before init plugin notification - core_plugins.notify(me, 'beforeInit'); - - helpers$1.retinaScale(me, me.options.devicePixelRatio); - - me.bindEvents(); - - if (me.options.responsive) { - // Initial resize before chart draws (must be silent to preserve initial animations). - me.resize(true); - } - - me.initToolTip(); - - // After init plugin notification - core_plugins.notify(me, 'afterInit'); - - return me; - }, - - clear: function() { - helpers$1.canvas.clear(this); - return this; - }, - - stop: function() { - // Stops any current animation loop occurring - core_animations.cancelAnimation(this); - return this; - }, - - resize: function(silent) { - var me = this; - var options = me.options; - var canvas = me.canvas; - var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; - - // the canvas render width and height will be casted to integers so make sure that - // the canvas display style uses the same integer values to avoid blurring effect. - - // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed - var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas))); - var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas))); - - if (me.width === newWidth && me.height === newHeight) { - return; - } - - canvas.width = me.width = newWidth; - canvas.height = me.height = newHeight; - canvas.style.width = newWidth + 'px'; - canvas.style.height = newHeight + 'px'; - - helpers$1.retinaScale(me, options.devicePixelRatio); - - if (!silent) { - // Notify any plugins about the resize - var newSize = {width: newWidth, height: newHeight}; - core_plugins.notify(me, 'resize', [newSize]); - - // Notify of resize - if (options.onResize) { - options.onResize(me, newSize); - } - - me.stop(); - me.update({ - duration: options.responsiveAnimationDuration - }); - } - }, - - ensureScalesHaveIDs: function() { - var options = this.options; - var scalesOptions = options.scales || {}; - var scaleOptions = options.scale; - - helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) { - if (!xAxisOptions.id) { - xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index); - } - }); - - helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) { - if (!yAxisOptions.id) { - yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index); - } - }); - - if (scaleOptions) { - scaleOptions.id = scaleOptions.id || 'scale'; - } - }, - - /** - * Builds a map of scale ID to scale object for future lookup. - */ - buildOrUpdateScales: function() { - var me = this; - var options = me.options; - var scales = me.scales || {}; - var items = []; - var updated = Object.keys(scales).reduce(function(obj, id) { - obj[id] = false; - return obj; - }, {}); - - if (options.scales) { - items = items.concat( - (options.scales.xAxes || []).map(function(xAxisOptions) { - return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; - }), - (options.scales.yAxes || []).map(function(yAxisOptions) { - return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; - }) - ); - } - - if (options.scale) { - items.push({ - options: options.scale, - dtype: 'radialLinear', - isDefault: true, - dposition: 'chartArea' - }); - } - - helpers$1.each(items, function(item) { - var scaleOptions = item.options; - var id = scaleOptions.id; - var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype); - - if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { - scaleOptions.position = item.dposition; - } - - updated[id] = true; - var scale = null; - if (id in scales && scales[id].type === scaleType) { - scale = scales[id]; - scale.options = scaleOptions; - scale.ctx = me.ctx; - scale.chart = me; - } else { - var scaleClass = core_scaleService.getScaleConstructor(scaleType); - if (!scaleClass) { - return; - } - scale = new scaleClass({ - id: id, - type: scaleType, - options: scaleOptions, - ctx: me.ctx, - chart: me - }); - scales[scale.id] = scale; - } - - scale.mergeTicksOptions(); - - // TODO(SB): I think we should be able to remove this custom case (options.scale) - // and consider it as a regular scale part of the "scales"" map only! This would - // make the logic easier and remove some useless? custom code. - if (item.isDefault) { - me.scale = scale; - } - }); - // clear up discarded scales - helpers$1.each(updated, function(hasUpdated, id) { - if (!hasUpdated) { - delete scales[id]; - } - }); - - me.scales = scales; - - core_scaleService.addScalesToLayout(this); - }, - - buildOrUpdateControllers: function() { - var me = this; - var newControllers = []; - var datasets = me.data.datasets; - var i, ilen; - - for (i = 0, ilen = datasets.length; i < ilen; i++) { - var dataset = datasets[i]; - var meta = me.getDatasetMeta(i); - var type = dataset.type || me.config.type; - - if (meta.type && meta.type !== type) { - me.destroyDatasetMeta(i); - meta = me.getDatasetMeta(i); - } - meta.type = type; - meta.order = dataset.order || 0; - meta.index = i; - - if (meta.controller) { - meta.controller.updateIndex(i); - meta.controller.linkScales(); - } else { - var ControllerClass = controllers[meta.type]; - if (ControllerClass === undefined) { - throw new Error('"' + meta.type + '" is not a chart type.'); - } - - meta.controller = new ControllerClass(me, i); - newControllers.push(meta.controller); - } - } - - return newControllers; - }, - - /** - * Reset the elements of all datasets - * @private - */ - resetElements: function() { - var me = this; - helpers$1.each(me.data.datasets, function(dataset, datasetIndex) { - me.getDatasetMeta(datasetIndex).controller.reset(); - }, me); - }, - - /** - * Resets the chart back to it's state before the initial animation - */ - reset: function() { - this.resetElements(); - this.tooltip.initialize(); - }, - - update: function(config) { - var me = this; - var i, ilen; - - if (!config || typeof config !== 'object') { - // backwards compatibility - config = { - duration: config, - lazy: arguments[1] - }; - } - - updateConfig(me); - - // plugins options references might have change, let's invalidate the cache - // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 - core_plugins._invalidate(me); - - if (core_plugins.notify(me, 'beforeUpdate') === false) { - return; - } - - // In case the entire data object changed - me.tooltip._data = me.data; - - // Make sure dataset controllers are updated and new controllers are reset - var newControllers = me.buildOrUpdateControllers(); - - // Make sure all dataset controllers have correct meta data counts - for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) { - me.getDatasetMeta(i).controller.buildOrUpdateElements(); - } - - me.updateLayout(); - - // Can only reset the new controllers after the scales have been updated - if (me.options.animation && me.options.animation.duration) { - helpers$1.each(newControllers, function(controller) { - controller.reset(); - }); - } - - me.updateDatasets(); - - // Need to reset tooltip in case it is displayed with elements that are removed - // after update. - me.tooltip.initialize(); - - // Last active contains items that were previously in the tooltip. - // When we reset the tooltip, we need to clear it - me.lastActive = []; - - // Do this before render so that any plugins that need final scale updates can use it - core_plugins.notify(me, 'afterUpdate'); - - me._layers.sort(compare2Level('z', '_idx')); - - if (me._bufferedRender) { - me._bufferedRequest = { - duration: config.duration, - easing: config.easing, - lazy: config.lazy - }; - } else { - me.render(config); - } - }, - - /** - * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` - * hook, in which case, plugins will not be called on `afterLayout`. - * @private - */ - updateLayout: function() { - var me = this; - - if (core_plugins.notify(me, 'beforeLayout') === false) { - return; - } - - core_layouts.update(this, this.width, this.height); - - me._layers = []; - helpers$1.each(me.boxes, function(box) { - // _configure is called twice, once in core.scale.update and once here. - // Here the boxes are fully updated and at their final positions. - if (box._configure) { - box._configure(); - } - me._layers.push.apply(me._layers, box._layers()); - }, me); - - me._layers.forEach(function(item, index) { - item._idx = index; - }); - - /** - * Provided for backward compatibility, use `afterLayout` instead. - * @method IPlugin#afterScaleUpdate - * @deprecated since version 2.5.0 - * @todo remove at version 3 - * @private - */ - core_plugins.notify(me, 'afterScaleUpdate'); - core_plugins.notify(me, 'afterLayout'); - }, - - /** - * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` - * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. - * @private - */ - updateDatasets: function() { - var me = this; - - if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) { - return; - } - - for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { - me.updateDataset(i); - } - - core_plugins.notify(me, 'afterDatasetsUpdate'); - }, - - /** - * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` - * hook, in which case, plugins will not be called on `afterDatasetUpdate`. - * @private - */ - updateDataset: function(index) { - var me = this; - var meta = me.getDatasetMeta(index); - var args = { - meta: meta, - index: index - }; - - if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { - return; - } - - meta.controller._update(); - - core_plugins.notify(me, 'afterDatasetUpdate', [args]); - }, - - render: function(config) { - var me = this; - - if (!config || typeof config !== 'object') { - // backwards compatibility - config = { - duration: config, - lazy: arguments[1] - }; - } - - var animationOptions = me.options.animation; - var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration); - var lazy = config.lazy; - - if (core_plugins.notify(me, 'beforeRender') === false) { - return; - } - - var onComplete = function(animation) { - core_plugins.notify(me, 'afterRender'); - helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me); - }; - - if (animationOptions && duration) { - var animation = new core_animation({ - numSteps: duration / 16.66, // 60 fps - easing: config.easing || animationOptions.easing, - - render: function(chart, animationObject) { - var easingFunction = helpers$1.easing.effects[animationObject.easing]; - var currentStep = animationObject.currentStep; - var stepDecimal = currentStep / animationObject.numSteps; - - chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); - }, - - onAnimationProgress: animationOptions.onProgress, - onAnimationComplete: onComplete - }); - - core_animations.addAnimation(me, animation, duration, lazy); - } else { - me.draw(); - - // See https://github.com/chartjs/Chart.js/issues/3781 - onComplete(new core_animation({numSteps: 0, chart: me})); - } - - return me; - }, - - draw: function(easingValue) { - var me = this; - var i, layers; - - me.clear(); - - if (helpers$1.isNullOrUndef(easingValue)) { - easingValue = 1; - } - - me.transition(easingValue); - - if (me.width <= 0 || me.height <= 0) { - return; - } - - if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) { - return; - } - - // Because of plugin hooks (before/afterDatasetsDraw), datasets can't - // currently be part of layers. Instead, we draw - // layers <= 0 before(default, backward compat), and the rest after - layers = me._layers; - for (i = 0; i < layers.length && layers[i].z <= 0; ++i) { - layers[i].draw(me.chartArea); - } - - me.drawDatasets(easingValue); - - // Rest of layers - for (; i < layers.length; ++i) { - layers[i].draw(me.chartArea); - } - - me._drawTooltip(easingValue); - - core_plugins.notify(me, 'afterDraw', [easingValue]); - }, - - /** - * @private - */ - transition: function(easingValue) { - var me = this; - - for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { - if (me.isDatasetVisible(i)) { - me.getDatasetMeta(i).controller.transition(easingValue); - } - } - - me.tooltip.transition(easingValue); - }, - - /** - * @private - */ - _getSortedDatasetMetas: function(filterVisible) { - var me = this; - var datasets = me.data.datasets || []; - var result = []; - var i, ilen; - - for (i = 0, ilen = datasets.length; i < ilen; ++i) { - if (!filterVisible || me.isDatasetVisible(i)) { - result.push(me.getDatasetMeta(i)); - } - } - - result.sort(compare2Level('order', 'index')); - - return result; - }, - - /** - * @private - */ - _getSortedVisibleDatasetMetas: function() { - return this._getSortedDatasetMetas(true); - }, - - /** - * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` - * hook, in which case, plugins will not be called on `afterDatasetsDraw`. - * @private - */ - drawDatasets: function(easingValue) { - var me = this; - var metasets, i; - - if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { - return; - } - - metasets = me._getSortedVisibleDatasetMetas(); - for (i = metasets.length - 1; i >= 0; --i) { - me.drawDataset(metasets[i], easingValue); - } - - core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]); - }, - - /** - * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` - * hook, in which case, plugins will not be called on `afterDatasetDraw`. - * @private - */ - drawDataset: function(meta, easingValue) { - var me = this; - var args = { - meta: meta, - index: meta.index, - easingValue: easingValue - }; - - if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { - return; - } - - meta.controller.draw(easingValue); - - core_plugins.notify(me, 'afterDatasetDraw', [args]); - }, - - /** - * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` - * hook, in which case, plugins will not be called on `afterTooltipDraw`. - * @private - */ - _drawTooltip: function(easingValue) { - var me = this; - var tooltip = me.tooltip; - var args = { - tooltip: tooltip, - easingValue: easingValue - }; - - if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { - return; - } - - tooltip.draw(); - - core_plugins.notify(me, 'afterTooltipDraw', [args]); - }, - - /** - * Get the single element that was clicked on - * @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw - */ - getElementAtEvent: function(e) { - return core_interaction.modes.single(this, e); - }, - - getElementsAtEvent: function(e) { - return core_interaction.modes.label(this, e, {intersect: true}); - }, - - getElementsAtXAxis: function(e) { - return core_interaction.modes['x-axis'](this, e, {intersect: true}); - }, - - getElementsAtEventForMode: function(e, mode, options) { - var method = core_interaction.modes[mode]; - if (typeof method === 'function') { - return method(this, e, options); - } - - return []; - }, - - getDatasetAtEvent: function(e) { - return core_interaction.modes.dataset(this, e, {intersect: true}); - }, - - getDatasetMeta: function(datasetIndex) { - var me = this; - var dataset = me.data.datasets[datasetIndex]; - if (!dataset._meta) { - dataset._meta = {}; - } - - var meta = dataset._meta[me.id]; - if (!meta) { - meta = dataset._meta[me.id] = { - type: null, - data: [], - dataset: null, - controller: null, - hidden: null, // See isDatasetVisible() comment - xAxisID: null, - yAxisID: null, - order: dataset.order || 0, - index: datasetIndex - }; - } - - return meta; - }, - - getVisibleDatasetCount: function() { - var count = 0; - for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { - if (this.isDatasetVisible(i)) { - count++; - } - } - return count; - }, - - isDatasetVisible: function(datasetIndex) { - var meta = this.getDatasetMeta(datasetIndex); - - // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, - // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. - return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; - }, - - generateLegend: function() { - return this.options.legendCallback(this); - }, - - /** - * @private - */ - destroyDatasetMeta: function(datasetIndex) { - var id = this.id; - var dataset = this.data.datasets[datasetIndex]; - var meta = dataset._meta && dataset._meta[id]; - - if (meta) { - meta.controller.destroy(); - delete dataset._meta[id]; - } - }, - - destroy: function() { - var me = this; - var canvas = me.canvas; - var i, ilen; - - me.stop(); - - // dataset controllers need to cleanup associated data - for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { - me.destroyDatasetMeta(i); - } - - if (canvas) { - me.unbindEvents(); - helpers$1.canvas.clear(me); - platform.releaseContext(me.ctx); - me.canvas = null; - me.ctx = null; - } - - core_plugins.notify(me, 'destroy'); - - delete Chart.instances[me.id]; - }, - - toBase64Image: function() { - return this.canvas.toDataURL.apply(this.canvas, arguments); - }, - - initToolTip: function() { - var me = this; - me.tooltip = new core_tooltip({ - _chart: me, - _chartInstance: me, // deprecated, backward compatibility - _data: me.data, - _options: me.options.tooltips - }, me); - }, - - /** - * @private - */ - bindEvents: function() { - var me = this; - var listeners = me._listeners = {}; - var listener = function() { - me.eventHandler.apply(me, arguments); - }; - - helpers$1.each(me.options.events, function(type) { - platform.addEventListener(me, type, listener); - listeners[type] = listener; - }); - - // Elements used to detect size change should not be injected for non responsive charts. - // See https://github.com/chartjs/Chart.js/issues/2210 - if (me.options.responsive) { - listener = function() { - me.resize(); - }; - - platform.addEventListener(me, 'resize', listener); - listeners.resize = listener; - } - }, - - /** - * @private - */ - unbindEvents: function() { - var me = this; - var listeners = me._listeners; - if (!listeners) { - return; - } - - delete me._listeners; - helpers$1.each(listeners, function(listener, type) { - platform.removeEventListener(me, type, listener); - }); - }, - - updateHoverStyle: function(elements, mode, enabled) { - var prefix = enabled ? 'set' : 'remove'; - var element, i, ilen; - - for (i = 0, ilen = elements.length; i < ilen; ++i) { - element = elements[i]; - if (element) { - this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element); - } - } - - if (mode === 'dataset') { - this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle'](); - } - }, - - /** - * @private - */ - eventHandler: function(e) { - var me = this; - var tooltip = me.tooltip; - - if (core_plugins.notify(me, 'beforeEvent', [e]) === false) { - return; - } - - // Buffer any update calls so that renders do not occur - me._bufferedRender = true; - me._bufferedRequest = null; - - var changed = me.handleEvent(e); - // for smooth tooltip animations issue #4989 - // the tooltip should be the source of change - // Animation check workaround: - // tooltip._start will be null when tooltip isn't animating - if (tooltip) { - changed = tooltip._start - ? tooltip.handleEvent(e) - : changed | tooltip.handleEvent(e); - } - - core_plugins.notify(me, 'afterEvent', [e]); - - var bufferedRequest = me._bufferedRequest; - if (bufferedRequest) { - // If we have an update that was triggered, we need to do a normal render - me.render(bufferedRequest); - } else if (changed && !me.animating) { - // If entering, leaving, or changing elements, animate the change via pivot - me.stop(); - - // We only need to render at this point. Updating will cause scales to be - // recomputed generating flicker & using more memory than necessary. - me.render({ - duration: me.options.hover.animationDuration, - lazy: true - }); - } - - me._bufferedRender = false; - me._bufferedRequest = null; - - return me; - }, - - /** - * Handle an event - * @private - * @param {IEvent} event the event to handle - * @return {boolean} true if the chart needs to re-render - */ - handleEvent: function(e) { - var me = this; - var options = me.options || {}; - var hoverOptions = options.hover; - var changed = false; - - me.lastActive = me.lastActive || []; - - // Find Active Elements for hover and tooltips - if (e.type === 'mouseout') { - me.active = []; - } else { - me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); - } - - // Invoke onHover hook - // Need to call with native event here to not break backwards compatibility - helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); - - if (e.type === 'mouseup' || e.type === 'click') { - if (options.onClick) { - // Use e.native here for backwards compatibility - options.onClick.call(me, e.native, me.active); - } - } - - // Remove styling for last active (even if it may still be active) - if (me.lastActive.length) { - me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); - } - - // Built in hover styling - if (me.active.length && hoverOptions.mode) { - me.updateHoverStyle(me.active, hoverOptions.mode, true); - } - - changed = !helpers$1.arrayEquals(me.active, me.lastActive); - - // Remember Last Actives - me.lastActive = me.active; - - return changed; - } -}); - -/** - * NOTE(SB) We actually don't use this container anymore but we need to keep it - * for backward compatibility. Though, it can still be useful for plugins that - * would need to work on multiple charts?! - */ -Chart.instances = {}; - -var core_controller = Chart; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart instead. - * @class Chart.Controller - * @deprecated since version 2.6 - * @todo remove at version 3 - * @private - */ -Chart.Controller = Chart; - -/** - * Provided for backward compatibility, not available anymore. - * @namespace Chart - * @deprecated since version 2.8 - * @todo remove at version 3 - * @private - */ -Chart.types = {}; - -/** - * Provided for backward compatibility, not available anymore. - * @namespace Chart.helpers.configMerge - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ -helpers$1.configMerge = mergeConfig; - -/** - * Provided for backward compatibility, not available anymore. - * @namespace Chart.helpers.scaleMerge - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ -helpers$1.scaleMerge = mergeScaleConfig; - -var core_helpers = function() { - - // -- Basic js utility methods - - helpers$1.where = function(collection, filterCallback) { - if (helpers$1.isArray(collection) && Array.prototype.filter) { - return collection.filter(filterCallback); - } - var filtered = []; - - helpers$1.each(collection, function(item) { - if (filterCallback(item)) { - filtered.push(item); - } - }); - - return filtered; - }; - helpers$1.findIndex = Array.prototype.findIndex ? - function(array, callback, scope) { - return array.findIndex(callback, scope); - } : - function(array, callback, scope) { - scope = scope === undefined ? array : scope; - for (var i = 0, ilen = array.length; i < ilen; ++i) { - if (callback.call(scope, array[i], i, array)) { - return i; - } - } - return -1; - }; - helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { - // Default to start of the array - if (helpers$1.isNullOrUndef(startIndex)) { - startIndex = -1; - } - for (var i = startIndex + 1; i < arrayToSearch.length; i++) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)) { - return currentItem; - } - } - }; - helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { - // Default to end of the array - if (helpers$1.isNullOrUndef(startIndex)) { - startIndex = arrayToSearch.length; - } - for (var i = startIndex - 1; i >= 0; i--) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)) { - return currentItem; - } - } - }; - - // -- Math methods - helpers$1.isNumber = function(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - }; - helpers$1.almostEquals = function(x, y, epsilon) { - return Math.abs(x - y) < epsilon; - }; - helpers$1.almostWhole = function(x, epsilon) { - var rounded = Math.round(x); - return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x); - }; - helpers$1.max = function(array) { - return array.reduce(function(max, value) { - if (!isNaN(value)) { - return Math.max(max, value); - } - return max; - }, Number.NEGATIVE_INFINITY); - }; - helpers$1.min = function(array) { - return array.reduce(function(min, value) { - if (!isNaN(value)) { - return Math.min(min, value); - } - return min; - }, Number.POSITIVE_INFINITY); - }; - helpers$1.sign = Math.sign ? - function(x) { - return Math.sign(x); - } : - function(x) { - x = +x; // convert to a number - if (x === 0 || isNaN(x)) { - return x; - } - return x > 0 ? 1 : -1; - }; - helpers$1.toRadians = function(degrees) { - return degrees * (Math.PI / 180); - }; - helpers$1.toDegrees = function(radians) { - return radians * (180 / Math.PI); - }; - - /** - * Returns the number of decimal places - * i.e. the number of digits after the decimal point, of the value of this Number. - * @param {number} x - A number. - * @returns {number} The number of decimal places. - * @private - */ - helpers$1._decimalPlaces = function(x) { - if (!helpers$1.isFinite(x)) { - return; - } - var e = 1; - var p = 0; - while (Math.round(x * e) / e !== x) { - e *= 10; - p++; - } - return p; - }; - - // Gets the angle from vertical upright to the point about a centre. - helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) { - var distanceFromXCenter = anglePoint.x - centrePoint.x; - var distanceFromYCenter = anglePoint.y - centrePoint.y; - var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); - - var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); - - if (angle < (-0.5 * Math.PI)) { - angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] - } - - return { - angle: angle, - distance: radialDistanceFromCenter - }; - }; - helpers$1.distanceBetweenPoints = function(pt1, pt2) { - return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); - }; - - /** - * Provided for backward compatibility, not available anymore - * @function Chart.helpers.aliasPixel - * @deprecated since version 2.8.0 - * @todo remove at version 3 - */ - helpers$1.aliasPixel = function(pixelWidth) { - return (pixelWidth % 2 === 0) ? 0 : 0.5; - }; - - /** - * Returns the aligned pixel value to avoid anti-aliasing blur - * @param {Chart} chart - The chart instance. - * @param {number} pixel - A pixel value. - * @param {number} width - The width of the element. - * @returns {number} The aligned pixel value. - * @private - */ - helpers$1._alignPixel = function(chart, pixel, width) { - var devicePixelRatio = chart.currentDevicePixelRatio; - var halfWidth = width / 2; - return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; - }; - - helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { - // Props to Rob Spencer at scaled innovation for his post on splining between points - // http://scaledinnovation.com/analytics/splines/aboutSplines.html - - // This function must also respect "skipped" points - - var previous = firstPoint.skip ? middlePoint : firstPoint; - var current = middlePoint; - var next = afterPoint.skip ? middlePoint : afterPoint; - - var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); - var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); - - var s01 = d01 / (d01 + d12); - var s12 = d12 / (d01 + d12); - - // If all points are the same, s01 & s02 will be inf - s01 = isNaN(s01) ? 0 : s01; - s12 = isNaN(s12) ? 0 : s12; - - var fa = t * s01; // scaling factor for triangle Ta - var fb = t * s12; - - return { - previous: { - x: current.x - fa * (next.x - previous.x), - y: current.y - fa * (next.y - previous.y) - }, - next: { - x: current.x + fb * (next.x - previous.x), - y: current.y + fb * (next.y - previous.y) - } - }; - }; - helpers$1.EPSILON = Number.EPSILON || 1e-14; - helpers$1.splineCurveMonotone = function(points) { - // This function calculates Bézier control points in a similar way than |splineCurve|, - // but preserves monotonicity of the provided data and ensures no local extremums are added - // between the dataset discrete points due to the interpolation. - // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation - - var pointsWithTangents = (points || []).map(function(point) { - return { - model: point._model, - deltaK: 0, - mK: 0 - }; - }); - - // Calculate slopes (deltaK) and initialize tangents (mK) - var pointsLen = pointsWithTangents.length; - var i, pointBefore, pointCurrent, pointAfter; - for (i = 0; i < pointsLen; ++i) { - pointCurrent = pointsWithTangents[i]; - if (pointCurrent.model.skip) { - continue; - } - - pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; - pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; - if (pointAfter && !pointAfter.model.skip) { - var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); - - // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 - pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; - } - - if (!pointBefore || pointBefore.model.skip) { - pointCurrent.mK = pointCurrent.deltaK; - } else if (!pointAfter || pointAfter.model.skip) { - pointCurrent.mK = pointBefore.deltaK; - } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { - pointCurrent.mK = 0; - } else { - pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; - } - } - - // Adjust tangents to ensure monotonic properties - var alphaK, betaK, tauK, squaredMagnitude; - for (i = 0; i < pointsLen - 1; ++i) { - pointCurrent = pointsWithTangents[i]; - pointAfter = pointsWithTangents[i + 1]; - if (pointCurrent.model.skip || pointAfter.model.skip) { - continue; - } - - if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { - pointCurrent.mK = pointAfter.mK = 0; - continue; - } - - alphaK = pointCurrent.mK / pointCurrent.deltaK; - betaK = pointAfter.mK / pointCurrent.deltaK; - squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); - if (squaredMagnitude <= 9) { - continue; - } - - tauK = 3 / Math.sqrt(squaredMagnitude); - pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; - pointAfter.mK = betaK * tauK * pointCurrent.deltaK; - } - - // Compute control points - var deltaX; - for (i = 0; i < pointsLen; ++i) { - pointCurrent = pointsWithTangents[i]; - if (pointCurrent.model.skip) { - continue; - } - - pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; - pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; - if (pointBefore && !pointBefore.model.skip) { - deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; - pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; - pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; - } - if (pointAfter && !pointAfter.model.skip) { - deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; - pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; - pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; - } - } - }; - helpers$1.nextItem = function(collection, index, loop) { - if (loop) { - return index >= collection.length - 1 ? collection[0] : collection[index + 1]; - } - return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; - }; - helpers$1.previousItem = function(collection, index, loop) { - if (loop) { - return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; - } - return index <= 0 ? collection[0] : collection[index - 1]; - }; - // Implementation of the nice number algorithm used in determining where axis labels will go - helpers$1.niceNum = function(range, round) { - var exponent = Math.floor(helpers$1.log10(range)); - var fraction = range / Math.pow(10, exponent); - var niceFraction; - - if (round) { - if (fraction < 1.5) { - niceFraction = 1; - } else if (fraction < 3) { - niceFraction = 2; - } else if (fraction < 7) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } else if (fraction <= 1.0) { - niceFraction = 1; - } else if (fraction <= 2) { - niceFraction = 2; - } else if (fraction <= 5) { - niceFraction = 5; - } else { - niceFraction = 10; - } - - return niceFraction * Math.pow(10, exponent); - }; - // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ - helpers$1.requestAnimFrame = (function() { - if (typeof window === 'undefined') { - return function(callback) { - callback(); - }; - } - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, 1000 / 60); - }; - }()); - // -- DOM methods - helpers$1.getRelativePosition = function(evt, chart) { - var mouseX, mouseY; - var e = evt.originalEvent || evt; - var canvas = evt.target || evt.srcElement; - var boundingRect = canvas.getBoundingClientRect(); - - var touches = e.touches; - if (touches && touches.length > 0) { - mouseX = touches[0].clientX; - mouseY = touches[0].clientY; - - } else { - mouseX = e.clientX; - mouseY = e.clientY; - } - - // Scale mouse coordinates into canvas coordinates - // by following the pattern laid out by 'jerryj' in the comments of - // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ - var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left')); - var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top')); - var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right')); - var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom')); - var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; - var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; - - // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However - // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here - mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); - mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); - - return { - x: mouseX, - y: mouseY - }; - - }; - - // Private helper function to convert max-width/max-height values that may be percentages into a number - function parseMaxStyle(styleValue, node, parentProperty) { - var valueInPixels; - if (typeof styleValue === 'string') { - valueInPixels = parseInt(styleValue, 10); - - if (styleValue.indexOf('%') !== -1) { - // percentage * size in dimension - valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; - } - } else { - valueInPixels = styleValue; - } - - return valueInPixels; - } - - /** - * Returns if the given value contains an effective constraint. - * @private - */ - function isConstrainedValue(value) { - return value !== undefined && value !== null && value !== 'none'; - } - - /** - * Returns the max width or height of the given DOM node in a cross-browser compatible fashion - * @param {HTMLElement} domNode - the node to check the constraint on - * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height') - * @param {string} percentageProperty - property of parent to use when calculating width as a percentage - * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser} - */ - function getConstraintDimension(domNode, maxStyle, percentageProperty) { - var view = document.defaultView; - var parentNode = helpers$1._getParentNode(domNode); - var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; - var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; - var hasCNode = isConstrainedValue(constrainedNode); - var hasCContainer = isConstrainedValue(constrainedContainer); - var infinity = Number.POSITIVE_INFINITY; - - if (hasCNode || hasCContainer) { - return Math.min( - hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, - hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); - } - - return 'none'; - } - // returns Number or undefined if no constraint - helpers$1.getConstraintWidth = function(domNode) { - return getConstraintDimension(domNode, 'max-width', 'clientWidth'); - }; - // returns Number or undefined if no constraint - helpers$1.getConstraintHeight = function(domNode) { - return getConstraintDimension(domNode, 'max-height', 'clientHeight'); - }; - /** - * @private - */ - helpers$1._calculatePadding = function(container, padding, parentDimension) { - padding = helpers$1.getStyle(container, padding); - - return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10); - }; - /** - * @private - */ - helpers$1._getParentNode = function(domNode) { - var parent = domNode.parentNode; - if (parent && parent.toString() === '[object ShadowRoot]') { - parent = parent.host; - } - return parent; - }; - helpers$1.getMaximumWidth = function(domNode) { - var container = helpers$1._getParentNode(domNode); - if (!container) { - return domNode.clientWidth; - } - - var clientWidth = container.clientWidth; - var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth); - var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth); - - var w = clientWidth - paddingLeft - paddingRight; - var cw = helpers$1.getConstraintWidth(domNode); - return isNaN(cw) ? w : Math.min(w, cw); - }; - helpers$1.getMaximumHeight = function(domNode) { - var container = helpers$1._getParentNode(domNode); - if (!container) { - return domNode.clientHeight; - } - - var clientHeight = container.clientHeight; - var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight); - var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight); - - var h = clientHeight - paddingTop - paddingBottom; - var ch = helpers$1.getConstraintHeight(domNode); - return isNaN(ch) ? h : Math.min(h, ch); - }; - helpers$1.getStyle = function(el, property) { - return el.currentStyle ? - el.currentStyle[property] : - document.defaultView.getComputedStyle(el, null).getPropertyValue(property); - }; - helpers$1.retinaScale = function(chart, forceRatio) { - var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; - if (pixelRatio === 1) { - return; - } - - var canvas = chart.canvas; - var height = chart.height; - var width = chart.width; - - canvas.height = height * pixelRatio; - canvas.width = width * pixelRatio; - chart.ctx.scale(pixelRatio, pixelRatio); - - // If no style has been set on the canvas, the render size is used as display size, - // making the chart visually bigger, so let's enforce it to the "correct" values. - // See https://github.com/chartjs/Chart.js/issues/3575 - if (!canvas.style.height && !canvas.style.width) { - canvas.style.height = height + 'px'; - canvas.style.width = width + 'px'; - } - }; - // -- Canvas methods - helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) { - return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; - }; - helpers$1.longestText = function(ctx, font, arrayOfThings, cache) { - cache = cache || {}; - var data = cache.data = cache.data || {}; - var gc = cache.garbageCollect = cache.garbageCollect || []; - - if (cache.font !== font) { - data = cache.data = {}; - gc = cache.garbageCollect = []; - cache.font = font; - } - - ctx.font = font; - var longest = 0; - var ilen = arrayOfThings.length; - var i, j, jlen, thing, nestedThing; - for (i = 0; i < ilen; i++) { - thing = arrayOfThings[i]; - - // Undefined strings and arrays should not be measured - if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) { - longest = helpers$1.measureText(ctx, data, gc, longest, thing); - } else if (helpers$1.isArray(thing)) { - // if it is an array lets measure each element - // to do maybe simplify this function a bit so we can do this more recursively? - for (j = 0, jlen = thing.length; j < jlen; j++) { - nestedThing = thing[j]; - // Undefined strings and arrays should not be measured - if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) { - longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing); - } - } - } - } - - var gcLen = gc.length / 2; - if (gcLen > arrayOfThings.length) { - for (i = 0; i < gcLen; i++) { - delete data[gc[i]]; - } - gc.splice(0, gcLen); - } - return longest; - }; - helpers$1.measureText = function(ctx, data, gc, longest, string) { - var textWidth = data[string]; - if (!textWidth) { - textWidth = data[string] = ctx.measureText(string).width; - gc.push(string); - } - if (textWidth > longest) { - longest = textWidth; - } - return longest; - }; - - /** - * @deprecated - */ - helpers$1.numberOfLabelLines = function(arrayOfThings) { - var numberOfLines = 1; - helpers$1.each(arrayOfThings, function(thing) { - if (helpers$1.isArray(thing)) { - if (thing.length > numberOfLines) { - numberOfLines = thing.length; - } - } - }); - return numberOfLines; - }; - - helpers$1.color = !chartjsColor ? - function(value) { - console.error('Color.js not found!'); - return value; - } : - function(value) { - /* global CanvasGradient */ - if (value instanceof CanvasGradient) { - value = core_defaults.global.defaultColor; - } - - return chartjsColor(value); - }; - - helpers$1.getHoverColor = function(colorValue) { - /* global CanvasPattern */ - return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ? - colorValue : - helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString(); - }; -}; - -function abstract() { - throw new Error( - 'This method is not implemented: either no adapter can ' + - 'be found or an incomplete integration was provided.' - ); -} - -/** - * Date adapter (current used by the time scale) - * @namespace Chart._adapters._date - * @memberof Chart._adapters - * @private - */ - -/** - * Currently supported unit string values. - * @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')} - * @memberof Chart._adapters._date - * @name Unit - */ - -/** - * @class - */ -function DateAdapter(options) { - this.options = options || {}; -} - -helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ { - /** - * Returns a map of time formats for the supported formatting units defined - * in Unit as well as 'datetime' representing a detailed date/time string. - * @returns {{string: string}} - */ - formats: abstract, - - /** - * Parses the given `value` and return the associated timestamp. - * @param {any} value - the value to parse (usually comes from the data) - * @param {string} [format] - the expected data format - * @returns {(number|null)} - * @function - */ - parse: abstract, - - /** - * Returns the formatted date in the specified `format` for a given `timestamp`. - * @param {number} timestamp - the timestamp to format - * @param {string} format - the date/time token - * @return {string} - * @function - */ - format: abstract, - - /** - * Adds the specified `amount` of `unit` to the given `timestamp`. - * @param {number} timestamp - the input timestamp - * @param {number} amount - the amount to add - * @param {Unit} unit - the unit as string - * @return {number} - * @function - */ - add: abstract, - - /** - * Returns the number of `unit` between the given timestamps. - * @param {number} max - the input timestamp (reference) - * @param {number} min - the timestamp to substract - * @param {Unit} unit - the unit as string - * @return {number} - * @function - */ - diff: abstract, - - /** - * Returns start of `unit` for the given `timestamp`. - * @param {number} timestamp - the input timestamp - * @param {Unit} unit - the unit as string - * @param {number} [weekday] - the ISO day of the week with 1 being Monday - * and 7 being Sunday (only needed if param *unit* is `isoWeek`). - * @function - */ - startOf: abstract, - - /** - * Returns end of `unit` for the given `timestamp`. - * @param {number} timestamp - the input timestamp - * @param {Unit} unit - the unit as string - * @function - */ - endOf: abstract, - - // DEPRECATIONS - - /** - * Provided for backward compatibility for scale.getValueForPixel(), - * this method should be overridden only by the moment adapter. - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ - _create: function(value) { - return value; - } -}); - -DateAdapter.override = function(members) { - helpers$1.extend(DateAdapter.prototype, members); -}; - -var _date = DateAdapter; - -var core_adapters = { - _date: _date -}; - -/** - * Namespace to hold static tick generation functions - * @namespace Chart.Ticks - */ -var core_ticks = { - /** - * Namespace to hold formatters for different types of ticks - * @namespace Chart.Ticks.formatters - */ - formatters: { - /** - * Formatter for value labels - * @method Chart.Ticks.formatters.values - * @param value the value to display - * @return {string|string[]} the label to display - */ - values: function(value) { - return helpers$1.isArray(value) ? value : '' + value; - }, - - /** - * Formatter for linear numeric ticks - * @method Chart.Ticks.formatters.linear - * @param tickValue {number} the value to be formatted - * @param index {number} the position of the tickValue parameter in the ticks array - * @param ticks {number[]} the list of ticks being converted - * @return {string} string representation of the tickValue parameter - */ - linear: function(tickValue, index, ticks) { - // If we have lots of ticks, don't use the ones - var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; - - // If we have a number like 2.5 as the delta, figure out how many decimal places we need - if (Math.abs(delta) > 1) { - if (tickValue !== Math.floor(tickValue)) { - // not an integer - delta = tickValue - Math.floor(tickValue); - } - } - - var logDelta = helpers$1.log10(Math.abs(delta)); - var tickString = ''; - - if (tickValue !== 0) { - var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); - if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation - var logTick = helpers$1.log10(Math.abs(tickValue)); - var numExponential = Math.floor(logTick) - Math.floor(logDelta); - numExponential = Math.max(Math.min(numExponential, 20), 0); - tickString = tickValue.toExponential(numExponential); - } else { - var numDecimal = -1 * Math.floor(logDelta); - numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places - tickString = tickValue.toFixed(numDecimal); - } - } else { - tickString = '0'; // never show decimal places for 0 - } - - return tickString; - }, - - logarithmic: function(tickValue, index, ticks) { - var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue)))); - - if (tickValue === 0) { - return '0'; - } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { - return tickValue.toExponential(); - } - return ''; - } - } -}; - -var isArray = helpers$1.isArray; -var isNullOrUndef = helpers$1.isNullOrUndef; -var valueOrDefault$a = helpers$1.valueOrDefault; -var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault; - -core_defaults._set('scale', { - display: true, - position: 'left', - offset: false, - - // grid line settings - gridLines: { - display: true, - color: 'rgba(0,0,0,0.1)', - lineWidth: 1, - drawBorder: true, - drawOnChartArea: true, - drawTicks: true, - tickMarkLength: 10, - zeroLineWidth: 1, - zeroLineColor: 'rgba(0,0,0,0.25)', - zeroLineBorderDash: [], - zeroLineBorderDashOffset: 0.0, - offsetGridLines: false, - borderDash: [], - borderDashOffset: 0.0 - }, - - // scale label - scaleLabel: { - // display property - display: false, - - // actual label - labelString: '', - - // top/bottom padding - padding: { - top: 4, - bottom: 4 - } - }, - - // label settings - ticks: { - beginAtZero: false, - minRotation: 0, - maxRotation: 50, - mirror: false, - padding: 0, - reverse: false, - display: true, - autoSkip: true, - autoSkipPadding: 0, - labelOffset: 0, - // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. - callback: core_ticks.formatters.values, - minor: {}, - major: {} - } -}); - -/** Returns a new array containing numItems from arr */ -function sample(arr, numItems) { - var result = []; - var increment = arr.length / numItems; - var i = 0; - var len = arr.length; - - for (; i < len; i += increment) { - result.push(arr[Math.floor(i)]); - } - return result; -} - -function getPixelForGridLine(scale, index, offsetGridLines) { - var length = scale.getTicks().length; - var validIndex = Math.min(index, length - 1); - var lineValue = scale.getPixelForTick(validIndex); - var start = scale._startPixel; - var end = scale._endPixel; - var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. - var offset; - - if (offsetGridLines) { - if (length === 1) { - offset = Math.max(lineValue - start, end - lineValue); - } else if (index === 0) { - offset = (scale.getPixelForTick(1) - lineValue) / 2; - } else { - offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; - } - lineValue += validIndex < index ? offset : -offset; - - // Return undefined if the pixel is out of the range - if (lineValue < start - epsilon || lineValue > end + epsilon) { - return; - } - } - return lineValue; -} - -function garbageCollect(caches, length) { - helpers$1.each(caches, function(cache) { - var gc = cache.gc; - var gcLen = gc.length / 2; - var i; - if (gcLen > length) { - for (i = 0; i < gcLen; ++i) { - delete cache.data[gc[i]]; - } - gc.splice(0, gcLen); - } - }); -} - -/** - * Returns {width, height, offset} objects for the first, last, widest, highest tick - * labels where offset indicates the anchor point offset from the top in pixels. - */ -function computeLabelSizes(ctx, tickFonts, ticks, caches) { - var length = ticks.length; - var widths = []; - var heights = []; - var offsets = []; - var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest; - - for (i = 0; i < length; ++i) { - label = ticks[i].label; - tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor; - ctx.font = fontString = tickFont.string; - cache = caches[fontString] = caches[fontString] || {data: {}, gc: []}; - lineHeight = tickFont.lineHeight; - width = height = 0; - // Undefined labels and arrays should not be measured - if (!isNullOrUndef(label) && !isArray(label)) { - width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label); - height = lineHeight; - } else if (isArray(label)) { - // if it is an array let's measure each element - for (j = 0, jlen = label.length; j < jlen; ++j) { - nestedLabel = label[j]; - // Undefined labels and arrays should not be measured - if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { - width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel); - height += lineHeight; - } - } - } - widths.push(width); - heights.push(height); - offsets.push(lineHeight / 2); - } - garbageCollect(caches, length); - - widest = widths.indexOf(Math.max.apply(null, widths)); - highest = heights.indexOf(Math.max.apply(null, heights)); - - function valueAt(idx) { - return { - width: widths[idx] || 0, - height: heights[idx] || 0, - offset: offsets[idx] || 0 - }; - } - - return { - first: valueAt(0), - last: valueAt(length - 1), - widest: valueAt(widest), - highest: valueAt(highest) - }; -} - -function getTickMarkLength(options) { - return options.drawTicks ? options.tickMarkLength : 0; -} - -function getScaleLabelHeight(options) { - var font, padding; - - if (!options.display) { - return 0; - } - - font = helpers$1.options._parseFont(options); - padding = helpers$1.options.toPadding(options.padding); - - return font.lineHeight + padding.height; -} - -function parseFontOptions(options, nestedOpts) { - return helpers$1.extend(helpers$1.options._parseFont({ - fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily), - fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize), - fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle), - lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight) - }), { - color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor]) - }); -} - -function parseTickFontOptions(options) { - var minor = parseFontOptions(options, options.minor); - var major = options.major.enabled ? parseFontOptions(options, options.major) : minor; - - return {minor: minor, major: major}; -} - -function nonSkipped(ticksToFilter) { - var filtered = []; - var item, index, len; - for (index = 0, len = ticksToFilter.length; index < len; ++index) { - item = ticksToFilter[index]; - if (typeof item._index !== 'undefined') { - filtered.push(item); - } - } - return filtered; -} - -function getEvenSpacing(arr) { - var len = arr.length; - var i, diff; - - if (len < 2) { - return false; - } - - for (diff = arr[0], i = 1; i < len; ++i) { - if (arr[i] - arr[i - 1] !== diff) { - return false; - } - } - return diff; -} - -function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) { - var evenMajorSpacing = getEvenSpacing(majorIndices); - var spacing = (ticks.length - 1) / ticksLimit; - var factors, factor, i, ilen; - - // If the major ticks are evenly spaced apart, place the minor ticks - // so that they divide the major ticks into even chunks - if (!evenMajorSpacing) { - return Math.max(spacing, 1); - } - - factors = helpers$1.math._factorize(evenMajorSpacing); - for (i = 0, ilen = factors.length - 1; i < ilen; i++) { - factor = factors[i]; - if (factor > spacing) { - return factor; - } - } - return Math.max(spacing, 1); -} - -function getMajorIndices(ticks) { - var result = []; - var i, ilen; - for (i = 0, ilen = ticks.length; i < ilen; i++) { - if (ticks[i].major) { - result.push(i); - } - } - return result; -} - -function skipMajors(ticks, majorIndices, spacing) { - var count = 0; - var next = majorIndices[0]; - var i, tick; - - spacing = Math.ceil(spacing); - for (i = 0; i < ticks.length; i++) { - tick = ticks[i]; - if (i === next) { - tick._index = i; - count++; - next = majorIndices[count * spacing]; - } else { - delete tick.label; - } - } -} - -function skip(ticks, spacing, majorStart, majorEnd) { - var start = valueOrDefault$a(majorStart, 0); - var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length); - var count = 0; - var length, i, tick, next; - - spacing = Math.ceil(spacing); - if (majorEnd) { - length = majorEnd - majorStart; - spacing = length / Math.floor(length / spacing); - } - - next = start; - - while (next < 0) { - count++; - next = Math.round(start + count * spacing); - } - - for (i = Math.max(start, 0); i < end; i++) { - tick = ticks[i]; - if (i === next) { - tick._index = i; - count++; - next = Math.round(start + count * spacing); - } else { - delete tick.label; - } - } -} - -var Scale = core_element.extend({ - - zeroLineIndex: 0, - - /** - * Get the padding needed for the scale - * @method getPadding - * @private - * @returns {Padding} the necessary padding - */ - getPadding: function() { - var me = this; - return { - left: me.paddingLeft || 0, - top: me.paddingTop || 0, - right: me.paddingRight || 0, - bottom: me.paddingBottom || 0 - }; - }, - - /** - * Returns the scale tick objects ({label, major}) - * @since 2.7 - */ - getTicks: function() { - return this._ticks; - }, - - /** - * @private - */ - _getLabels: function() { - var data = this.chart.data; - return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; - }, - - // These methods are ordered by lifecyle. Utilities then follow. - // Any function defined here is inherited by all scale types. - // Any function can be extended by the scale type - - /** - * Provided for backward compatibility, not available anymore - * @function Chart.Scale.mergeTicksOptions - * @deprecated since version 2.8.0 - * @todo remove at version 3 - */ - mergeTicksOptions: function() { - // noop - }, - - beforeUpdate: function() { - helpers$1.callback(this.options.beforeUpdate, [this]); - }, - - /** - * @param {number} maxWidth - the max width in pixels - * @param {number} maxHeight - the max height in pixels - * @param {object} margins - the space between the edge of the other scales and edge of the chart - * This space comes from two sources: - * - padding - space that's required to show the labels at the edges of the scale - * - thickness of scales or legends in another orientation - */ - update: function(maxWidth, maxHeight, margins) { - var me = this; - var tickOpts = me.options.ticks; - var sampleSize = tickOpts.sampleSize; - var i, ilen, labels, ticks, samplingEnabled; - - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); - - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = helpers$1.extend({ - left: 0, - right: 0, - top: 0, - bottom: 0 - }, margins); - - me._ticks = null; - me.ticks = null; - me._labelSizes = null; - me._maxLabelLines = 0; - me.longestLabelWidth = 0; - me.longestTextCache = me.longestTextCache || {}; - me._gridLineItems = null; - me._labelItems = null; - - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - - // Data min/max - me.beforeDataLimits(); - me.determineDataLimits(); - me.afterDataLimits(); - - // Ticks - `this.ticks` is now DEPRECATED! - // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member - // and must not be accessed directly from outside this class. `this.ticks` being - // around for long time and not marked as private, we can't change its structure - // without unexpected breaking changes. If you need to access the scale ticks, - // use scale.getTicks() instead. - - me.beforeBuildTicks(); - - // New implementations should return an array of objects but for BACKWARD COMPAT, - // we still support no return (`this.ticks` internally set by calling this method). - ticks = me.buildTicks() || []; - - // Allow modification of ticks in callback. - ticks = me.afterBuildTicks(ticks) || ticks; - - // Ensure ticks contains ticks in new tick format - if ((!ticks || !ticks.length) && me.ticks) { - ticks = []; - for (i = 0, ilen = me.ticks.length; i < ilen; ++i) { - ticks.push({ - value: me.ticks[i], - major: false - }); - } - } - - me._ticks = ticks; - - // Compute tick rotation and fit using a sampled subset of labels - // We generally don't need to compute the size of every single label for determining scale size - samplingEnabled = sampleSize < ticks.length; - labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks); - - // _configure is called twice, once here, once from core.controller.updateLayout. - // Here we haven't been positioned yet, but dimensions are correct. - // Variables set in _configure are needed for calculateTickRotation, and - // it's ok that coordinates are not correct there, only dimensions matter. - me._configure(); - - // Tick Rotation - me.beforeCalculateTickRotation(); - me.calculateTickRotation(); - me.afterCalculateTickRotation(); - - me.beforeFit(); - me.fit(); - me.afterFit(); - - // Auto-skip - me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks; - - if (samplingEnabled) { - // Generate labels using all non-skipped ticks - labels = me._convertTicksToLabels(me._ticksToDraw); - } - - me.ticks = labels; // BACKWARD COMPATIBILITY - - // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change! - - me.afterUpdate(); - - // TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused - // make maxWidth and maxHeight private - return me.minSize; - }, - - /** - * @private - */ - _configure: function() { - var me = this; - var reversePixels = me.options.ticks.reverse; - var startPixel, endPixel; - - if (me.isHorizontal()) { - startPixel = me.left; - endPixel = me.right; - } else { - startPixel = me.top; - endPixel = me.bottom; - // by default vertical scales are from bottom to top, so pixels are reversed - reversePixels = !reversePixels; - } - me._startPixel = startPixel; - me._endPixel = endPixel; - me._reversePixels = reversePixels; - me._length = endPixel - startPixel; - }, - - afterUpdate: function() { - helpers$1.callback(this.options.afterUpdate, [this]); - }, - - // - - beforeSetDimensions: function() { - helpers$1.callback(this.options.beforeSetDimensions, [this]); - }, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; - - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } - - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; - }, - afterSetDimensions: function() { - helpers$1.callback(this.options.afterSetDimensions, [this]); - }, - - // Data limits - beforeDataLimits: function() { - helpers$1.callback(this.options.beforeDataLimits, [this]); - }, - determineDataLimits: helpers$1.noop, - afterDataLimits: function() { - helpers$1.callback(this.options.afterDataLimits, [this]); - }, - - // - beforeBuildTicks: function() { - helpers$1.callback(this.options.beforeBuildTicks, [this]); - }, - buildTicks: helpers$1.noop, - afterBuildTicks: function(ticks) { - var me = this; - // ticks is empty for old axis implementations here - if (isArray(ticks) && ticks.length) { - return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]); - } - // Support old implementations (that modified `this.ticks` directly in buildTicks) - me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks; - return ticks; - }, - - beforeTickToLabelConversion: function() { - helpers$1.callback(this.options.beforeTickToLabelConversion, [this]); - }, - convertTicksToLabels: function() { - var me = this; - // Convert ticks to strings - var tickOpts = me.options.ticks; - me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); - }, - afterTickToLabelConversion: function() { - helpers$1.callback(this.options.afterTickToLabelConversion, [this]); - }, - - // - - beforeCalculateTickRotation: function() { - helpers$1.callback(this.options.beforeCalculateTickRotation, [this]); - }, - calculateTickRotation: function() { - var me = this; - var options = me.options; - var tickOpts = options.ticks; - var numTicks = me.getTicks().length; - var minRotation = tickOpts.minRotation || 0; - var maxRotation = tickOpts.maxRotation; - var labelRotation = minRotation; - var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal; - - if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) { - me.labelRotation = minRotation; - return; - } - - labelSizes = me._getLabelSizes(); - maxLabelWidth = labelSizes.widest.width; - maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset; - - // Estimate the width of each grid based on the canvas width, the maximum - // label width and the number of tick intervals - maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth); - tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1); - - // Allow 3 pixels x2 padding either side for label readability - if (maxLabelWidth + 6 > tickWidth) { - tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); - maxHeight = me.maxHeight - getTickMarkLength(options.gridLines) - - tickOpts.padding - getScaleLabelHeight(options.scaleLabel); - maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); - labelRotation = helpers$1.toDegrees(Math.min( - Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)), - Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal) - )); - labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); - } - - me.labelRotation = labelRotation; - }, - afterCalculateTickRotation: function() { - helpers$1.callback(this.options.afterCalculateTickRotation, [this]); - }, - - // - - beforeFit: function() { - helpers$1.callback(this.options.beforeFit, [this]); - }, - fit: function() { - var me = this; - // Reset - var minSize = me.minSize = { - width: 0, - height: 0 - }; - - var chart = me.chart; - var opts = me.options; - var tickOpts = opts.ticks; - var scaleLabelOpts = opts.scaleLabel; - var gridLineOpts = opts.gridLines; - var display = me._isVisible(); - var isBottom = opts.position === 'bottom'; - var isHorizontal = me.isHorizontal(); - - // Width - if (isHorizontal) { - minSize.width = me.maxWidth; - } else if (display) { - minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); - } - - // height - if (!isHorizontal) { - minSize.height = me.maxHeight; // fill all the height - } else if (display) { - minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); - } - - // Don't bother fitting the ticks if we are not showing the labels - if (tickOpts.display && display) { - var tickFonts = parseTickFontOptions(tickOpts); - var labelSizes = me._getLabelSizes(); - var firstLabelSize = labelSizes.first; - var lastLabelSize = labelSizes.last; - var widestLabelSize = labelSizes.widest; - var highestLabelSize = labelSizes.highest; - var lineSpace = tickFonts.minor.lineHeight * 0.4; - var tickPadding = tickOpts.padding; - - if (isHorizontal) { - // A horizontal axis is more constrained by the height. - var isRotated = me.labelRotation !== 0; - var angleRadians = helpers$1.toRadians(me.labelRotation); - var cosRotation = Math.cos(angleRadians); - var sinRotation = Math.sin(angleRadians); - - var labelHeight = sinRotation * widestLabelSize.width - + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0)) - + (isRotated ? 0 : lineSpace); // padding - - minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); - - var offsetLeft = me.getPixelForTick(0) - me.left; - var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1); - var paddingLeft, paddingRight; - - // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned - // which means that the right padding is dominated by the font height - if (isRotated) { - paddingLeft = isBottom ? - cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : - sinRotation * (firstLabelSize.height - firstLabelSize.offset); - paddingRight = isBottom ? - sinRotation * (lastLabelSize.height - lastLabelSize.offset) : - cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; - } else { - paddingLeft = firstLabelSize.width / 2; - paddingRight = lastLabelSize.width / 2; - } - - // Adjust padding taking into account changes in offsets - // and add 3 px to move away from canvas edges - me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3; - me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3; - } else { - // A vertical axis is more constrained by the width. Labels are the - // dominant factor here, so get that length first and account for padding - var labelWidth = tickOpts.mirror ? 0 : - // use lineSpace for consistency with horizontal axis - // tickPadding is not implemented for horizontal - widestLabelSize.width + tickPadding + lineSpace; - - minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); - - me.paddingTop = firstLabelSize.height / 2; - me.paddingBottom = lastLabelSize.height / 2; - } - } - - me.handleMargins(); - - if (isHorizontal) { - me.width = me._length = chart.width - me.margins.left - me.margins.right; - me.height = minSize.height; - } else { - me.width = minSize.width; - me.height = me._length = chart.height - me.margins.top - me.margins.bottom; - } - }, - - /** - * Handle margins and padding interactions - * @private - */ - handleMargins: function() { - var me = this; - if (me.margins) { - me.margins.left = Math.max(me.paddingLeft, me.margins.left); - me.margins.top = Math.max(me.paddingTop, me.margins.top); - me.margins.right = Math.max(me.paddingRight, me.margins.right); - me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom); - } - }, - - afterFit: function() { - helpers$1.callback(this.options.afterFit, [this]); - }, - - // Shared Methods - isHorizontal: function() { - var pos = this.options.position; - return pos === 'top' || pos === 'bottom'; - }, - isFullWidth: function() { - return this.options.fullWidth; - }, - - // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not - getRightValue: function(rawValue) { - // Null and undefined values first - if (isNullOrUndef(rawValue)) { - return NaN; - } - // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values - if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) { - return NaN; - } - - // If it is in fact an object, dive in one more level - if (rawValue) { - if (this.isHorizontal()) { - if (rawValue.x !== undefined) { - return this.getRightValue(rawValue.x); - } - } else if (rawValue.y !== undefined) { - return this.getRightValue(rawValue.y); - } - } - - // Value is good, return it - return rawValue; - }, - - _convertTicksToLabels: function(ticks) { - var me = this; - var labels, i, ilen; - - me.ticks = ticks.map(function(tick) { - return tick.value; - }); - - me.beforeTickToLabelConversion(); - - // New implementations should return the formatted tick labels but for BACKWARD - // COMPAT, we still support no return (`this.ticks` internally changed by calling - // this method and supposed to contain only string values). - labels = me.convertTicksToLabels(ticks) || me.ticks; - - me.afterTickToLabelConversion(); - - // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - ticks[i].label = labels[i]; - } - - return labels; - }, - - /** - * @private - */ - _getLabelSizes: function() { - var me = this; - var labelSizes = me._labelSizes; - - if (!labelSizes) { - me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache); - me.longestLabelWidth = labelSizes.widest.width; - } - - return labelSizes; - }, - - /** - * @private - */ - _parseValue: function(value) { - var start, end, min, max; - - if (isArray(value)) { - start = +this.getRightValue(value[0]); - end = +this.getRightValue(value[1]); - min = Math.min(start, end); - max = Math.max(start, end); - } else { - value = +this.getRightValue(value); - start = undefined; - end = value; - min = value; - max = value; - } - - return { - min: min, - max: max, - start: start, - end: end - }; - }, - - /** - * @private - */ - _getScaleLabel: function(rawValue) { - var v = this._parseValue(rawValue); - if (v.start !== undefined) { - return '[' + v.start + ', ' + v.end + ']'; - } - - return +this.getRightValue(rawValue); - }, - - /** - * Used to get the value to display in the tooltip for the data at the given index - * @param index - * @param datasetIndex - */ - getLabelForIndex: helpers$1.noop, - - /** - * Returns the location of the given data point. Value can either be an index or a numerical value - * The coordinate (0, 0) is at the upper-left corner of the canvas - * @param value - * @param index - * @param datasetIndex - */ - getPixelForValue: helpers$1.noop, - - /** - * Used to get the data value from a given pixel. This is the inverse of getPixelForValue - * The coordinate (0, 0) is at the upper-left corner of the canvas - * @param pixel - */ - getValueForPixel: helpers$1.noop, - - /** - * Returns the location of the tick at the given index - * The coordinate (0, 0) is at the upper-left corner of the canvas - */ - getPixelForTick: function(index) { - var me = this; - var offset = me.options.offset; - var numTicks = me._ticks.length; - var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1); - - return index < 0 || index > numTicks - 1 - ? null - : me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0)); - }, - - /** - * Utility for getting the pixel location of a percentage of scale - * The coordinate (0, 0) is at the upper-left corner of the canvas - */ - getPixelForDecimal: function(decimal) { - var me = this; - - if (me._reversePixels) { - decimal = 1 - decimal; - } - - return me._startPixel + decimal * me._length; - }, - - getDecimalForPixel: function(pixel) { - var decimal = (pixel - this._startPixel) / this._length; - return this._reversePixels ? 1 - decimal : decimal; - }, - - /** - * Returns the pixel for the minimum chart value - * The coordinate (0, 0) is at the upper-left corner of the canvas - */ - getBasePixel: function() { - return this.getPixelForValue(this.getBaseValue()); - }, - - getBaseValue: function() { - var me = this; - var min = me.min; - var max = me.max; - - return me.beginAtZero ? 0 : - min < 0 && max < 0 ? max : - min > 0 && max > 0 ? min : - 0; - }, - - /** - * Returns a subset of ticks to be plotted to avoid overlapping labels. - * @private - */ - _autoSkip: function(ticks) { - var me = this; - var tickOpts = me.options.ticks; - var axisLength = me._length; - var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1; - var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; - var numMajorIndices = majorIndices.length; - var first = majorIndices[0]; - var last = majorIndices[numMajorIndices - 1]; - var i, ilen, spacing, avgMajorSpacing; - - // If there are too many major ticks to display them all - if (numMajorIndices > ticksLimit) { - skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit); - return nonSkipped(ticks); - } - - spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit); - - if (numMajorIndices > 0) { - for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) { - skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]); - } - avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null; - skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); - skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); - return nonSkipped(ticks); - } - skip(ticks, spacing); - return nonSkipped(ticks); - }, - - /** - * @private - */ - _tickSize: function() { - var me = this; - var optionTicks = me.options.ticks; - - // Calculate space needed by label in axis direction. - var rot = helpers$1.toRadians(me.labelRotation); - var cos = Math.abs(Math.cos(rot)); - var sin = Math.abs(Math.sin(rot)); - - var labelSizes = me._getLabelSizes(); - var padding = optionTicks.autoSkipPadding || 0; - var w = labelSizes ? labelSizes.widest.width + padding : 0; - var h = labelSizes ? labelSizes.highest.height + padding : 0; - - // Calculate space needed for 1 tick in axis direction. - return me.isHorizontal() - ? h * cos > w * sin ? w / cos : h / sin - : h * sin < w * cos ? h / cos : w / sin; - }, - - /** - * @private - */ - _isVisible: function() { - var me = this; - var chart = me.chart; - var display = me.options.display; - var i, ilen, meta; - - if (display !== 'auto') { - return !!display; - } - - // When 'auto', the scale is visible if at least one associated dataset is visible. - for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { - if (chart.isDatasetVisible(i)) { - meta = chart.getDatasetMeta(i); - if (meta.xAxisID === me.id || meta.yAxisID === me.id) { - return true; - } - } - } - - return false; - }, - - /** - * @private - */ - _computeGridLineItems: function(chartArea) { - var me = this; - var chart = me.chart; - var options = me.options; - var gridLines = options.gridLines; - var position = options.position; - var offsetGridLines = gridLines.offsetGridLines; - var isHorizontal = me.isHorizontal(); - var ticks = me._ticksToDraw; - var ticksLength = ticks.length + (offsetGridLines ? 1 : 0); - - var tl = getTickMarkLength(gridLines); - var items = []; - var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; - var axisHalfWidth = axisWidth / 2; - var alignPixel = helpers$1._alignPixel; - var alignBorderValue = function(pixel) { - return alignPixel(chart, pixel, axisWidth); - }; - var borderValue, i, tick, lineValue, alignedLineValue; - var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset; - - if (position === 'top') { - borderValue = alignBorderValue(me.bottom); - ty1 = me.bottom - tl; - ty2 = borderValue - axisHalfWidth; - y1 = alignBorderValue(chartArea.top) + axisHalfWidth; - y2 = chartArea.bottom; - } else if (position === 'bottom') { - borderValue = alignBorderValue(me.top); - y1 = chartArea.top; - y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; - ty1 = borderValue + axisHalfWidth; - ty2 = me.top + tl; - } else if (position === 'left') { - borderValue = alignBorderValue(me.right); - tx1 = me.right - tl; - tx2 = borderValue - axisHalfWidth; - x1 = alignBorderValue(chartArea.left) + axisHalfWidth; - x2 = chartArea.right; - } else { - borderValue = alignBorderValue(me.left); - x1 = chartArea.left; - x2 = alignBorderValue(chartArea.right) - axisHalfWidth; - tx1 = borderValue + axisHalfWidth; - tx2 = me.left + tl; - } - - for (i = 0; i < ticksLength; ++i) { - tick = ticks[i] || {}; - - // autoskipper skipped this tick (#4635) - if (isNullOrUndef(tick.label) && i < ticks.length) { - continue; - } - - if (i === me.zeroLineIndex && options.offset === offsetGridLines) { - // Draw the first index specially - lineWidth = gridLines.zeroLineWidth; - lineColor = gridLines.zeroLineColor; - borderDash = gridLines.zeroLineBorderDash || []; - borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0; - } else { - lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1); - lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)'); - borderDash = gridLines.borderDash || []; - borderDashOffset = gridLines.borderDashOffset || 0.0; - } - - lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines); - - // Skip if the pixel is out of the range - if (lineValue === undefined) { - continue; - } - - alignedLineValue = alignPixel(chart, lineValue, lineWidth); - - if (isHorizontal) { - tx1 = tx2 = x1 = x2 = alignedLineValue; - } else { - ty1 = ty2 = y1 = y2 = alignedLineValue; - } - - items.push({ - tx1: tx1, - ty1: ty1, - tx2: tx2, - ty2: ty2, - x1: x1, - y1: y1, - x2: x2, - y2: y2, - width: lineWidth, - color: lineColor, - borderDash: borderDash, - borderDashOffset: borderDashOffset, - }); - } - - items.ticksLength = ticksLength; - items.borderValue = borderValue; - - return items; - }, - - /** - * @private - */ - _computeLabelItems: function() { - var me = this; - var options = me.options; - var optionTicks = options.ticks; - var position = options.position; - var isMirrored = optionTicks.mirror; - var isHorizontal = me.isHorizontal(); - var ticks = me._ticksToDraw; - var fonts = parseTickFontOptions(optionTicks); - var tickPadding = optionTicks.padding; - var tl = getTickMarkLength(options.gridLines); - var rotation = -helpers$1.toRadians(me.labelRotation); - var items = []; - var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; - - if (position === 'top') { - y = me.bottom - tl - tickPadding; - textAlign = !rotation ? 'center' : 'left'; - } else if (position === 'bottom') { - y = me.top + tl + tickPadding; - textAlign = !rotation ? 'center' : 'right'; - } else if (position === 'left') { - x = me.right - (isMirrored ? 0 : tl) - tickPadding; - textAlign = isMirrored ? 'left' : 'right'; - } else { - x = me.left + (isMirrored ? 0 : tl) + tickPadding; - textAlign = isMirrored ? 'right' : 'left'; - } - - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - tick = ticks[i]; - label = tick.label; - - // autoskipper skipped this tick (#4635) - if (isNullOrUndef(label)) { - continue; - } - - pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset; - font = tick.major ? fonts.major : fonts.minor; - lineHeight = font.lineHeight; - lineCount = isArray(label) ? label.length : 1; - - if (isHorizontal) { - x = pixel; - textOffset = position === 'top' - ? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight - : (!rotation ? 0.5 : 0) * lineHeight; - } else { - y = pixel; - textOffset = (1 - lineCount) * lineHeight / 2; - } - - items.push({ - x: x, - y: y, - rotation: rotation, - label: label, - font: font, - textOffset: textOffset, - textAlign: textAlign - }); - } - - return items; - }, - - /** - * @private - */ - _drawGrid: function(chartArea) { - var me = this; - var gridLines = me.options.gridLines; - - if (!gridLines.display) { - return; - } - - var ctx = me.ctx; - var chart = me.chart; - var alignPixel = helpers$1._alignPixel; - var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; - var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea)); - var width, color, i, ilen, item; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - item = items[i]; - width = item.width; - color = item.color; - - if (width && color) { - ctx.save(); - ctx.lineWidth = width; - ctx.strokeStyle = color; - if (ctx.setLineDash) { - ctx.setLineDash(item.borderDash); - ctx.lineDashOffset = item.borderDashOffset; - } - - ctx.beginPath(); - - if (gridLines.drawTicks) { - ctx.moveTo(item.tx1, item.ty1); - ctx.lineTo(item.tx2, item.ty2); - } - - if (gridLines.drawOnChartArea) { - ctx.moveTo(item.x1, item.y1); - ctx.lineTo(item.x2, item.y2); - } - - ctx.stroke(); - ctx.restore(); - } - } - - if (axisWidth) { - // Draw the line at the edge of the axis - var firstLineWidth = axisWidth; - var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1); - var borderValue = items.borderValue; - var x1, x2, y1, y2; - - if (me.isHorizontal()) { - x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2; - x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2; - y1 = y2 = borderValue; - } else { - y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2; - y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2; - x1 = x2 = borderValue; - } - - ctx.lineWidth = axisWidth; - ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0); - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - } - }, - - /** - * @private - */ - _drawLabels: function() { - var me = this; - var optionTicks = me.options.ticks; - - if (!optionTicks.display) { - return; - } - - var ctx = me.ctx; - var items = me._labelItems || (me._labelItems = me._computeLabelItems()); - var i, j, ilen, jlen, item, tickFont, label, y; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - item = items[i]; - tickFont = item.font; - - // Make sure we draw text in the correct color and font - ctx.save(); - ctx.translate(item.x, item.y); - ctx.rotate(item.rotation); - ctx.font = tickFont.string; - ctx.fillStyle = tickFont.color; - ctx.textBaseline = 'middle'; - ctx.textAlign = item.textAlign; - - label = item.label; - y = item.textOffset; - if (isArray(label)) { - for (j = 0, jlen = label.length; j < jlen; ++j) { - // We just make sure the multiline element is a string here.. - ctx.fillText('' + label[j], 0, y); - y += tickFont.lineHeight; - } - } else { - ctx.fillText(label, 0, y); - } - ctx.restore(); - } - }, - - /** - * @private - */ - _drawTitle: function() { - var me = this; - var ctx = me.ctx; - var options = me.options; - var scaleLabel = options.scaleLabel; - - if (!scaleLabel.display) { - return; - } - - var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor); - var scaleLabelFont = helpers$1.options._parseFont(scaleLabel); - var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding); - var halfLineHeight = scaleLabelFont.lineHeight / 2; - var position = options.position; - var rotation = 0; - var scaleLabelX, scaleLabelY; - - if (me.isHorizontal()) { - scaleLabelX = me.left + me.width / 2; // midpoint of the width - scaleLabelY = position === 'bottom' - ? me.bottom - halfLineHeight - scaleLabelPadding.bottom - : me.top + halfLineHeight + scaleLabelPadding.top; - } else { - var isLeft = position === 'left'; - scaleLabelX = isLeft - ? me.left + halfLineHeight + scaleLabelPadding.top - : me.right - halfLineHeight - scaleLabelPadding.top; - scaleLabelY = me.top + me.height / 2; - rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; - } - - ctx.save(); - ctx.translate(scaleLabelX, scaleLabelY); - ctx.rotate(rotation); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillStyle = scaleLabelFontColor; // render in correct colour - ctx.font = scaleLabelFont.string; - ctx.fillText(scaleLabel.labelString, 0, 0); - ctx.restore(); - }, - - draw: function(chartArea) { - var me = this; - - if (!me._isVisible()) { - return; - } - - me._drawGrid(chartArea); - me._drawTitle(); - me._drawLabels(); - }, - - /** - * @private - */ - _layers: function() { - var me = this; - var opts = me.options; - var tz = opts.ticks && opts.ticks.z || 0; - var gz = opts.gridLines && opts.gridLines.z || 0; - - if (!me._isVisible() || tz === gz || me.draw !== me._draw) { - // backward compatibility: draw has been overridden by custom scale - return [{ - z: tz, - draw: function() { - me.draw.apply(me, arguments); - } - }]; - } - - return [{ - z: gz, - draw: function() { - me._drawGrid.apply(me, arguments); - me._drawTitle.apply(me, arguments); - } - }, { - z: tz, - draw: function() { - me._drawLabels.apply(me, arguments); - } - }]; - }, - - /** - * @private - */ - _getMatchingVisibleMetas: function(type) { - var me = this; - var isHorizontal = me.isHorizontal(); - return me.chart._getSortedVisibleDatasetMetas() - .filter(function(meta) { - return (!type || meta.type === type) - && (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id); - }); - } -}); - -Scale.prototype._draw = Scale.prototype.draw; - -var core_scale = Scale; - -var isNullOrUndef$1 = helpers$1.isNullOrUndef; - -var defaultConfig = { - position: 'bottom' -}; - -var scale_category = core_scale.extend({ - determineDataLimits: function() { - var me = this; - var labels = me._getLabels(); - var ticksOpts = me.options.ticks; - var min = ticksOpts.min; - var max = ticksOpts.max; - var minIndex = 0; - var maxIndex = labels.length - 1; - var findIndex; - - if (min !== undefined) { - // user specified min value - findIndex = labels.indexOf(min); - if (findIndex >= 0) { - minIndex = findIndex; - } - } - - if (max !== undefined) { - // user specified max value - findIndex = labels.indexOf(max); - if (findIndex >= 0) { - maxIndex = findIndex; - } - } - - me.minIndex = minIndex; - me.maxIndex = maxIndex; - me.min = labels[minIndex]; - me.max = labels[maxIndex]; - }, - - buildTicks: function() { - var me = this; - var labels = me._getLabels(); - var minIndex = me.minIndex; - var maxIndex = me.maxIndex; - - // If we are viewing some subset of labels, slice the original array - me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1); - }, - - getLabelForIndex: function(index, datasetIndex) { - var me = this; - var chart = me.chart; - - if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) { - return me.getRightValue(chart.data.datasets[datasetIndex].data[index]); - } - - return me._getLabels()[index]; - }, - - _configure: function() { - var me = this; - var offset = me.options.offset; - var ticks = me.ticks; - - core_scale.prototype._configure.call(me); - - if (!me.isHorizontal()) { - // For backward compatibility, vertical category scale reverse is inverted. - me._reversePixels = !me._reversePixels; - } - - if (!ticks) { - return; - } - - me._startValue = me.minIndex - (offset ? 0.5 : 0); - me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1); - }, - - // Used to get data value locations. Value can either be an index or a numerical value - getPixelForValue: function(value, index, datasetIndex) { - var me = this; - var valueCategory, labels, idx; - - if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) { - value = me.chart.data.datasets[datasetIndex].data[index]; - } - - // If value is a data object, then index is the index in the data array, - // not the index of the scale. We need to change that. - if (!isNullOrUndef$1(value)) { - valueCategory = me.isHorizontal() ? value.x : value.y; - } - if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { - labels = me._getLabels(); - value = helpers$1.valueOrDefault(valueCategory, value); - idx = labels.indexOf(value); - index = idx !== -1 ? idx : index; - if (isNaN(index)) { - index = value; - } - } - return me.getPixelForDecimal((index - me._startValue) / me._valueRange); - }, - - getPixelForTick: function(index) { - var ticks = this.ticks; - return index < 0 || index > ticks.length - 1 - ? null - : this.getPixelForValue(ticks[index], index + this.minIndex); - }, - - getValueForPixel: function(pixel) { - var me = this; - var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange); - return Math.min(Math.max(value, 0), me.ticks.length - 1); - }, - - getBasePixel: function() { - return this.bottom; - } -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults = defaultConfig; -scale_category._defaults = _defaults; - -var noop = helpers$1.noop; -var isNullOrUndef$2 = helpers$1.isNullOrUndef; - -/** - * Generate a set of linear ticks - * @param generationOptions the options used to generate the ticks - * @param dataRange the range of the data - * @returns {number[]} array of tick values - */ -function generateTicks(generationOptions, dataRange) { - var ticks = []; - // To get a "nice" value for the tick spacing, we will use the appropriately named - // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks - // for details. - - var MIN_SPACING = 1e-14; - var stepSize = generationOptions.stepSize; - var unit = stepSize || 1; - var maxNumSpaces = generationOptions.maxTicks - 1; - var min = generationOptions.min; - var max = generationOptions.max; - var precision = generationOptions.precision; - var rmin = dataRange.min; - var rmax = dataRange.max; - var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; - var factor, niceMin, niceMax, numSpaces; - - // Beyond MIN_SPACING floating point numbers being to lose precision - // such that we can't do the math necessary to generate ticks - if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) { - return [rmin, rmax]; - } - - numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); - if (numSpaces > maxNumSpaces) { - // If the calculated num of spaces exceeds maxNumSpaces, recalculate it - spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; - } - - if (stepSize || isNullOrUndef$2(precision)) { - // If a precision is not specified, calculate factor based on spacing - factor = Math.pow(10, helpers$1._decimalPlaces(spacing)); - } else { - // If the user specified a precision, round to that number of decimal places - factor = Math.pow(10, precision); - spacing = Math.ceil(spacing * factor) / factor; - } - - niceMin = Math.floor(rmin / spacing) * spacing; - niceMax = Math.ceil(rmax / spacing) * spacing; - - // If min, max and stepSize is set and they make an evenly spaced scale use it. - if (stepSize) { - // If very close to our whole number, use it. - if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) { - niceMin = min; - } - if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) { - niceMax = max; - } - } - - numSpaces = (niceMax - niceMin) / spacing; - // If very close to our rounded value, use it. - if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { - numSpaces = Math.round(numSpaces); - } else { - numSpaces = Math.ceil(numSpaces); - } - - niceMin = Math.round(niceMin * factor) / factor; - niceMax = Math.round(niceMax * factor) / factor; - ticks.push(isNullOrUndef$2(min) ? niceMin : min); - for (var j = 1; j < numSpaces; ++j) { - ticks.push(Math.round((niceMin + j * spacing) * factor) / factor); - } - ticks.push(isNullOrUndef$2(max) ? niceMax : max); - - return ticks; -} - -var scale_linearbase = core_scale.extend({ - getRightValue: function(value) { - if (typeof value === 'string') { - return +value; - } - return core_scale.prototype.getRightValue.call(this, value); - }, - - handleTickRangeOptions: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - - // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, - // do nothing since that would make the chart weird. If the user really wants a weird chart - // axis, they can manually override it - if (tickOpts.beginAtZero) { - var minSign = helpers$1.sign(me.min); - var maxSign = helpers$1.sign(me.max); - - if (minSign < 0 && maxSign < 0) { - // move the top up to 0 - me.max = 0; - } else if (minSign > 0 && maxSign > 0) { - // move the bottom down to 0 - me.min = 0; - } - } - - var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; - var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; - - if (tickOpts.min !== undefined) { - me.min = tickOpts.min; - } else if (tickOpts.suggestedMin !== undefined) { - if (me.min === null) { - me.min = tickOpts.suggestedMin; - } else { - me.min = Math.min(me.min, tickOpts.suggestedMin); - } - } - - if (tickOpts.max !== undefined) { - me.max = tickOpts.max; - } else if (tickOpts.suggestedMax !== undefined) { - if (me.max === null) { - me.max = tickOpts.suggestedMax; - } else { - me.max = Math.max(me.max, tickOpts.suggestedMax); - } - } - - if (setMin !== setMax) { - // We set the min or the max but not both. - // So ensure that our range is good - // Inverted or 0 length range can happen when - // ticks.min is set, and no datasets are visible - if (me.min >= me.max) { - if (setMin) { - me.max = me.min + 1; - } else { - me.min = me.max - 1; - } - } - } - - if (me.min === me.max) { - me.max++; - - if (!tickOpts.beginAtZero) { - me.min--; - } - } - }, - - getTickLimit: function() { - var me = this; - var tickOpts = me.options.ticks; - var stepSize = tickOpts.stepSize; - var maxTicksLimit = tickOpts.maxTicksLimit; - var maxTicks; - - if (stepSize) { - maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; - } else { - maxTicks = me._computeTickLimit(); - maxTicksLimit = maxTicksLimit || 11; - } - - if (maxTicksLimit) { - maxTicks = Math.min(maxTicksLimit, maxTicks); - } - - return maxTicks; - }, - - _computeTickLimit: function() { - return Number.POSITIVE_INFINITY; - }, - - handleDirectionalChanges: noop, - - buildTicks: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - - // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph. Make sure we always have at least 2 ticks - var maxTicks = me.getTickLimit(); - maxTicks = Math.max(2, maxTicks); - - var numericGeneratorOptions = { - maxTicks: maxTicks, - min: tickOpts.min, - max: tickOpts.max, - precision: tickOpts.precision, - stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) - }; - var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); - - me.handleDirectionalChanges(); - - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers$1.max(ticks); - me.min = helpers$1.min(ticks); - - if (tickOpts.reverse) { - ticks.reverse(); - - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - }, - - convertTicksToLabels: function() { - var me = this; - me.ticksAsNumbers = me.ticks.slice(); - me.zeroLineIndex = me.ticks.indexOf(0); - - core_scale.prototype.convertTicksToLabels.call(me); - }, - - _configure: function() { - var me = this; - var ticks = me.getTicks(); - var start = me.min; - var end = me.max; - var offset; - - core_scale.prototype._configure.call(me); - - if (me.options.offset && ticks.length) { - offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; - start -= offset; - end += offset; - } - me._startValue = start; - me._endValue = end; - me._valueRange = end - start; - } -}); - -var defaultConfig$1 = { - position: 'left', - ticks: { - callback: core_ticks.formatters.linear - } -}; - -var DEFAULT_MIN = 0; -var DEFAULT_MAX = 1; - -function getOrCreateStack(stacks, stacked, meta) { - var key = [ - meta.type, - // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined - stacked === undefined && meta.stack === undefined ? meta.index : '', - meta.stack - ].join('.'); - - if (stacks[key] === undefined) { - stacks[key] = { - pos: [], - neg: [] - }; - } - - return stacks[key]; -} - -function stackData(scale, stacks, meta, data) { - var opts = scale.options; - var stacked = opts.stacked; - var stack = getOrCreateStack(stacks, stacked, meta); - var pos = stack.pos; - var neg = stack.neg; - var ilen = data.length; - var i, value; - - for (i = 0; i < ilen; ++i) { - value = scale._parseValue(data[i]); - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { - continue; - } - - pos[i] = pos[i] || 0; - neg[i] = neg[i] || 0; - - if (opts.relativePoints) { - pos[i] = 100; - } else if (value.min < 0 || value.max < 0) { - neg[i] += value.min; - } else { - pos[i] += value.max; - } - } -} - -function updateMinMax(scale, meta, data) { - var ilen = data.length; - var i, value; - - for (i = 0; i < ilen; ++i) { - value = scale._parseValue(data[i]); - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { - continue; - } - - scale.min = Math.min(scale.min, value.min); - scale.max = Math.max(scale.max, value.max); - } -} - -var scale_linear = scale_linearbase.extend({ - determineDataLimits: function() { - var me = this; - var opts = me.options; - var chart = me.chart; - var datasets = chart.data.datasets; - var metasets = me._getMatchingVisibleMetas(); - var hasStacks = opts.stacked; - var stacks = {}; - var ilen = metasets.length; - var i, meta, data, values; - - me.min = Number.POSITIVE_INFINITY; - me.max = Number.NEGATIVE_INFINITY; - - if (hasStacks === undefined) { - for (i = 0; !hasStacks && i < ilen; ++i) { - meta = metasets[i]; - hasStacks = meta.stack !== undefined; - } - } - - for (i = 0; i < ilen; ++i) { - meta = metasets[i]; - data = datasets[meta.index].data; - if (hasStacks) { - stackData(me, stacks, meta, data); - } else { - updateMinMax(me, meta, data); - } - } - - helpers$1.each(stacks, function(stackValues) { - values = stackValues.pos.concat(stackValues.neg); - me.min = Math.min(me.min, helpers$1.min(values)); - me.max = Math.max(me.max, helpers$1.max(values)); - }); - - me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; - me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; - - // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - me.handleTickRangeOptions(); - }, - - // Returns the maximum number of ticks based on the scale dimension - _computeTickLimit: function() { - var me = this; - var tickFont; - - if (me.isHorizontal()) { - return Math.ceil(me.width / 40); - } - tickFont = helpers$1.options._parseFont(me.options.ticks); - return Math.ceil(me.height / tickFont.lineHeight); - }, - - // Called after the ticks are built. We need - handleDirectionalChanges: function() { - if (!this.isHorizontal()) { - // We are in a vertical orientation. The top value is the highest. So reverse the array - this.ticks.reverse(); - } - }, - - getLabelForIndex: function(index, datasetIndex) { - return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); - }, - - // Utils - getPixelForValue: function(value) { - var me = this; - return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange); - }, - - getValueForPixel: function(pixel) { - return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; - }, - - getPixelForTick: function(index) { - var ticks = this.ticksAsNumbers; - if (index < 0 || index > ticks.length - 1) { - return null; - } - return this.getPixelForValue(ticks[index]); - } -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults$1 = defaultConfig$1; -scale_linear._defaults = _defaults$1; - -var valueOrDefault$b = helpers$1.valueOrDefault; -var log10 = helpers$1.math.log10; - -/** - * Generate a set of logarithmic ticks - * @param generationOptions the options used to generate the ticks - * @param dataRange the range of the data - * @returns {number[]} array of tick values - */ -function generateTicks$1(generationOptions, dataRange) { - var ticks = []; - - var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); - - var endExp = Math.floor(log10(dataRange.max)); - var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); - var exp, significand; - - if (tickVal === 0) { - exp = Math.floor(log10(dataRange.minNotZero)); - significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); - - ticks.push(tickVal); - tickVal = significand * Math.pow(10, exp); - } else { - exp = Math.floor(log10(tickVal)); - significand = Math.floor(tickVal / Math.pow(10, exp)); - } - var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; - - do { - ticks.push(tickVal); - - ++significand; - if (significand === 10) { - significand = 1; - ++exp; - precision = exp >= 0 ? 1 : precision; - } - - tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; - } while (exp < endExp || (exp === endExp && significand < endSignificand)); - - var lastTick = valueOrDefault$b(generationOptions.max, tickVal); - ticks.push(lastTick); - - return ticks; -} - -var defaultConfig$2 = { - position: 'left', - - // label settings - ticks: { - callback: core_ticks.formatters.logarithmic - } -}; - -// TODO(v3): change this to positiveOrDefault -function nonNegativeOrDefault(value, defaultValue) { - return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue; -} - -var scale_logarithmic = core_scale.extend({ - determineDataLimits: function() { - var me = this; - var opts = me.options; - var chart = me.chart; - var datasets = chart.data.datasets; - var isHorizontal = me.isHorizontal(); - function IDMatches(meta) { - return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; - } - var datasetIndex, meta, value, data, i, ilen; - - // Calculate Range - me.min = Number.POSITIVE_INFINITY; - me.max = Number.NEGATIVE_INFINITY; - me.minNotZero = Number.POSITIVE_INFINITY; - - var hasStacks = opts.stacked; - if (hasStacks === undefined) { - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && - meta.stack !== undefined) { - hasStacks = true; - break; - } - } - } - - if (opts.stacked || hasStacks) { - var valuesPerStack = {}; - - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - meta = chart.getDatasetMeta(datasetIndex); - var key = [ - meta.type, - // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined - ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), - meta.stack - ].join('.'); - - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - if (valuesPerStack[key] === undefined) { - valuesPerStack[key] = []; - } - - data = datasets[datasetIndex].data; - for (i = 0, ilen = data.length; i < ilen; i++) { - var values = valuesPerStack[key]; - value = me._parseValue(data[i]); - // invalid, hidden and negative values are ignored - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { - continue; - } - values[i] = values[i] || 0; - values[i] += value.max; - } - } - } - - helpers$1.each(valuesPerStack, function(valuesForType) { - if (valuesForType.length > 0) { - var minVal = helpers$1.min(valuesForType); - var maxVal = helpers$1.max(valuesForType); - me.min = Math.min(me.min, minVal); - me.max = Math.max(me.max, maxVal); - } - }); - - } else { - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - data = datasets[datasetIndex].data; - for (i = 0, ilen = data.length; i < ilen; i++) { - value = me._parseValue(data[i]); - // invalid, hidden and negative values are ignored - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { - continue; - } - - me.min = Math.min(value.min, me.min); - me.max = Math.max(value.max, me.max); - - if (value.min !== 0) { - me.minNotZero = Math.min(value.min, me.minNotZero); - } - } - } - } - } - - me.min = helpers$1.isFinite(me.min) ? me.min : null; - me.max = helpers$1.isFinite(me.max) ? me.max : null; - me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null; - - // Common base implementation to handle ticks.min, ticks.max - this.handleTickRangeOptions(); - }, - - handleTickRangeOptions: function() { - var me = this; - var tickOpts = me.options.ticks; - var DEFAULT_MIN = 1; - var DEFAULT_MAX = 10; - - me.min = nonNegativeOrDefault(tickOpts.min, me.min); - me.max = nonNegativeOrDefault(tickOpts.max, me.max); - - if (me.min === me.max) { - if (me.min !== 0 && me.min !== null) { - me.min = Math.pow(10, Math.floor(log10(me.min)) - 1); - me.max = Math.pow(10, Math.floor(log10(me.max)) + 1); - } else { - me.min = DEFAULT_MIN; - me.max = DEFAULT_MAX; - } - } - if (me.min === null) { - me.min = Math.pow(10, Math.floor(log10(me.max)) - 1); - } - if (me.max === null) { - me.max = me.min !== 0 - ? Math.pow(10, Math.floor(log10(me.min)) + 1) - : DEFAULT_MAX; - } - if (me.minNotZero === null) { - if (me.min > 0) { - me.minNotZero = me.min; - } else if (me.max < 1) { - me.minNotZero = Math.pow(10, Math.floor(log10(me.max))); - } else { - me.minNotZero = DEFAULT_MIN; - } - } - }, - - buildTicks: function() { - var me = this; - var tickOpts = me.options.ticks; - var reverse = !me.isHorizontal(); - - var generationOptions = { - min: nonNegativeOrDefault(tickOpts.min), - max: nonNegativeOrDefault(tickOpts.max) - }; - var ticks = me.ticks = generateTicks$1(generationOptions, me); - - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers$1.max(ticks); - me.min = helpers$1.min(ticks); - - if (tickOpts.reverse) { - reverse = !reverse; - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - if (reverse) { - ticks.reverse(); - } - }, - - convertTicksToLabels: function() { - this.tickValues = this.ticks.slice(); - - core_scale.prototype.convertTicksToLabels.call(this); - }, - - // Get the correct tooltip label - getLabelForIndex: function(index, datasetIndex) { - return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); - }, - - getPixelForTick: function(index) { - var ticks = this.tickValues; - if (index < 0 || index > ticks.length - 1) { - return null; - } - return this.getPixelForValue(ticks[index]); - }, - - /** - * Returns the value of the first tick. - * @param {number} value - The minimum not zero value. - * @return {number} The first tick value. - * @private - */ - _getFirstTickValue: function(value) { - var exp = Math.floor(log10(value)); - var significand = Math.floor(value / Math.pow(10, exp)); - - return significand * Math.pow(10, exp); - }, - - _configure: function() { - var me = this; - var start = me.min; - var offset = 0; - - core_scale.prototype._configure.call(me); - - if (start === 0) { - start = me._getFirstTickValue(me.minNotZero); - offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length; - } - - me._startValue = log10(start); - me._valueOffset = offset; - me._valueRange = (log10(me.max) - log10(start)) / (1 - offset); - }, - - getPixelForValue: function(value) { - var me = this; - var decimal = 0; - - value = +me.getRightValue(value); - - if (value > me.min && value > 0) { - decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset; - } - return me.getPixelForDecimal(decimal); - }, - - getValueForPixel: function(pixel) { - var me = this; - var decimal = me.getDecimalForPixel(pixel); - return decimal === 0 && me.min === 0 - ? 0 - : Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange); - } -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults$2 = defaultConfig$2; -scale_logarithmic._defaults = _defaults$2; - -var valueOrDefault$c = helpers$1.valueOrDefault; -var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault; -var resolve$4 = helpers$1.options.resolve; - -var defaultConfig$3 = { - display: true, - - // Boolean - Whether to animate scaling the chart from the centre - animate: true, - position: 'chartArea', - - angleLines: { - display: true, - color: 'rgba(0,0,0,0.1)', - lineWidth: 1, - borderDash: [], - borderDashOffset: 0.0 - }, - - gridLines: { - circular: false - }, - - // label settings - ticks: { - // Boolean - Show a backdrop to the scale label - showLabelBackdrop: true, - - // String - The colour of the label backdrop - backdropColor: 'rgba(255,255,255,0.75)', - - // Number - The backdrop padding above & below the label in pixels - backdropPaddingY: 2, - - // Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2, - - callback: core_ticks.formatters.linear - }, - - pointLabels: { - // Boolean - if true, show point labels - display: true, - - // Number - Point label font size in pixels - fontSize: 10, - - // Function - Used to convert point labels - callback: function(label) { - return label; - } - } -}; - -function getTickBackdropHeight(opts) { - var tickOpts = opts.ticks; - - if (tickOpts.display && opts.display) { - return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; - } - return 0; -} - -function measureLabelSize(ctx, lineHeight, label) { - if (helpers$1.isArray(label)) { - return { - w: helpers$1.longestText(ctx, ctx.font, label), - h: label.length * lineHeight - }; - } - - return { - w: ctx.measureText(label).width, - h: lineHeight - }; -} - -function determineLimits(angle, pos, size, min, max) { - if (angle === min || angle === max) { - return { - start: pos - (size / 2), - end: pos + (size / 2) - }; - } else if (angle < min || angle > max) { - return { - start: pos - size, - end: pos - }; - } - - return { - start: pos, - end: pos + size - }; -} - -/** - * Helper function to fit a radial linear scale with point labels - */ -function fitWithPointLabels(scale) { - - // Right, this is really confusing and there is a lot of maths going on here - // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 - // - // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif - // - // Solution: - // - // We assume the radius of the polygon is half the size of the canvas at first - // at each index we check if the text overlaps. - // - // Where it does, we store that angle and that index. - // - // After finding the largest index and angle we calculate how much we need to remove - // from the shape radius to move the point inwards by that x. - // - // We average the left and right distances to get the maximum shape radius that can fit in the box - // along with labels. - // - // Once we have that, we can find the centre point for the chart, by taking the x text protrusion - // on each side, removing that from the size, halving it and adding the left x protrusion width. - // - // This will mean we have a shape fitted to the canvas, as large as it can be with the labels - // and position it in the most space efficient manner - // - // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif - - var plFont = helpers$1.options._parseFont(scale.options.pointLabels); - - // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. - // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points - var furthestLimits = { - l: 0, - r: scale.width, - t: 0, - b: scale.height - scale.paddingTop - }; - var furthestAngles = {}; - var i, textSize, pointPosition; - - scale.ctx.font = plFont.string; - scale._pointLabelSizes = []; - - var valueCount = scale.chart.data.labels.length; - for (i = 0; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); - textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]); - scale._pointLabelSizes[i] = textSize; - - // Add quarter circle to make degree 0 mean top of circle - var angleRadians = scale.getIndexAngle(i); - var angle = helpers$1.toDegrees(angleRadians) % 360; - var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); - var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); - - if (hLimits.start < furthestLimits.l) { - furthestLimits.l = hLimits.start; - furthestAngles.l = angleRadians; - } - - if (hLimits.end > furthestLimits.r) { - furthestLimits.r = hLimits.end; - furthestAngles.r = angleRadians; - } - - if (vLimits.start < furthestLimits.t) { - furthestLimits.t = vLimits.start; - furthestAngles.t = angleRadians; - } - - if (vLimits.end > furthestLimits.b) { - furthestLimits.b = vLimits.end; - furthestAngles.b = angleRadians; - } - } - - scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); -} - -function getTextAlignForAngle(angle) { - if (angle === 0 || angle === 180) { - return 'center'; - } else if (angle < 180) { - return 'left'; - } - - return 'right'; -} - -function fillText(ctx, text, position, lineHeight) { - var y = position.y + lineHeight / 2; - var i, ilen; - - if (helpers$1.isArray(text)) { - for (i = 0, ilen = text.length; i < ilen; ++i) { - ctx.fillText(text[i], position.x, y); - y += lineHeight; - } - } else { - ctx.fillText(text, position.x, y); - } -} - -function adjustPointPositionForLabelHeight(angle, textSize, position) { - if (angle === 90 || angle === 270) { - position.y -= (textSize.h / 2); - } else if (angle > 270 || angle < 90) { - position.y -= textSize.h; - } -} - -function drawPointLabels(scale) { - var ctx = scale.ctx; - var opts = scale.options; - var pointLabelOpts = opts.pointLabels; - var tickBackdropHeight = getTickBackdropHeight(opts); - var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); - var plFont = helpers$1.options._parseFont(pointLabelOpts); - - ctx.save(); - - ctx.font = plFont.string; - ctx.textBaseline = 'middle'; - - for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) { - // Extra pixels out for some label spacing - var extra = (i === 0 ? tickBackdropHeight / 2 : 0); - var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); - - // Keep this in loop since we may support array properties here - var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor); - ctx.fillStyle = pointLabelFontColor; - - var angleRadians = scale.getIndexAngle(i); - var angle = helpers$1.toDegrees(angleRadians); - ctx.textAlign = getTextAlignForAngle(angle); - adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); - fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight); - } - ctx.restore(); -} - -function drawRadiusLine(scale, gridLineOpts, radius, index) { - var ctx = scale.ctx; - var circular = gridLineOpts.circular; - var valueCount = scale.chart.data.labels.length; - var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1); - var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1); - var pointPosition; - - if ((!circular && !valueCount) || !lineColor || !lineWidth) { - return; - } - - ctx.save(); - ctx.strokeStyle = lineColor; - ctx.lineWidth = lineWidth; - if (ctx.setLineDash) { - ctx.setLineDash(gridLineOpts.borderDash || []); - ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; - } - - ctx.beginPath(); - if (circular) { - // Draw circular arcs between the points - ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); - } else { - // Draw straight lines connecting each index - pointPosition = scale.getPointPosition(0, radius); - ctx.moveTo(pointPosition.x, pointPosition.y); - - for (var i = 1; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, radius); - ctx.lineTo(pointPosition.x, pointPosition.y); - } - } - ctx.closePath(); - ctx.stroke(); - ctx.restore(); -} - -function numberOrZero(param) { - return helpers$1.isNumber(param) ? param : 0; -} - -var scale_radialLinear = scale_linearbase.extend({ - setDimensions: function() { - var me = this; - - // Set the unconstrained dimension before label rotation - me.width = me.maxWidth; - me.height = me.maxHeight; - me.paddingTop = getTickBackdropHeight(me.options) / 2; - me.xCenter = Math.floor(me.width / 2); - me.yCenter = Math.floor((me.height - me.paddingTop) / 2); - me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; - }, - - determineDataLimits: function() { - var me = this; - var chart = me.chart; - var min = Number.POSITIVE_INFINITY; - var max = Number.NEGATIVE_INFINITY; - - helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) { - if (chart.isDatasetVisible(datasetIndex)) { - var meta = chart.getDatasetMeta(datasetIndex); - - helpers$1.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } - - min = Math.min(value, min); - max = Math.max(value, max); - }); - } - }); - - me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); - me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); - - // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - me.handleTickRangeOptions(); - }, - - // Returns the maximum number of ticks based on the scale dimension - _computeTickLimit: function() { - return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); - }, - - convertTicksToLabels: function() { - var me = this; - - scale_linearbase.prototype.convertTicksToLabels.call(me); - - // Point labels - me.pointLabels = me.chart.data.labels.map(function() { - var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me); - return label || label === 0 ? label : ''; - }); - }, - - getLabelForIndex: function(index, datasetIndex) { - return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); - }, - - fit: function() { - var me = this; - var opts = me.options; - - if (opts.display && opts.pointLabels.display) { - fitWithPointLabels(me); - } else { - me.setCenterPoint(0, 0, 0, 0); - } - }, - - /** - * Set radius reductions and determine new radius and center point - * @private - */ - setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { - var me = this; - var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); - var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); - var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); - var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); - - radiusReductionLeft = numberOrZero(radiusReductionLeft); - radiusReductionRight = numberOrZero(radiusReductionRight); - radiusReductionTop = numberOrZero(radiusReductionTop); - radiusReductionBottom = numberOrZero(radiusReductionBottom); - - me.drawingArea = Math.min( - Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), - Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); - me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); - }, - - setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { - var me = this; - var maxRight = me.width - rightMovement - me.drawingArea; - var maxLeft = leftMovement + me.drawingArea; - var maxTop = topMovement + me.drawingArea; - var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; - - me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); - me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); - }, - - getIndexAngle: function(index) { - var chart = this.chart; - var angleMultiplier = 360 / chart.data.labels.length; - var options = chart.options || {}; - var startAngle = options.startAngle || 0; - - // Start from the top instead of right, so remove a quarter of the circle - var angle = (index * angleMultiplier + startAngle) % 360; - - return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360; - }, - - getDistanceFromCenterForValue: function(value) { - var me = this; - - if (helpers$1.isNullOrUndef(value)) { - return NaN; - } - - // Take into account half font size + the yPadding of the top value - var scalingFactor = me.drawingArea / (me.max - me.min); - if (me.options.ticks.reverse) { - return (me.max - value) * scalingFactor; - } - return (value - me.min) * scalingFactor; - }, - - getPointPosition: function(index, distanceFromCenter) { - var me = this; - var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); - return { - x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter, - y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter - }; - }, - - getPointPositionForValue: function(index, value) { - return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); - }, - - getBasePosition: function(index) { - var me = this; - var min = me.min; - var max = me.max; - - return me.getPointPositionForValue(index || 0, - me.beginAtZero ? 0 : - min < 0 && max < 0 ? max : - min > 0 && max > 0 ? min : - 0); - }, - - /** - * @private - */ - _drawGrid: function() { - var me = this; - var ctx = me.ctx; - var opts = me.options; - var gridLineOpts = opts.gridLines; - var angleLineOpts = opts.angleLines; - var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth); - var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color); - var i, offset, position; - - if (opts.pointLabels.display) { - drawPointLabels(me); - } - - if (gridLineOpts.display) { - helpers$1.each(me.ticks, function(label, index) { - if (index !== 0) { - offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); - drawRadiusLine(me, gridLineOpts, offset, index); - } - }); - } - - if (angleLineOpts.display && lineWidth && lineColor) { - ctx.save(); - ctx.lineWidth = lineWidth; - ctx.strokeStyle = lineColor; - if (ctx.setLineDash) { - ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []])); - ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]); - } - - for (i = me.chart.data.labels.length - 1; i >= 0; i--) { - offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max); - position = me.getPointPosition(i, offset); - ctx.beginPath(); - ctx.moveTo(me.xCenter, me.yCenter); - ctx.lineTo(position.x, position.y); - ctx.stroke(); - } - - ctx.restore(); - } - }, - - /** - * @private - */ - _drawLabels: function() { - var me = this; - var ctx = me.ctx; - var opts = me.options; - var tickOpts = opts.ticks; - - if (!tickOpts.display) { - return; - } - - var startAngle = me.getIndexAngle(0); - var tickFont = helpers$1.options._parseFont(tickOpts); - var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor); - var offset, width; - - ctx.save(); - ctx.font = tickFont.string; - ctx.translate(me.xCenter, me.yCenter); - ctx.rotate(startAngle); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - - helpers$1.each(me.ticks, function(label, index) { - if (index === 0 && !tickOpts.reverse) { - return; - } - - offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); - - if (tickOpts.showLabelBackdrop) { - width = ctx.measureText(label).width; - ctx.fillStyle = tickOpts.backdropColor; - - ctx.fillRect( - -width / 2 - tickOpts.backdropPaddingX, - -offset - tickFont.size / 2 - tickOpts.backdropPaddingY, - width + tickOpts.backdropPaddingX * 2, - tickFont.size + tickOpts.backdropPaddingY * 2 - ); - } - - ctx.fillStyle = tickFontColor; - ctx.fillText(label, 0, -offset); - }); - - ctx.restore(); - }, - - /** - * @private - */ - _drawTitle: helpers$1.noop -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults$3 = defaultConfig$3; -scale_radialLinear._defaults = _defaults$3; - -var deprecated$1 = helpers$1._deprecated; -var resolve$5 = helpers$1.options.resolve; -var valueOrDefault$d = helpers$1.valueOrDefault; - -// Integer constants are from the ES6 spec. -var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; -var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; - -var INTERVALS = { - millisecond: { - common: true, - size: 1, - steps: 1000 - }, - second: { - common: true, - size: 1000, - steps: 60 - }, - minute: { - common: true, - size: 60000, - steps: 60 - }, - hour: { - common: true, - size: 3600000, - steps: 24 - }, - day: { - common: true, - size: 86400000, - steps: 30 - }, - week: { - common: false, - size: 604800000, - steps: 4 - }, - month: { - common: true, - size: 2.628e9, - steps: 12 - }, - quarter: { - common: false, - size: 7.884e9, - steps: 4 - }, - year: { - common: true, - size: 3.154e10 - } -}; - -var UNITS = Object.keys(INTERVALS); - -function sorter(a, b) { - return a - b; -} - -function arrayUnique(items) { - var hash = {}; - var out = []; - var i, ilen, item; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - item = items[i]; - if (!hash[item]) { - hash[item] = true; - out.push(item); - } - } - - return out; -} - -function getMin(options) { - return helpers$1.valueOrDefault(options.time.min, options.ticks.min); -} - -function getMax(options) { - return helpers$1.valueOrDefault(options.time.max, options.ticks.max); -} - -/** - * Returns an array of {time, pos} objects used to interpolate a specific `time` or position - * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is - * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other - * extremity (left + width or top + height). Note that it would be more optimized to directly - * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need - * to create the lookup table. The table ALWAYS contains at least two items: min and max. - * - * @param {number[]} timestamps - timestamps sorted from lowest to highest. - * @param {string} distribution - If 'linear', timestamps will be spread linearly along the min - * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. - * If 'series', timestamps will be positioned at the same distance from each other. In this - * case, only timestamps that break the time linearity are registered, meaning that in the - * best case, all timestamps are linear, the table contains only min and max. - */ -function buildLookupTable(timestamps, min, max, distribution) { - if (distribution === 'linear' || !timestamps.length) { - return [ - {time: min, pos: 0}, - {time: max, pos: 1} - ]; - } - - var table = []; - var items = [min]; - var i, ilen, prev, curr, next; - - for (i = 0, ilen = timestamps.length; i < ilen; ++i) { - curr = timestamps[i]; - if (curr > min && curr < max) { - items.push(curr); - } - } - - items.push(max); - - for (i = 0, ilen = items.length; i < ilen; ++i) { - next = items[i + 1]; - prev = items[i - 1]; - curr = items[i]; - - // only add points that breaks the scale linearity - if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { - table.push({time: curr, pos: i / (ilen - 1)}); - } - } - - return table; -} - -// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ -function lookup(table, key, value) { - var lo = 0; - var hi = table.length - 1; - var mid, i0, i1; - - while (lo >= 0 && lo <= hi) { - mid = (lo + hi) >> 1; - i0 = table[mid - 1] || null; - i1 = table[mid]; - - if (!i0) { - // given value is outside table (before first item) - return {lo: null, hi: i1}; - } else if (i1[key] < value) { - lo = mid + 1; - } else if (i0[key] > value) { - hi = mid - 1; - } else { - return {lo: i0, hi: i1}; - } - } - - // given value is outside table (after last item) - return {lo: i1, hi: null}; -} - -/** - * Linearly interpolates the given source `value` using the table items `skey` values and - * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') - * returns the position for a timestamp equal to 42. If value is out of bounds, values at - * index [0, 1] or [n - 1, n] are used for the interpolation. - */ -function interpolate$1(table, skey, sval, tkey) { - var range = lookup(table, skey, sval); - - // Note: the lookup table ALWAYS contains at least 2 items (min and max) - var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; - var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; - - var span = next[skey] - prev[skey]; - var ratio = span ? (sval - prev[skey]) / span : 0; - var offset = (next[tkey] - prev[tkey]) * ratio; - - return prev[tkey] + offset; -} - -function toTimestamp(scale, input) { - var adapter = scale._adapter; - var options = scale.options.time; - var parser = options.parser; - var format = parser || options.format; - var value = input; - - if (typeof parser === 'function') { - value = parser(value); - } - - // Only parse if its not a timestamp already - if (!helpers$1.isFinite(value)) { - value = typeof format === 'string' - ? adapter.parse(value, format) - : adapter.parse(value); - } - - if (value !== null) { - return +value; - } - - // Labels are in an incompatible format and no `parser` has been provided. - // The user might still use the deprecated `format` option for parsing. - if (!parser && typeof format === 'function') { - value = format(input); - - // `format` could return something else than a timestamp, if so, parse it - if (!helpers$1.isFinite(value)) { - value = adapter.parse(value); - } - } - - return value; -} - -function parse(scale, input) { - if (helpers$1.isNullOrUndef(input)) { - return null; - } - - var options = scale.options.time; - var value = toTimestamp(scale, scale.getRightValue(input)); - if (value === null) { - return value; - } - - if (options.round) { - value = +scale._adapter.startOf(value, options.round); - } - - return value; -} - -/** - * Figures out what unit results in an appropriate number of auto-generated ticks - */ -function determineUnitForAutoTicks(minUnit, min, max, capacity) { - var ilen = UNITS.length; - var i, interval, factor; - - for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { - interval = INTERVALS[UNITS[i]]; - factor = interval.steps ? interval.steps : MAX_INTEGER; - - if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { - return UNITS[i]; - } - } - - return UNITS[ilen - 1]; -} - -/** - * Figures out what unit to format a set of ticks with - */ -function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { - var i, unit; - - for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) { - unit = UNITS[i]; - if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { - return unit; - } - } - - return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; -} - -function determineMajorUnit(unit) { - for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { - if (INTERVALS[UNITS[i]].common) { - return UNITS[i]; - } - } -} - -/** - * Generates a maximum of `capacity` timestamps between min and max, rounded to the - * `minor` unit using the given scale time `options`. - * Important: this method can return ticks outside the min and max range, it's the - * responsibility of the calling code to clamp values if needed. - */ -function generate(scale, min, max, capacity) { - var adapter = scale._adapter; - var options = scale.options; - var timeOpts = options.time; - var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); - var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]); - var weekday = minor === 'week' ? timeOpts.isoWeekday : false; - var first = min; - var ticks = []; - var time; - - // For 'week' unit, handle the first day of week option - if (weekday) { - first = +adapter.startOf(first, 'isoWeek', weekday); - } - - // Align first ticks on unit - first = +adapter.startOf(first, weekday ? 'day' : minor); - - // Prevent browser from freezing in case user options request millions of milliseconds - if (adapter.diff(max, min, minor) > 100000 * stepSize) { - throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor; - } - - for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) { - ticks.push(time); - } - - if (time === max || options.bounds === 'ticks') { - ticks.push(time); - } - - return ticks; -} - -/** - * Returns the start and end offsets from edges in the form of {start, end} - * where each value is a relative width to the scale and ranges between 0 and 1. - * They add extra margins on the both sides by scaling down the original scale. - * Offsets are added when the `offset` option is true. - */ -function computeOffsets(table, ticks, min, max, options) { - var start = 0; - var end = 0; - var first, last; - - if (options.offset && ticks.length) { - first = interpolate$1(table, 'time', ticks[0], 'pos'); - if (ticks.length === 1) { - start = 1 - first; - } else { - start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2; - } - last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos'); - if (ticks.length === 1) { - end = last; - } else { - end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2; - } - } - - return {start: start, end: end, factor: 1 / (start + 1 + end)}; -} - -function setMajorTicks(scale, ticks, map, majorUnit) { - var adapter = scale._adapter; - var first = +adapter.startOf(ticks[0].value, majorUnit); - var last = ticks[ticks.length - 1].value; - var major, index; - - for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) { - index = map[major]; - if (index >= 0) { - ticks[index].major = true; - } - } - return ticks; -} - -function ticksFromTimestamps(scale, values, majorUnit) { - var ticks = []; - var map = {}; - var ilen = values.length; - var i, value; - - for (i = 0; i < ilen; ++i) { - value = values[i]; - map[value] = i; - - ticks.push({ - value: value, - major: false - }); - } - - // We set the major ticks separately from the above loop because calling startOf for every tick - // is expensive when there is a large number of ticks - return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit); -} - -var defaultConfig$4 = { - position: 'bottom', - - /** - * Data distribution along the scale: - * - 'linear': data are spread according to their time (distances can vary), - * - 'series': data are spread at the same distance from each other. - * @see https://github.com/chartjs/Chart.js/pull/4507 - * @since 2.7.0 - */ - distribution: 'linear', - - /** - * Scale boundary strategy (bypassed by min/max time options) - * - `data`: make sure data are fully visible, ticks outside are removed - * - `ticks`: make sure ticks are fully visible, data outside are truncated - * @see https://github.com/chartjs/Chart.js/pull/4556 - * @since 2.7.0 - */ - bounds: 'data', - - adapters: {}, - time: { - parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment - unit: false, // false == automatic or override with week, month, year, etc. - round: false, // none, or override with week, month, year, etc. - displayFormat: false, // DEPRECATED - isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/ - minUnit: 'millisecond', - displayFormats: {} - }, - ticks: { - autoSkip: false, - - /** - * Ticks generation input values: - * - 'auto': generates "optimal" ticks based on scale size and time options. - * - 'data': generates ticks from data (including labels from data {t|x|y} objects). - * - 'labels': generates ticks from user given `data.labels` values ONLY. - * @see https://github.com/chartjs/Chart.js/pull/4507 - * @since 2.7.0 - */ - source: 'auto', - - major: { - enabled: false - } - } -}; - -var scale_time = core_scale.extend({ - initialize: function() { - this.mergeTicksOptions(); - core_scale.prototype.initialize.call(this); - }, - - update: function() { - var me = this; - var options = me.options; - var time = options.time || (options.time = {}); - var adapter = me._adapter = new core_adapters._date(options.adapters.date); - - // DEPRECATIONS: output a message only one time per update - deprecated$1('time scale', time.format, 'time.format', 'time.parser'); - deprecated$1('time scale', time.min, 'time.min', 'ticks.min'); - deprecated$1('time scale', time.max, 'time.max', 'ticks.max'); - - // Backward compatibility: before introducing adapter, `displayFormats` was - // supposed to contain *all* unit/string pairs but this can't be resolved - // when loading the scale (adapters are loaded afterward), so let's populate - // missing formats on update - helpers$1.mergeIf(time.displayFormats, adapter.formats()); - - return core_scale.prototype.update.apply(me, arguments); - }, - - /** - * Allows data to be referenced via 't' attribute - */ - getRightValue: function(rawValue) { - if (rawValue && rawValue.t !== undefined) { - rawValue = rawValue.t; - } - return core_scale.prototype.getRightValue.call(this, rawValue); - }, - - determineDataLimits: function() { - var me = this; - var chart = me.chart; - var adapter = me._adapter; - var options = me.options; - var unit = options.time.unit || 'day'; - var min = MAX_INTEGER; - var max = MIN_INTEGER; - var timestamps = []; - var datasets = []; - var labels = []; - var i, j, ilen, jlen, data, timestamp, labelsAdded; - var dataLabels = me._getLabels(); - - for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { - labels.push(parse(me, dataLabels[i])); - } - - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - if (chart.isDatasetVisible(i)) { - data = chart.data.datasets[i].data; - - // Let's consider that all data have the same format. - if (helpers$1.isObject(data[0])) { - datasets[i] = []; - - for (j = 0, jlen = data.length; j < jlen; ++j) { - timestamp = parse(me, data[j]); - timestamps.push(timestamp); - datasets[i][j] = timestamp; - } - } else { - datasets[i] = labels.slice(0); - if (!labelsAdded) { - timestamps = timestamps.concat(labels); - labelsAdded = true; - } - } - } else { - datasets[i] = []; - } - } - - if (labels.length) { - min = Math.min(min, labels[0]); - max = Math.max(max, labels[labels.length - 1]); - } - - if (timestamps.length) { - timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter); - min = Math.min(min, timestamps[0]); - max = Math.max(max, timestamps[timestamps.length - 1]); - } - - min = parse(me, getMin(options)) || min; - max = parse(me, getMax(options)) || max; - - // In case there is no valid min/max, set limits based on unit time option - min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min; - max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max; - - // Make sure that max is strictly higher than min (required by the lookup table) - me.min = Math.min(min, max); - me.max = Math.max(min + 1, max); - - // PRIVATE - me._table = []; - me._timestamps = { - data: timestamps, - datasets: datasets, - labels: labels - }; - }, - - buildTicks: function() { - var me = this; - var min = me.min; - var max = me.max; - var options = me.options; - var tickOpts = options.ticks; - var timeOpts = options.time; - var timestamps = me._timestamps; - var ticks = []; - var capacity = me.getLabelCapacity(min); - var source = tickOpts.source; - var distribution = options.distribution; - var i, ilen, timestamp; - - if (source === 'data' || (source === 'auto' && distribution === 'series')) { - timestamps = timestamps.data; - } else if (source === 'labels') { - timestamps = timestamps.labels; - } else { - timestamps = generate(me, min, max, capacity); - } - - if (options.bounds === 'ticks' && timestamps.length) { - min = timestamps[0]; - max = timestamps[timestamps.length - 1]; - } - - // Enforce limits with user min/max options - min = parse(me, getMin(options)) || min; - max = parse(me, getMax(options)) || max; - - // Remove ticks outside the min/max range - for (i = 0, ilen = timestamps.length; i < ilen; ++i) { - timestamp = timestamps[i]; - if (timestamp >= min && timestamp <= max) { - ticks.push(timestamp); - } - } - - me.min = min; - me.max = max; - - // PRIVATE - // determineUnitForFormatting relies on the number of ticks so we don't use it when - // autoSkip is enabled because we don't yet know what the final number of ticks will be - me._unit = timeOpts.unit || (tickOpts.autoSkip - ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity) - : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max)); - me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined - : determineMajorUnit(me._unit); - me._table = buildLookupTable(me._timestamps.data, min, max, distribution); - me._offsets = computeOffsets(me._table, ticks, min, max, options); - - if (tickOpts.reverse) { - ticks.reverse(); - } - - return ticksFromTimestamps(me, ticks, me._majorUnit); - }, - - getLabelForIndex: function(index, datasetIndex) { - var me = this; - var adapter = me._adapter; - var data = me.chart.data; - var timeOpts = me.options.time; - var label = data.labels && index < data.labels.length ? data.labels[index] : ''; - var value = data.datasets[datasetIndex].data[index]; - - if (helpers$1.isObject(value)) { - label = me.getRightValue(value); - } - if (timeOpts.tooltipFormat) { - return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat); - } - if (typeof label === 'string') { - return label; - } - return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime); - }, - - /** - * Function to format an individual tick mark - * @private - */ - tickFormatFunction: function(time, index, ticks, format) { - var me = this; - var adapter = me._adapter; - var options = me.options; - var formats = options.time.displayFormats; - var minorFormat = formats[me._unit]; - var majorUnit = me._majorUnit; - var majorFormat = formats[majorUnit]; - var tick = ticks[index]; - var tickOpts = options.ticks; - var major = majorUnit && majorFormat && tick && tick.major; - var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat); - var nestedTickOpts = major ? tickOpts.major : tickOpts.minor; - var formatter = resolve$5([ - nestedTickOpts.callback, - nestedTickOpts.userCallback, - tickOpts.callback, - tickOpts.userCallback - ]); - - return formatter ? formatter(label, index, ticks) : label; - }, - - convertTicksToLabels: function(ticks) { - var labels = []; - var i, ilen; - - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - labels.push(this.tickFormatFunction(ticks[i].value, i, ticks)); - } - - return labels; - }, - - /** - * @private - */ - getPixelForOffset: function(time) { - var me = this; - var offsets = me._offsets; - var pos = interpolate$1(me._table, 'time', time, 'pos'); - return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); - }, - - getPixelForValue: function(value, index, datasetIndex) { - var me = this; - var time = null; - - if (index !== undefined && datasetIndex !== undefined) { - time = me._timestamps.datasets[datasetIndex][index]; - } - - if (time === null) { - time = parse(me, value); - } - - if (time !== null) { - return me.getPixelForOffset(time); - } - }, - - getPixelForTick: function(index) { - var ticks = this.getTicks(); - return index >= 0 && index < ticks.length ? - this.getPixelForOffset(ticks[index].value) : - null; - }, - - getValueForPixel: function(pixel) { - var me = this; - var offsets = me._offsets; - var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; - var time = interpolate$1(me._table, 'pos', pos, 'time'); - - // DEPRECATION, we should return time directly - return me._adapter._create(time); - }, - - /** - * @private - */ - _getLabelSize: function(label) { - var me = this; - var ticksOpts = me.options.ticks; - var tickLabelWidth = me.ctx.measureText(label).width; - var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); - var cosRotation = Math.cos(angle); - var sinRotation = Math.sin(angle); - var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize); - - return { - w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation), - h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation) - }; - }, - - /** - * Crude approximation of what the label width might be - * @private - */ - getLabelWidth: function(label) { - return this._getLabelSize(label).w; - }, - - /** - * @private - */ - getLabelCapacity: function(exampleTime) { - var me = this; - var timeOpts = me.options.time; - var displayFormats = timeOpts.displayFormats; - - // pick the longest format (milliseconds) for guestimation - var format = displayFormats[timeOpts.unit] || displayFormats.millisecond; - var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format); - var size = me._getLabelSize(exampleLabel); - var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h); - - if (me.options.offset) { - capacity--; - } - - return capacity > 0 ? capacity : 1; - } -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults$4 = defaultConfig$4; -scale_time._defaults = _defaults$4; - -var scales = { - category: scale_category, - linear: scale_linear, - logarithmic: scale_logarithmic, - radialLinear: scale_radialLinear, - time: scale_time -}; - -var moment = createCommonjsModule(function (module, exports) { -(function (global, factory) { - module.exports = factory() ; -}(commonjsGlobal, (function () { - var hookCallback; - - function hooks () { - return hookCallback.apply(null, arguments); - } - - // This is done to register the method called with moment() - // without creating circular dependencies. - function setHookCallback (callback) { - hookCallback = callback; - } - - function isArray(input) { - return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; - } - - function isObject(input) { - // IE8 will treat undefined and null as object if it wasn't for - // input != null - return input != null && Object.prototype.toString.call(input) === '[object Object]'; - } - - function isObjectEmpty(obj) { - if (Object.getOwnPropertyNames) { - return (Object.getOwnPropertyNames(obj).length === 0); - } else { - var k; - for (k in obj) { - if (obj.hasOwnProperty(k)) { - return false; - } - } - return true; - } - } - - function isUndefined(input) { - return input === void 0; - } - - function isNumber(input) { - return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]'; - } - - function isDate(input) { - return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; - } - - function map(arr, fn) { - var res = [], i; - for (i = 0; i < arr.length; ++i) { - res.push(fn(arr[i], i)); - } - return res; - } - - function hasOwnProp(a, b) { - return Object.prototype.hasOwnProperty.call(a, b); - } - - function extend(a, b) { - for (var i in b) { - if (hasOwnProp(b, i)) { - a[i] = b[i]; - } - } - - if (hasOwnProp(b, 'toString')) { - a.toString = b.toString; - } - - if (hasOwnProp(b, 'valueOf')) { - a.valueOf = b.valueOf; - } - - return a; - } - - function createUTC (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, true).utc(); - } - - function defaultParsingFlags() { - // We need to deep clone this object. - return { - empty : false, - unusedTokens : [], - unusedInput : [], - overflow : -2, - charsLeftOver : 0, - nullInput : false, - invalidMonth : null, - invalidFormat : false, - userInvalidated : false, - iso : false, - parsedDateParts : [], - meridiem : null, - rfc2822 : false, - weekdayMismatch : false - }; - } - - function getParsingFlags(m) { - if (m._pf == null) { - m._pf = defaultParsingFlags(); - } - return m._pf; - } - - var some; - if (Array.prototype.some) { - some = Array.prototype.some; - } else { - some = function (fun) { - var t = Object(this); - var len = t.length >>> 0; - - for (var i = 0; i < len; i++) { - if (i in t && fun.call(this, t[i], i, t)) { - return true; - } - } - - return false; - }; - } - - function isValid(m) { - if (m._isValid == null) { - var flags = getParsingFlags(m); - var parsedParts = some.call(flags.parsedDateParts, function (i) { - return i != null; - }); - var isNowValid = !isNaN(m._d.getTime()) && - flags.overflow < 0 && - !flags.empty && - !flags.invalidMonth && - !flags.invalidWeekday && - !flags.weekdayMismatch && - !flags.nullInput && - !flags.invalidFormat && - !flags.userInvalidated && - (!flags.meridiem || (flags.meridiem && parsedParts)); - - if (m._strict) { - isNowValid = isNowValid && - flags.charsLeftOver === 0 && - flags.unusedTokens.length === 0 && - flags.bigHour === undefined; - } - - if (Object.isFrozen == null || !Object.isFrozen(m)) { - m._isValid = isNowValid; - } - else { - return isNowValid; - } - } - return m._isValid; - } - - function createInvalid (flags) { - var m = createUTC(NaN); - if (flags != null) { - extend(getParsingFlags(m), flags); - } - else { - getParsingFlags(m).userInvalidated = true; - } - - return m; - } - - // Plugins that add properties should also add the key here (null value), - // so we can properly clone ourselves. - var momentProperties = hooks.momentProperties = []; - - function copyConfig(to, from) { - var i, prop, val; - - if (!isUndefined(from._isAMomentObject)) { - to._isAMomentObject = from._isAMomentObject; - } - if (!isUndefined(from._i)) { - to._i = from._i; - } - if (!isUndefined(from._f)) { - to._f = from._f; - } - if (!isUndefined(from._l)) { - to._l = from._l; - } - if (!isUndefined(from._strict)) { - to._strict = from._strict; - } - if (!isUndefined(from._tzm)) { - to._tzm = from._tzm; - } - if (!isUndefined(from._isUTC)) { - to._isUTC = from._isUTC; - } - if (!isUndefined(from._offset)) { - to._offset = from._offset; - } - if (!isUndefined(from._pf)) { - to._pf = getParsingFlags(from); - } - if (!isUndefined(from._locale)) { - to._locale = from._locale; - } - - if (momentProperties.length > 0) { - for (i = 0; i < momentProperties.length; i++) { - prop = momentProperties[i]; - val = from[prop]; - if (!isUndefined(val)) { - to[prop] = val; - } - } - } - - return to; - } - - var updateInProgress = false; - - // Moment prototype object - function Moment(config) { - copyConfig(this, config); - this._d = new Date(config._d != null ? config._d.getTime() : NaN); - if (!this.isValid()) { - this._d = new Date(NaN); - } - // Prevent infinite loop in case updateOffset creates new moment - // objects. - if (updateInProgress === false) { - updateInProgress = true; - hooks.updateOffset(this); - updateInProgress = false; - } - } - - function isMoment (obj) { - return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); - } - - function absFloor (number) { - if (number < 0) { - // -0 -> 0 - return Math.ceil(number) || 0; - } else { - return Math.floor(number); - } - } - - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; - - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - value = absFloor(coercedNumber); - } - - return value; - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ((dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { - diffs++; - } - } - return diffs + lengthDiff; - } - - function warn(msg) { - if (hooks.suppressDeprecationWarnings === false && - (typeof console !== 'undefined') && console.warn) { - console.warn('Deprecation warning: ' + msg); - } - } - - function deprecate(msg, fn) { - var firstTime = true; - - return extend(function () { - if (hooks.deprecationHandler != null) { - hooks.deprecationHandler(null, msg); - } - if (firstTime) { - var args = []; - var arg; - for (var i = 0; i < arguments.length; i++) { - arg = ''; - if (typeof arguments[i] === 'object') { - arg += '\n[' + i + '] '; - for (var key in arguments[0]) { - arg += key + ': ' + arguments[0][key] + ', '; - } - arg = arg.slice(0, -2); // Remove trailing comma and space - } else { - arg = arguments[i]; - } - args.push(arg); - } - warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } - - var deprecations = {}; - - function deprecateSimple(name, msg) { - if (hooks.deprecationHandler != null) { - hooks.deprecationHandler(name, msg); - } - if (!deprecations[name]) { - warn(msg); - deprecations[name] = true; - } - } - - hooks.suppressDeprecationWarnings = false; - hooks.deprecationHandler = null; - - function isFunction(input) { - return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; - } - - function set (config) { - var prop, i; - for (i in config) { - prop = config[i]; - if (isFunction(prop)) { - this[i] = prop; - } else { - this['_' + i] = prop; - } - } - this._config = config; - // Lenient ordinal parsing accepts just a number in addition to - // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. - // TODO: Remove "ordinalParse" fallback in next major release. - this._dayOfMonthOrdinalParseLenient = new RegExp( - (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + - '|' + (/\d{1,2}/).source); - } - - function mergeConfigs(parentConfig, childConfig) { - var res = extend({}, parentConfig), prop; - for (prop in childConfig) { - if (hasOwnProp(childConfig, prop)) { - if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { - res[prop] = {}; - extend(res[prop], parentConfig[prop]); - extend(res[prop], childConfig[prop]); - } else if (childConfig[prop] != null) { - res[prop] = childConfig[prop]; - } else { - delete res[prop]; - } - } - } - for (prop in parentConfig) { - if (hasOwnProp(parentConfig, prop) && - !hasOwnProp(childConfig, prop) && - isObject(parentConfig[prop])) { - // make sure changes to properties don't modify parent config - res[prop] = extend({}, res[prop]); - } - } - return res; - } - - function Locale(config) { - if (config != null) { - this.set(config); - } - } - - var keys; - - if (Object.keys) { - keys = Object.keys; - } else { - keys = function (obj) { - var i, res = []; - for (i in obj) { - if (hasOwnProp(obj, i)) { - res.push(i); - } - } - return res; - }; - } - - var defaultCalendar = { - sameDay : '[Today at] LT', - nextDay : '[Tomorrow at] LT', - nextWeek : 'dddd [at] LT', - lastDay : '[Yesterday at] LT', - lastWeek : '[Last] dddd [at] LT', - sameElse : 'L' - }; - - function calendar (key, mom, now) { - var output = this._calendar[key] || this._calendar['sameElse']; - return isFunction(output) ? output.call(mom, now) : output; - } - - var defaultLongDateFormat = { - LTS : 'h:mm:ss A', - LT : 'h:mm A', - L : 'MM/DD/YYYY', - LL : 'MMMM D, YYYY', - LLL : 'MMMM D, YYYY h:mm A', - LLLL : 'dddd, MMMM D, YYYY h:mm A' - }; - - function longDateFormat (key) { - var format = this._longDateFormat[key], - formatUpper = this._longDateFormat[key.toUpperCase()]; - - if (format || !formatUpper) { - return format; - } - - this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { - return val.slice(1); - }); - - return this._longDateFormat[key]; - } - - var defaultInvalidDate = 'Invalid date'; - - function invalidDate () { - return this._invalidDate; - } - - var defaultOrdinal = '%d'; - var defaultDayOfMonthOrdinalParse = /\d{1,2}/; - - function ordinal (number) { - return this._ordinal.replace('%d', number); - } - - var defaultRelativeTime = { - future : 'in %s', - past : '%s ago', - s : 'a few seconds', - ss : '%d seconds', - m : 'a minute', - mm : '%d minutes', - h : 'an hour', - hh : '%d hours', - d : 'a day', - dd : '%d days', - M : 'a month', - MM : '%d months', - y : 'a year', - yy : '%d years' - }; - - function relativeTime (number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return (isFunction(output)) ? - output(number, withoutSuffix, string, isFuture) : - output.replace(/%d/i, number); - } - - function pastFuture (diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return isFunction(format) ? format(output) : format.replace(/%s/i, output); - } - - var aliases = {}; - - function addUnitAlias (unit, shorthand) { - var lowerCase = unit.toLowerCase(); - aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; - } - - function normalizeUnits(units) { - return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; - } - - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; - - for (prop in inputObject) { - if (hasOwnProp(inputObject, prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } - } - } - - return normalizedInput; - } - - var priorities = {}; - - function addUnitPriority(unit, priority) { - priorities[unit] = priority; - } - - function getPrioritizedUnits(unitsObj) { - var units = []; - for (var u in unitsObj) { - units.push({unit: u, priority: priorities[u]}); - } - units.sort(function (a, b) { - return a.priority - b.priority; - }); - return units; - } - - function zeroFill(number, targetLength, forceSign) { - var absNumber = '' + Math.abs(number), - zerosToFill = targetLength - absNumber.length, - sign = number >= 0; - return (sign ? (forceSign ? '+' : '') : '-') + - Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; - } - - var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; - - var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; - - var formatFunctions = {}; - - var formatTokenFunctions = {}; - - // token: 'M' - // padded: ['MM', 2] - // ordinal: 'Mo' - // callback: function () { this.month() + 1 } - function addFormatToken (token, padded, ordinal, callback) { - var func = callback; - if (typeof callback === 'string') { - func = function () { - return this[callback](); - }; - } - if (token) { - formatTokenFunctions[token] = func; - } - if (padded) { - formatTokenFunctions[padded[0]] = function () { - return zeroFill(func.apply(this, arguments), padded[1], padded[2]); - }; - } - if (ordinal) { - formatTokenFunctions[ordinal] = function () { - return this.localeData().ordinal(func.apply(this, arguments), token); - }; - } - } - - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); - } - - function makeFormatFunction(format) { - var array = format.match(formattingTokens), i, length; - - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } - - return function (mom) { - var output = '', i; - for (i = 0; i < length; i++) { - output += isFunction(array[i]) ? array[i].call(mom, format) : array[i]; - } - return output; - }; - } - - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); - } - - format = expandFormat(format, m.localeData()); - formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); - - return formatFunctions[format](m); - } - - function expandFormat(format, locale) { - var i = 5; - - function replaceLongDateFormatTokens(input) { - return locale.longDateFormat(input) || input; - } - - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); - localFormattingTokens.lastIndex = 0; - i -= 1; - } - - return format; - } - - var match1 = /\d/; // 0 - 9 - var match2 = /\d\d/; // 00 - 99 - var match3 = /\d{3}/; // 000 - 999 - var match4 = /\d{4}/; // 0000 - 9999 - var match6 = /[+-]?\d{6}/; // -999999 - 999999 - var match1to2 = /\d\d?/; // 0 - 99 - var match3to4 = /\d\d\d\d?/; // 999 - 9999 - var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 - var match1to3 = /\d{1,3}/; // 0 - 999 - var match1to4 = /\d{1,4}/; // 0 - 9999 - var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 - - var matchUnsigned = /\d+/; // 0 - inf - var matchSigned = /[+-]?\d+/; // -inf - inf - - var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z - var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z - - var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 - - // any word (or two) characters or numbers including two/three word month in arabic. - // includes scottish gaelic two word and hyphenated months - var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i; - - var regexes = {}; - - function addRegexToken (token, regex, strictRegex) { - regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { - return (isStrict && strictRegex) ? strictRegex : regex; - }; - } - - function getParseRegexForToken (token, config) { - if (!hasOwnProp(regexes, token)) { - return new RegExp(unescapeFormat(token)); - } - - return regexes[token](config._strict, config._locale); - } - - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function unescapeFormat(s) { - return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { - return p1 || p2 || p3 || p4; - })); - } - - function regexEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - } - - var tokens = {}; - - function addParseToken (token, callback) { - var i, func = callback; - if (typeof token === 'string') { - token = [token]; - } - if (isNumber(callback)) { - func = function (input, array) { - array[callback] = toInt(input); - }; - } - for (i = 0; i < token.length; i++) { - tokens[token[i]] = func; - } - } - - function addWeekParseToken (token, callback) { - addParseToken(token, function (input, array, config, token) { - config._w = config._w || {}; - callback(input, config._w, config, token); - }); - } - - function addTimeToArrayFromToken(token, input, config) { - if (input != null && hasOwnProp(tokens, token)) { - tokens[token](input, config._a, config, token); - } - } - - var YEAR = 0; - var MONTH = 1; - var DATE = 2; - var HOUR = 3; - var MINUTE = 4; - var SECOND = 5; - var MILLISECOND = 6; - var WEEK = 7; - var WEEKDAY = 8; - - // FORMATTING - - addFormatToken('Y', 0, 0, function () { - var y = this.year(); - return y <= 9999 ? '' + y : '+' + y; - }); - - addFormatToken(0, ['YY', 2], 0, function () { - return this.year() % 100; - }); - - addFormatToken(0, ['YYYY', 4], 0, 'year'); - addFormatToken(0, ['YYYYY', 5], 0, 'year'); - addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); - - // ALIASES - - addUnitAlias('year', 'y'); - - // PRIORITIES - - addUnitPriority('year', 1); - - // PARSING - - addRegexToken('Y', matchSigned); - addRegexToken('YY', match1to2, match2); - addRegexToken('YYYY', match1to4, match4); - addRegexToken('YYYYY', match1to6, match6); - addRegexToken('YYYYYY', match1to6, match6); - - addParseToken(['YYYYY', 'YYYYYY'], YEAR); - addParseToken('YYYY', function (input, array) { - array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); - }); - addParseToken('YY', function (input, array) { - array[YEAR] = hooks.parseTwoDigitYear(input); - }); - addParseToken('Y', function (input, array) { - array[YEAR] = parseInt(input, 10); - }); - - // HELPERS - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } - - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - - // HOOKS - - hooks.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; - - // MOMENTS - - var getSetYear = makeGetSet('FullYear', true); - - function getIsLeapYear () { - return isLeapYear(this.year()); - } - - function makeGetSet (unit, keepTime) { - return function (value) { - if (value != null) { - set$1(this, unit, value); - hooks.updateOffset(this, keepTime); - return this; - } else { - return get(this, unit); - } - }; - } - - function get (mom, unit) { - return mom.isValid() ? - mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; - } - - function set$1 (mom, unit, value) { - if (mom.isValid() && !isNaN(value)) { - if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) { - mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month())); - } - else { - mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); - } - } - } - - // MOMENTS - - function stringGet (units) { - units = normalizeUnits(units); - if (isFunction(this[units])) { - return this[units](); - } - return this; - } - - - function stringSet (units, value) { - if (typeof units === 'object') { - units = normalizeObjectUnits(units); - var prioritized = getPrioritizedUnits(units); - for (var i = 0; i < prioritized.length; i++) { - this[prioritized[i].unit](units[prioritized[i].unit]); - } - } else { - units = normalizeUnits(units); - if (isFunction(this[units])) { - return this[units](value); - } - } - return this; - } - - function mod(n, x) { - return ((n % x) + x) % x; - } - - var indexOf; - - if (Array.prototype.indexOf) { - indexOf = Array.prototype.indexOf; - } else { - indexOf = function (o) { - // I know - var i; - for (i = 0; i < this.length; ++i) { - if (this[i] === o) { - return i; - } - } - return -1; - }; - } - - function daysInMonth(year, month) { - if (isNaN(year) || isNaN(month)) { - return NaN; - } - var modMonth = mod(month, 12); - year += (month - modMonth) / 12; - return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2); - } - - // FORMATTING - - addFormatToken('M', ['MM', 2], 'Mo', function () { - return this.month() + 1; - }); - - addFormatToken('MMM', 0, 0, function (format) { - return this.localeData().monthsShort(this, format); - }); - - addFormatToken('MMMM', 0, 0, function (format) { - return this.localeData().months(this, format); - }); - - // ALIASES - - addUnitAlias('month', 'M'); - - // PRIORITY - - addUnitPriority('month', 8); - - // PARSING - - addRegexToken('M', match1to2); - addRegexToken('MM', match1to2, match2); - addRegexToken('MMM', function (isStrict, locale) { - return locale.monthsShortRegex(isStrict); - }); - addRegexToken('MMMM', function (isStrict, locale) { - return locale.monthsRegex(isStrict); - }); - - addParseToken(['M', 'MM'], function (input, array) { - array[MONTH] = toInt(input) - 1; - }); - - addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { - var month = config._locale.monthsParse(input, token, config._strict); - // if we didn't find a month name, mark the date as invalid. - if (month != null) { - array[MONTH] = month; - } else { - getParsingFlags(config).invalidMonth = input; - } - }); - - // LOCALES - - var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/; - var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); - function localeMonths (m, format) { - if (!m) { - return isArray(this._months) ? this._months : - this._months['standalone']; - } - return isArray(this._months) ? this._months[m.month()] : - this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; - } - - var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); - function localeMonthsShort (m, format) { - if (!m) { - return isArray(this._monthsShort) ? this._monthsShort : - this._monthsShort['standalone']; - } - return isArray(this._monthsShort) ? this._monthsShort[m.month()] : - this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; - } - - function handleStrictParse(monthName, format, strict) { - var i, ii, mom, llc = monthName.toLocaleLowerCase(); - if (!this._monthsParse) { - // this is not used - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - for (i = 0; i < 12; ++i) { - mom = createUTC([2000, i]); - this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); - this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); - } - } - - if (strict) { - if (format === 'MMM') { - ii = indexOf.call(this._shortMonthsParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._longMonthsParse, llc); - return ii !== -1 ? ii : null; - } - } else { - if (format === 'MMM') { - ii = indexOf.call(this._shortMonthsParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._longMonthsParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._longMonthsParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortMonthsParse, llc); - return ii !== -1 ? ii : null; - } - } - } - - function localeMonthsParse (monthName, format, strict) { - var i, mom, regex; - - if (this._monthsParseExact) { - return handleStrictParse.call(this, monthName, format, strict); - } - - if (!this._monthsParse) { - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - } - - // TODO: add sorting - // Sorting makes sure if one month (or abbr) is a prefix of another - // see sorting in computeMonthsParse - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = createUTC([2000, i]); - if (strict && !this._longMonthsParse[i]) { - this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); - this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); - } - if (!strict && !this._monthsParse[i]) { - regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { - return i; - } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { - return i; - } else if (!strict && this._monthsParse[i].test(monthName)) { - return i; - } - } - } - - // MOMENTS - - function setMonth (mom, value) { - var dayOfMonth; - - if (!mom.isValid()) { - // No op - return mom; - } - - if (typeof value === 'string') { - if (/^\d+$/.test(value)) { - value = toInt(value); - } else { - value = mom.localeData().monthsParse(value); - // TODO: Another silent failure? - if (!isNumber(value)) { - return mom; - } - } - } - - dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); - return mom; - } - - function getSetMonth (value) { - if (value != null) { - setMonth(this, value); - hooks.updateOffset(this, true); - return this; - } else { - return get(this, 'Month'); - } - } - - function getDaysInMonth () { - return daysInMonth(this.year(), this.month()); - } - - var defaultMonthsShortRegex = matchWord; - function monthsShortRegex (isStrict) { - if (this._monthsParseExact) { - if (!hasOwnProp(this, '_monthsRegex')) { - computeMonthsParse.call(this); - } - if (isStrict) { - return this._monthsShortStrictRegex; - } else { - return this._monthsShortRegex; - } - } else { - if (!hasOwnProp(this, '_monthsShortRegex')) { - this._monthsShortRegex = defaultMonthsShortRegex; - } - return this._monthsShortStrictRegex && isStrict ? - this._monthsShortStrictRegex : this._monthsShortRegex; - } - } - - var defaultMonthsRegex = matchWord; - function monthsRegex (isStrict) { - if (this._monthsParseExact) { - if (!hasOwnProp(this, '_monthsRegex')) { - computeMonthsParse.call(this); - } - if (isStrict) { - return this._monthsStrictRegex; - } else { - return this._monthsRegex; - } - } else { - if (!hasOwnProp(this, '_monthsRegex')) { - this._monthsRegex = defaultMonthsRegex; - } - return this._monthsStrictRegex && isStrict ? - this._monthsStrictRegex : this._monthsRegex; - } - } - - function computeMonthsParse () { - function cmpLenRev(a, b) { - return b.length - a.length; - } - - var shortPieces = [], longPieces = [], mixedPieces = [], - i, mom; - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = createUTC([2000, i]); - shortPieces.push(this.monthsShort(mom, '')); - longPieces.push(this.months(mom, '')); - mixedPieces.push(this.months(mom, '')); - mixedPieces.push(this.monthsShort(mom, '')); - } - // Sorting makes sure if one month (or abbr) is a prefix of another it - // will match the longer piece. - shortPieces.sort(cmpLenRev); - longPieces.sort(cmpLenRev); - mixedPieces.sort(cmpLenRev); - for (i = 0; i < 12; i++) { - shortPieces[i] = regexEscape(shortPieces[i]); - longPieces[i] = regexEscape(longPieces[i]); - } - for (i = 0; i < 24; i++) { - mixedPieces[i] = regexEscape(mixedPieces[i]); - } - - this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._monthsShortRegex = this._monthsRegex; - this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); - this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); - } - - function createDate (y, m, d, h, M, s, ms) { - // can't just apply() to create a date: - // https://stackoverflow.com/q/181348 - var date; - // the date constructor remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0) { - // preserve leap years using a full 400 year cycle, then reset - date = new Date(y + 400, m, d, h, M, s, ms); - if (isFinite(date.getFullYear())) { - date.setFullYear(y); - } - } else { - date = new Date(y, m, d, h, M, s, ms); - } - - return date; - } - - function createUTCDate (y) { - var date; - // the Date.UTC function remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0) { - var args = Array.prototype.slice.call(arguments); - // preserve leap years using a full 400 year cycle, then reset - args[0] = y + 400; - date = new Date(Date.UTC.apply(null, args)); - if (isFinite(date.getUTCFullYear())) { - date.setUTCFullYear(y); - } - } else { - date = new Date(Date.UTC.apply(null, arguments)); - } - - return date; - } - - // start-of-first-week - start-of-year - function firstWeekOffset(year, dow, doy) { - var // first-week day -- which january is always in the first week (4 for iso, 1 for other) - fwd = 7 + dow - doy, - // first-week day local weekday -- which local weekday is fwd - fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; - - return -fwdlw + fwd - 1; - } - - // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, dow, doy) { - var localWeekday = (7 + weekday - dow) % 7, - weekOffset = firstWeekOffset(year, dow, doy), - dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, - resYear, resDayOfYear; - - if (dayOfYear <= 0) { - resYear = year - 1; - resDayOfYear = daysInYear(resYear) + dayOfYear; - } else if (dayOfYear > daysInYear(year)) { - resYear = year + 1; - resDayOfYear = dayOfYear - daysInYear(year); - } else { - resYear = year; - resDayOfYear = dayOfYear; - } - - return { - year: resYear, - dayOfYear: resDayOfYear - }; - } - - function weekOfYear(mom, dow, doy) { - var weekOffset = firstWeekOffset(mom.year(), dow, doy), - week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, - resWeek, resYear; - - if (week < 1) { - resYear = mom.year() - 1; - resWeek = week + weeksInYear(resYear, dow, doy); - } else if (week > weeksInYear(mom.year(), dow, doy)) { - resWeek = week - weeksInYear(mom.year(), dow, doy); - resYear = mom.year() + 1; - } else { - resYear = mom.year(); - resWeek = week; - } - - return { - week: resWeek, - year: resYear - }; - } - - function weeksInYear(year, dow, doy) { - var weekOffset = firstWeekOffset(year, dow, doy), - weekOffsetNext = firstWeekOffset(year + 1, dow, doy); - return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; - } - - // FORMATTING - - addFormatToken('w', ['ww', 2], 'wo', 'week'); - addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); - - // ALIASES - - addUnitAlias('week', 'w'); - addUnitAlias('isoWeek', 'W'); - - // PRIORITIES - - addUnitPriority('week', 5); - addUnitPriority('isoWeek', 5); - - // PARSING - - addRegexToken('w', match1to2); - addRegexToken('ww', match1to2, match2); - addRegexToken('W', match1to2); - addRegexToken('WW', match1to2, match2); - - addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { - week[token.substr(0, 1)] = toInt(input); - }); - - // HELPERS - - // LOCALES - - function localeWeek (mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - } - - var defaultLocaleWeek = { - dow : 0, // Sunday is the first day of the week. - doy : 6 // The week that contains Jan 6th is the first week of the year. - }; - - function localeFirstDayOfWeek () { - return this._week.dow; - } - - function localeFirstDayOfYear () { - return this._week.doy; - } - - // MOMENTS - - function getSetWeek (input) { - var week = this.localeData().week(this); - return input == null ? week : this.add((input - week) * 7, 'd'); - } - - function getSetISOWeek (input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add((input - week) * 7, 'd'); - } - - // FORMATTING - - addFormatToken('d', 0, 'do', 'day'); - - addFormatToken('dd', 0, 0, function (format) { - return this.localeData().weekdaysMin(this, format); - }); - - addFormatToken('ddd', 0, 0, function (format) { - return this.localeData().weekdaysShort(this, format); - }); - - addFormatToken('dddd', 0, 0, function (format) { - return this.localeData().weekdays(this, format); - }); - - addFormatToken('e', 0, 0, 'weekday'); - addFormatToken('E', 0, 0, 'isoWeekday'); - - // ALIASES - - addUnitAlias('day', 'd'); - addUnitAlias('weekday', 'e'); - addUnitAlias('isoWeekday', 'E'); - - // PRIORITY - addUnitPriority('day', 11); - addUnitPriority('weekday', 11); - addUnitPriority('isoWeekday', 11); - - // PARSING - - addRegexToken('d', match1to2); - addRegexToken('e', match1to2); - addRegexToken('E', match1to2); - addRegexToken('dd', function (isStrict, locale) { - return locale.weekdaysMinRegex(isStrict); - }); - addRegexToken('ddd', function (isStrict, locale) { - return locale.weekdaysShortRegex(isStrict); - }); - addRegexToken('dddd', function (isStrict, locale) { - return locale.weekdaysRegex(isStrict); - }); - - addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { - var weekday = config._locale.weekdaysParse(input, token, config._strict); - // if we didn't get a weekday name, mark the date as invalid - if (weekday != null) { - week.d = weekday; - } else { - getParsingFlags(config).invalidWeekday = input; - } - }); - - addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { - week[token] = toInt(input); - }); - - // HELPERS - - function parseWeekday(input, locale) { - if (typeof input !== 'string') { - return input; - } - - if (!isNaN(input)) { - return parseInt(input, 10); - } - - input = locale.weekdaysParse(input); - if (typeof input === 'number') { - return input; - } - - return null; - } - - function parseIsoWeekday(input, locale) { - if (typeof input === 'string') { - return locale.weekdaysParse(input) % 7 || 7; - } - return isNaN(input) ? null : input; - } - - // LOCALES - function shiftWeekdays (ws, n) { - return ws.slice(n, 7).concat(ws.slice(0, n)); - } - - var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); - function localeWeekdays (m, format) { - var weekdays = isArray(this._weekdays) ? this._weekdays : - this._weekdays[(m && m !== true && this._weekdays.isFormat.test(format)) ? 'format' : 'standalone']; - return (m === true) ? shiftWeekdays(weekdays, this._week.dow) - : (m) ? weekdays[m.day()] : weekdays; - } - - var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); - function localeWeekdaysShort (m) { - return (m === true) ? shiftWeekdays(this._weekdaysShort, this._week.dow) - : (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; - } - - var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); - function localeWeekdaysMin (m) { - return (m === true) ? shiftWeekdays(this._weekdaysMin, this._week.dow) - : (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; - } - - function handleStrictParse$1(weekdayName, format, strict) { - var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); - if (!this._weekdaysParse) { - this._weekdaysParse = []; - this._shortWeekdaysParse = []; - this._minWeekdaysParse = []; - - for (i = 0; i < 7; ++i) { - mom = createUTC([2000, 1]).day(i); - this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); - this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); - this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); - } - } - - if (strict) { - if (format === 'dddd') { - ii = indexOf.call(this._weekdaysParse, llc); - return ii !== -1 ? ii : null; - } else if (format === 'ddd') { - ii = indexOf.call(this._shortWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } - } else { - if (format === 'dddd') { - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else if (format === 'ddd') { - ii = indexOf.call(this._shortWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._minWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } - } - } - - function localeWeekdaysParse (weekdayName, format, strict) { - var i, mom, regex; - - if (this._weekdaysParseExact) { - return handleStrictParse$1.call(this, weekdayName, format, strict); - } - - if (!this._weekdaysParse) { - this._weekdaysParse = []; - this._minWeekdaysParse = []; - this._shortWeekdaysParse = []; - this._fullWeekdaysParse = []; - } - - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - - mom = createUTC([2000, 1]).day(i); - if (strict && !this._fullWeekdaysParse[i]) { - this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', 'i'); - this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', 'i'); - this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', 'i'); - } - if (!this._weekdaysParse[i]) { - regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { - return i; - } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - } - - // MOMENTS - - function getSetDayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.localeData()); - return this.add(input - day, 'd'); - } else { - return day; - } - } - - function getSetLocaleDayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; - return input == null ? weekday : this.add(input - weekday, 'd'); - } - - function getSetISODayOfWeek (input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - - if (input != null) { - var weekday = parseIsoWeekday(input, this.localeData()); - return this.day(this.day() % 7 ? weekday : weekday - 7); - } else { - return this.day() || 7; - } - } - - var defaultWeekdaysRegex = matchWord; - function weekdaysRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysStrictRegex; - } else { - return this._weekdaysRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysRegex')) { - this._weekdaysRegex = defaultWeekdaysRegex; - } - return this._weekdaysStrictRegex && isStrict ? - this._weekdaysStrictRegex : this._weekdaysRegex; - } - } - - var defaultWeekdaysShortRegex = matchWord; - function weekdaysShortRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysShortStrictRegex; - } else { - return this._weekdaysShortRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysShortRegex')) { - this._weekdaysShortRegex = defaultWeekdaysShortRegex; - } - return this._weekdaysShortStrictRegex && isStrict ? - this._weekdaysShortStrictRegex : this._weekdaysShortRegex; - } - } - - var defaultWeekdaysMinRegex = matchWord; - function weekdaysMinRegex (isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysMinStrictRegex; - } else { - return this._weekdaysMinRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysMinRegex')) { - this._weekdaysMinRegex = defaultWeekdaysMinRegex; - } - return this._weekdaysMinStrictRegex && isStrict ? - this._weekdaysMinStrictRegex : this._weekdaysMinRegex; - } - } - - - function computeWeekdaysParse () { - function cmpLenRev(a, b) { - return b.length - a.length; - } - - var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], - i, mom, minp, shortp, longp; - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - mom = createUTC([2000, 1]).day(i); - minp = this.weekdaysMin(mom, ''); - shortp = this.weekdaysShort(mom, ''); - longp = this.weekdays(mom, ''); - minPieces.push(minp); - shortPieces.push(shortp); - longPieces.push(longp); - mixedPieces.push(minp); - mixedPieces.push(shortp); - mixedPieces.push(longp); - } - // Sorting makes sure if one weekday (or abbr) is a prefix of another it - // will match the longer piece. - minPieces.sort(cmpLenRev); - shortPieces.sort(cmpLenRev); - longPieces.sort(cmpLenRev); - mixedPieces.sort(cmpLenRev); - for (i = 0; i < 7; i++) { - shortPieces[i] = regexEscape(shortPieces[i]); - longPieces[i] = regexEscape(longPieces[i]); - mixedPieces[i] = regexEscape(mixedPieces[i]); - } - - this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._weekdaysShortRegex = this._weekdaysRegex; - this._weekdaysMinRegex = this._weekdaysRegex; - - this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); - this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); - this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); - } - - // FORMATTING - - function hFormat() { - return this.hours() % 12 || 12; - } - - function kFormat() { - return this.hours() || 24; - } - - addFormatToken('H', ['HH', 2], 0, 'hour'); - addFormatToken('h', ['hh', 2], 0, hFormat); - addFormatToken('k', ['kk', 2], 0, kFormat); - - addFormatToken('hmm', 0, 0, function () { - return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); - }); - - addFormatToken('hmmss', 0, 0, function () { - return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + - zeroFill(this.seconds(), 2); - }); - - addFormatToken('Hmm', 0, 0, function () { - return '' + this.hours() + zeroFill(this.minutes(), 2); - }); - - addFormatToken('Hmmss', 0, 0, function () { - return '' + this.hours() + zeroFill(this.minutes(), 2) + - zeroFill(this.seconds(), 2); - }); - - function meridiem (token, lowercase) { - addFormatToken(token, 0, 0, function () { - return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); - }); - } - - meridiem('a', true); - meridiem('A', false); - - // ALIASES - - addUnitAlias('hour', 'h'); - - // PRIORITY - addUnitPriority('hour', 13); - - // PARSING - - function matchMeridiem (isStrict, locale) { - return locale._meridiemParse; - } - - addRegexToken('a', matchMeridiem); - addRegexToken('A', matchMeridiem); - addRegexToken('H', match1to2); - addRegexToken('h', match1to2); - addRegexToken('k', match1to2); - addRegexToken('HH', match1to2, match2); - addRegexToken('hh', match1to2, match2); - addRegexToken('kk', match1to2, match2); - - addRegexToken('hmm', match3to4); - addRegexToken('hmmss', match5to6); - addRegexToken('Hmm', match3to4); - addRegexToken('Hmmss', match5to6); - - addParseToken(['H', 'HH'], HOUR); - addParseToken(['k', 'kk'], function (input, array, config) { - var kInput = toInt(input); - array[HOUR] = kInput === 24 ? 0 : kInput; - }); - addParseToken(['a', 'A'], function (input, array, config) { - config._isPm = config._locale.isPM(input); - config._meridiem = input; - }); - addParseToken(['h', 'hh'], function (input, array, config) { - array[HOUR] = toInt(input); - getParsingFlags(config).bigHour = true; - }); - addParseToken('hmm', function (input, array, config) { - var pos = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos)); - array[MINUTE] = toInt(input.substr(pos)); - getParsingFlags(config).bigHour = true; - }); - addParseToken('hmmss', function (input, array, config) { - var pos1 = input.length - 4; - var pos2 = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos1)); - array[MINUTE] = toInt(input.substr(pos1, 2)); - array[SECOND] = toInt(input.substr(pos2)); - getParsingFlags(config).bigHour = true; - }); - addParseToken('Hmm', function (input, array, config) { - var pos = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos)); - array[MINUTE] = toInt(input.substr(pos)); - }); - addParseToken('Hmmss', function (input, array, config) { - var pos1 = input.length - 4; - var pos2 = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos1)); - array[MINUTE] = toInt(input.substr(pos1, 2)); - array[SECOND] = toInt(input.substr(pos2)); - }); - - // LOCALES - - function localeIsPM (input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return ((input + '').toLowerCase().charAt(0) === 'p'); - } - - var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; - function localeMeridiem (hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - } - - - // MOMENTS - - // Setting the hour should keep the time, because the user explicitly - // specified which hour they want. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - var getSetHour = makeGetSet('Hours', true); - - var baseConfig = { - calendar: defaultCalendar, - longDateFormat: defaultLongDateFormat, - invalidDate: defaultInvalidDate, - ordinal: defaultOrdinal, - dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, - relativeTime: defaultRelativeTime, - - months: defaultLocaleMonths, - monthsShort: defaultLocaleMonthsShort, - - week: defaultLocaleWeek, - - weekdays: defaultLocaleWeekdays, - weekdaysMin: defaultLocaleWeekdaysMin, - weekdaysShort: defaultLocaleWeekdaysShort, - - meridiemParse: defaultLocaleMeridiemParse - }; - - // internal storage for locale config files - var locales = {}; - var localeFamilies = {}; - var globalLocale; - - function normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } - - // pick the locale from the array - // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - function chooseLocale(names) { - var i = 0, j, next, locale, split; - - while (i < names.length) { - split = normalizeLocale(names[i]).split('-'); - j = split.length; - next = normalizeLocale(names[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - locale = loadLocale(split.slice(0, j).join('-')); - if (locale) { - return locale; - } - if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return globalLocale; - } - - function loadLocale(name) { - var oldLocale = null; - // TODO: Find a better way to register and load all the locales in Node - if (!locales[name] && ('object' !== 'undefined') && - module && module.exports) { - try { - oldLocale = globalLocale._abbr; - var aliasedRequire = commonjsRequire; - aliasedRequire('./locale/' + name); - getSetGlobalLocale(oldLocale); - } catch (e) {} - } - return locales[name]; - } - - // This function will load locale and then set the global locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - function getSetGlobalLocale (key, values) { - var data; - if (key) { - if (isUndefined(values)) { - data = getLocale(key); - } - else { - data = defineLocale(key, values); - } - - if (data) { - // moment.duration._locale = moment._locale = data; - globalLocale = data; - } - else { - if ((typeof console !== 'undefined') && console.warn) { - //warn user if arguments are passed but the locale could not be set - console.warn('Locale ' + key + ' not found. Did you forget to load it?'); - } - } - } - - return globalLocale._abbr; - } - - function defineLocale (name, config) { - if (config !== null) { - var locale, parentConfig = baseConfig; - config.abbr = name; - if (locales[name] != null) { - deprecateSimple('defineLocaleOverride', - 'use moment.updateLocale(localeName, config) to change ' + - 'an existing locale. moment.defineLocale(localeName, ' + - 'config) should only be used for creating a new locale ' + - 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); - parentConfig = locales[name]._config; - } else if (config.parentLocale != null) { - if (locales[config.parentLocale] != null) { - parentConfig = locales[config.parentLocale]._config; - } else { - locale = loadLocale(config.parentLocale); - if (locale != null) { - parentConfig = locale._config; - } else { - if (!localeFamilies[config.parentLocale]) { - localeFamilies[config.parentLocale] = []; - } - localeFamilies[config.parentLocale].push({ - name: name, - config: config - }); - return null; - } - } - } - locales[name] = new Locale(mergeConfigs(parentConfig, config)); - - if (localeFamilies[name]) { - localeFamilies[name].forEach(function (x) { - defineLocale(x.name, x.config); - }); - } - - // backwards compat for now: also set the locale - // make sure we set the locale AFTER all child locales have been - // created, so we won't end up with the child locale set. - getSetGlobalLocale(name); - - - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; - } - } - - function updateLocale(name, config) { - if (config != null) { - var locale, tmpLocale, parentConfig = baseConfig; - // MERGE - tmpLocale = loadLocale(name); - if (tmpLocale != null) { - parentConfig = tmpLocale._config; - } - config = mergeConfigs(parentConfig, config); - locale = new Locale(config); - locale.parentLocale = locales[name]; - locales[name] = locale; - - // backwards compat for now: also set the locale - getSetGlobalLocale(name); - } else { - // pass null for config to unupdate, useful for tests - if (locales[name] != null) { - if (locales[name].parentLocale != null) { - locales[name] = locales[name].parentLocale; - } else if (locales[name] != null) { - delete locales[name]; - } - } - } - return locales[name]; - } - - // returns locale data - function getLocale (key) { - var locale; - - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; - } - - if (!key) { - return globalLocale; - } - - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; - } - key = [key]; - } - - return chooseLocale(key); - } - - function listLocales() { - return keys(locales); - } - - function checkOverflow (m) { - var overflow; - var a = m._a; - - if (a && getParsingFlags(m).overflow === -2) { - overflow = - a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : - a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : - a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : - a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : - a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : - a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : - -1; - - if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { - overflow = DATE; - } - if (getParsingFlags(m)._overflowWeeks && overflow === -1) { - overflow = WEEK; - } - if (getParsingFlags(m)._overflowWeekday && overflow === -1) { - overflow = WEEKDAY; - } - - getParsingFlags(m).overflow = overflow; - } - - return m; - } - - // Pick the first defined of two or three arguments. - function defaults(a, b, c) { - if (a != null) { - return a; - } - if (b != null) { - return b; - } - return c; - } - - function currentDateArray(config) { - // hooks is actually the exported moment object - var nowValue = new Date(hooks.now()); - if (config._useUTC) { - return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; - } - return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; - } - - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function configFromArray (config) { - var i, date, input = [], currentDate, expectedWeekday, yearToUse; - - if (config._d) { - return; - } - - currentDate = currentDateArray(config); - - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - dayOfYearFromWeekInfo(config); - } - - //if the day of the year is set, figure out what it is - if (config._dayOfYear != null) { - yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); - - if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) { - getParsingFlags(config)._overflowDayOfYear = true; - } - - date = createUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } - - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } - - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; - } - - // Check for 24:00:00.000 - if (config._a[HOUR] === 24 && - config._a[MINUTE] === 0 && - config._a[SECOND] === 0 && - config._a[MILLISECOND] === 0) { - config._nextDay = true; - config._a[HOUR] = 0; - } - - config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); - expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay(); - - // Apply timezone offset from input. The actual utcOffset can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - } - - if (config._nextDay) { - config._a[HOUR] = 24; - } - - // check for mismatching day of week - if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) { - getParsingFlags(config).weekdayMismatch = true; - } - } - - function dayOfYearFromWeekInfo(config) { - var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - dow = 1; - doy = 4; - - // TODO: We need to take the current isoWeekYear, but that depends on - // how we interpret now (local, utc, fixed offset). So create - // a now version of current config (take local/utc/offset flags, and - // create now). - weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year); - week = defaults(w.W, 1); - weekday = defaults(w.E, 1); - if (weekday < 1 || weekday > 7) { - weekdayOverflow = true; - } - } else { - dow = config._locale._week.dow; - doy = config._locale._week.doy; - - var curWeek = weekOfYear(createLocal(), dow, doy); - - weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); - - // Default to current week. - week = defaults(w.w, curWeek.week); - - if (w.d != null) { - // weekday -- low day numbers are considered next week - weekday = w.d; - if (weekday < 0 || weekday > 6) { - weekdayOverflow = true; - } - } else if (w.e != null) { - // local weekday -- counting starts from beginning of week - weekday = w.e + dow; - if (w.e < 0 || w.e > 6) { - weekdayOverflow = true; - } - } else { - // default to beginning of week - weekday = dow; - } - } - if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { - getParsingFlags(config)._overflowWeeks = true; - } else if (weekdayOverflow != null) { - getParsingFlags(config)._overflowWeekday = true; - } else { - temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } - } - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; - var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; - - var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; - - var isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], - ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], - ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], - ['GGGG-[W]WW', /\d{4}-W\d\d/, false], - ['YYYY-DDD', /\d{4}-\d{3}/], - ['YYYY-MM', /\d{4}-\d\d/, false], - ['YYYYYYMMDD', /[+-]\d{10}/], - ['YYYYMMDD', /\d{8}/], - // YYYYMM is NOT allowed by the standard - ['GGGG[W]WWE', /\d{4}W\d{3}/], - ['GGGG[W]WW', /\d{4}W\d{2}/, false], - ['YYYYDDD', /\d{7}/] - ]; - - // iso time formats and regexes - var isoTimes = [ - ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], - ['HH:mm:ss', /\d\d:\d\d:\d\d/], - ['HH:mm', /\d\d:\d\d/], - ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], - ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], - ['HHmmss', /\d\d\d\d\d\d/], - ['HHmm', /\d\d\d\d/], - ['HH', /\d\d/] - ]; - - var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; - - // date from iso format - function configFromISO(config) { - var i, l, - string = config._i, - match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), - allowTime, dateFormat, timeFormat, tzFormat; - - if (match) { - getParsingFlags(config).iso = true; - - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(match[1])) { - dateFormat = isoDates[i][0]; - allowTime = isoDates[i][2] !== false; - break; - } - } - if (dateFormat == null) { - config._isValid = false; - return; - } - if (match[3]) { - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(match[3])) { - // match[2] should be 'T' or space - timeFormat = (match[2] || ' ') + isoTimes[i][0]; - break; - } - } - if (timeFormat == null) { - config._isValid = false; - return; - } - } - if (!allowTime && timeFormat != null) { - config._isValid = false; - return; - } - if (match[4]) { - if (tzRegex.exec(match[4])) { - tzFormat = 'Z'; - } else { - config._isValid = false; - return; - } - } - config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); - configFromStringAndFormat(config); - } else { - config._isValid = false; - } - } - - // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 - var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/; - - function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) { - var result = [ - untruncateYear(yearStr), - defaultLocaleMonthsShort.indexOf(monthStr), - parseInt(dayStr, 10), - parseInt(hourStr, 10), - parseInt(minuteStr, 10) - ]; - - if (secondStr) { - result.push(parseInt(secondStr, 10)); - } - - return result; - } - - function untruncateYear(yearStr) { - var year = parseInt(yearStr, 10); - if (year <= 49) { - return 2000 + year; - } else if (year <= 999) { - return 1900 + year; - } - return year; - } - - function preprocessRFC2822(s) { - // Remove comments and folding whitespace and replace multiple-spaces with a single space - return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); - } - - function checkWeekday(weekdayStr, parsedInput, config) { - if (weekdayStr) { - // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check. - var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), - weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay(); - if (weekdayProvided !== weekdayActual) { - getParsingFlags(config).weekdayMismatch = true; - config._isValid = false; - return false; - } - } - return true; - } - - var obsOffsets = { - UT: 0, - GMT: 0, - EDT: -4 * 60, - EST: -5 * 60, - CDT: -5 * 60, - CST: -6 * 60, - MDT: -6 * 60, - MST: -7 * 60, - PDT: -7 * 60, - PST: -8 * 60 - }; - - function calculateOffset(obsOffset, militaryOffset, numOffset) { - if (obsOffset) { - return obsOffsets[obsOffset]; - } else if (militaryOffset) { - // the only allowed military tz is Z - return 0; - } else { - var hm = parseInt(numOffset, 10); - var m = hm % 100, h = (hm - m) / 100; - return h * 60 + m; - } - } - - // date and time from ref 2822 format - function configFromRFC2822(config) { - var match = rfc2822.exec(preprocessRFC2822(config._i)); - if (match) { - var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]); - if (!checkWeekday(match[1], parsedArray, config)) { - return; - } - - config._a = parsedArray; - config._tzm = calculateOffset(match[8], match[9], match[10]); - - config._d = createUTCDate.apply(null, config._a); - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - - getParsingFlags(config).rfc2822 = true; - } else { - config._isValid = false; - } - } - - // date from iso format or fallback - function configFromString(config) { - var matched = aspNetJsonRegex.exec(config._i); - - if (matched !== null) { - config._d = new Date(+matched[1]); - return; - } - - configFromISO(config); - if (config._isValid === false) { - delete config._isValid; - } else { - return; - } - - configFromRFC2822(config); - if (config._isValid === false) { - delete config._isValid; - } else { - return; - } - - // Final attempt, use Input Fallback - hooks.createFromInputFallback(config); - } - - hooks.createFromInputFallback = deprecate( - 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + - 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + - 'discouraged and will be removed in an upcoming major release. Please refer to ' + - 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', - function (config) { - config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); - } - ); - - // constant that refers to the ISO standard - hooks.ISO_8601 = function () {}; - - // constant that refers to the RFC 2822 form - hooks.RFC_2822 = function () {}; - - // date from string and format string - function configFromStringAndFormat(config) { - // TODO: Move this to another part of the creation flow to prevent circular deps - if (config._f === hooks.ISO_8601) { - configFromISO(config); - return; - } - if (config._f === hooks.RFC_2822) { - configFromRFC2822(config); - return; - } - config._a = []; - getParsingFlags(config).empty = true; - - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var string = '' + config._i, - i, parsedInput, tokens, token, skipped, - stringLength = string.length, - totalParsedInputLength = 0; - - tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; - // console.log('token', token, 'parsedInput', parsedInput, - // 'regex', getParseRegexForToken(token, config)); - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - getParsingFlags(config).unusedInput.push(skipped); - } - string = string.slice(string.indexOf(parsedInput) + parsedInput.length); - totalParsedInputLength += parsedInput.length; - } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - getParsingFlags(config).empty = false; - } - else { - getParsingFlags(config).unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } - else if (config._strict && !parsedInput) { - getParsingFlags(config).unusedTokens.push(token); - } - } - - // add remaining unparsed input length to the string - getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; - if (string.length > 0) { - getParsingFlags(config).unusedInput.push(string); - } - - // clear _12h flag if hour is <= 12 - if (config._a[HOUR] <= 12 && - getParsingFlags(config).bigHour === true && - config._a[HOUR] > 0) { - getParsingFlags(config).bigHour = undefined; - } - - getParsingFlags(config).parsedDateParts = config._a.slice(0); - getParsingFlags(config).meridiem = config._meridiem; - // handle meridiem - config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - - configFromArray(config); - checkOverflow(config); - } - - - function meridiemFixWrap (locale, hour, meridiem) { - var isPm; - - if (meridiem == null) { - // nothing to do - return hour; - } - if (locale.meridiemHour != null) { - return locale.meridiemHour(hour, meridiem); - } else if (locale.isPM != null) { - // Fallback - isPm = locale.isPM(meridiem); - if (isPm && hour < 12) { - hour += 12; - } - if (!isPm && hour === 12) { - hour = 0; - } - return hour; - } else { - // this is not supposed to happen - return hour; - } - } - - // date from string and array of format strings - function configFromStringAndArray(config) { - var tempConfig, - bestMoment, - - scoreToBeat, - i, - currentScore; - - if (config._f.length === 0) { - getParsingFlags(config).invalidFormat = true; - config._d = new Date(NaN); - return; - } - - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - tempConfig = copyConfig({}, config); - if (config._useUTC != null) { - tempConfig._useUTC = config._useUTC; - } - tempConfig._f = config._f[i]; - configFromStringAndFormat(tempConfig); - - if (!isValid(tempConfig)) { - continue; - } - - // if there is any input that was not parsed add a penalty for that format - currentScore += getParsingFlags(tempConfig).charsLeftOver; - - //or tokens - currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; - - getParsingFlags(tempConfig).score = currentScore; - - if (scoreToBeat == null || currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } - } - - extend(config, bestMoment || tempConfig); - } - - function configFromObject(config) { - if (config._d) { - return; - } - - var i = normalizeObjectUnits(config._i); - config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { - return obj && parseInt(obj, 10); - }); - - configFromArray(config); - } - - function createFromConfig (config) { - var res = new Moment(checkOverflow(prepareConfig(config))); - if (res._nextDay) { - // Adding is smart enough around DST - res.add(1, 'd'); - res._nextDay = undefined; - } - - return res; - } - - function prepareConfig (config) { - var input = config._i, - format = config._f; - - config._locale = config._locale || getLocale(config._l); - - if (input === null || (format === undefined && input === '')) { - return createInvalid({nullInput: true}); - } - - if (typeof input === 'string') { - config._i = input = config._locale.preparse(input); - } - - if (isMoment(input)) { - return new Moment(checkOverflow(input)); - } else if (isDate(input)) { - config._d = input; - } else if (isArray(format)) { - configFromStringAndArray(config); - } else if (format) { - configFromStringAndFormat(config); - } else { - configFromInput(config); - } - - if (!isValid(config)) { - config._d = null; - } - - return config; - } - - function configFromInput(config) { - var input = config._i; - if (isUndefined(input)) { - config._d = new Date(hooks.now()); - } else if (isDate(input)) { - config._d = new Date(input.valueOf()); - } else if (typeof input === 'string') { - configFromString(config); - } else if (isArray(input)) { - config._a = map(input.slice(0), function (obj) { - return parseInt(obj, 10); - }); - configFromArray(config); - } else if (isObject(input)) { - configFromObject(config); - } else if (isNumber(input)) { - // from milliseconds - config._d = new Date(input); - } else { - hooks.createFromInputFallback(config); - } - } - - function createLocalOrUTC (input, format, locale, strict, isUTC) { - var c = {}; - - if (locale === true || locale === false) { - strict = locale; - locale = undefined; - } - - if ((isObject(input) && isObjectEmpty(input)) || - (isArray(input) && input.length === 0)) { - input = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c._isAMomentObject = true; - c._useUTC = c._isUTC = isUTC; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; - - return createFromConfig(c); - } - - function createLocal (input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, false); - } - - var prototypeMin = deprecate( - 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', - function () { - var other = createLocal.apply(null, arguments); - if (this.isValid() && other.isValid()) { - return other < this ? this : other; - } else { - return createInvalid(); - } - } - ); - - var prototypeMax = deprecate( - 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', - function () { - var other = createLocal.apply(null, arguments); - if (this.isValid() && other.isValid()) { - return other > this ? this : other; - } else { - return createInvalid(); - } - } - ); - - // Pick a moment m from moments so that m[fn](other) is true for all - // other. This relies on the function fn to be transitive. - // - // moments should either be an array of moment objects or an array, whose - // first element is an array of moment objects. - function pickBy(fn, moments) { - var res, i; - if (moments.length === 1 && isArray(moments[0])) { - moments = moments[0]; - } - if (!moments.length) { - return createLocal(); - } - res = moments[0]; - for (i = 1; i < moments.length; ++i) { - if (!moments[i].isValid() || moments[i][fn](res)) { - res = moments[i]; - } - } - return res; - } - - // TODO: Use [].sort instead? - function min () { - var args = [].slice.call(arguments, 0); - - return pickBy('isBefore', args); - } - - function max () { - var args = [].slice.call(arguments, 0); - - return pickBy('isAfter', args); - } - - var now = function () { - return Date.now ? Date.now() : +(new Date()); - }; - - var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond']; - - function isDurationValid(m) { - for (var key in m) { - if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) { - return false; - } - } - - var unitHasDecimal = false; - for (var i = 0; i < ordering.length; ++i) { - if (m[ordering[i]]) { - if (unitHasDecimal) { - return false; // only allow non-integers for smallest unit - } - if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { - unitHasDecimal = true; - } - } - } - - return true; - } - - function isValid$1() { - return this._isValid; - } - - function createInvalid$1() { - return createDuration(NaN); - } - - function Duration (duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || normalizedInput.isoWeek || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - this._isValid = isDurationValid(normalizedInput); - - // representation for dateAddRemove - this._milliseconds = +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + - weeks * 7; - // It is impossible to translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + - quarters * 3 + - years * 12; - - this._data = {}; - - this._locale = getLocale(); - - this._bubble(); - } - - function isDuration (obj) { - return obj instanceof Duration; - } - - function absRound (number) { - if (number < 0) { - return Math.round(-1 * number) * -1; - } else { - return Math.round(number); - } - } - - // FORMATTING - - function offset (token, separator) { - addFormatToken(token, 0, 0, function () { - var offset = this.utcOffset(); - var sign = '+'; - if (offset < 0) { - offset = -offset; - sign = '-'; - } - return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); - }); - } - - offset('Z', ':'); - offset('ZZ', ''); - - // PARSING - - addRegexToken('Z', matchShortOffset); - addRegexToken('ZZ', matchShortOffset); - addParseToken(['Z', 'ZZ'], function (input, array, config) { - config._useUTC = true; - config._tzm = offsetFromString(matchShortOffset, input); - }); - - // HELPERS - - // timezone chunker - // '+10:00' > ['10', '00'] - // '-1530' > ['-15', '30'] - var chunkOffset = /([\+\-]|\d\d)/gi; - - function offsetFromString(matcher, string) { - var matches = (string || '').match(matcher); - - if (matches === null) { - return null; - } - - var chunk = matches[matches.length - 1] || []; - var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; - var minutes = +(parts[1] * 60) + toInt(parts[2]); - - return minutes === 0 ? - 0 : - parts[0] === '+' ? minutes : -minutes; - } - - // Return a moment from input, that is local/utc/zone equivalent to model. - function cloneWithOffset(input, model) { - var res, diff; - if (model._isUTC) { - res = model.clone(); - diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); - // Use low-level api, because this fn is low-level api. - res._d.setTime(res._d.valueOf() + diff); - hooks.updateOffset(res, false); - return res; - } else { - return createLocal(input).local(); - } - } - - function getDateOffset (m) { - // On Firefox.24 Date#getTimezoneOffset returns a floating point. - // https://github.com/moment/moment/pull/1871 - return -Math.round(m._d.getTimezoneOffset() / 15) * 15; - } - - // HOOKS - - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - hooks.updateOffset = function () {}; - - // MOMENTS - - // keepLocalTime = true means only change the timezone, without - // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> - // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset - // +0200, so we adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - function getSetOffset (input, keepLocalTime, keepMinutes) { - var offset = this._offset || 0, - localAdjust; - if (!this.isValid()) { - return input != null ? this : NaN; - } - if (input != null) { - if (typeof input === 'string') { - input = offsetFromString(matchShortOffset, input); - if (input === null) { - return this; - } - } else if (Math.abs(input) < 16 && !keepMinutes) { - input = input * 60; - } - if (!this._isUTC && keepLocalTime) { - localAdjust = getDateOffset(this); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.add(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - addSubtract(this, createDuration(input - offset, 'm'), 1, false); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - hooks.updateOffset(this, true); - this._changeInProgress = null; - } - } - return this; - } else { - return this._isUTC ? offset : getDateOffset(this); - } - } - - function getSetZone (input, keepLocalTime) { - if (input != null) { - if (typeof input !== 'string') { - input = -input; - } - - this.utcOffset(input, keepLocalTime); - - return this; - } else { - return -this.utcOffset(); - } - } - - function setOffsetToUTC (keepLocalTime) { - return this.utcOffset(0, keepLocalTime); - } - - function setOffsetToLocal (keepLocalTime) { - if (this._isUTC) { - this.utcOffset(0, keepLocalTime); - this._isUTC = false; - - if (keepLocalTime) { - this.subtract(getDateOffset(this), 'm'); - } - } - return this; - } - - function setOffsetToParsedOffset () { - if (this._tzm != null) { - this.utcOffset(this._tzm, false, true); - } else if (typeof this._i === 'string') { - var tZone = offsetFromString(matchOffset, this._i); - if (tZone != null) { - this.utcOffset(tZone); - } - else { - this.utcOffset(0, true); - } - } - return this; - } - - function hasAlignedHourOffset (input) { - if (!this.isValid()) { - return false; - } - input = input ? createLocal(input).utcOffset() : 0; - - return (this.utcOffset() - input) % 60 === 0; - } - - function isDaylightSavingTime () { - return ( - this.utcOffset() > this.clone().month(0).utcOffset() || - this.utcOffset() > this.clone().month(5).utcOffset() - ); - } - - function isDaylightSavingTimeShifted () { - if (!isUndefined(this._isDSTShifted)) { - return this._isDSTShifted; - } - - var c = {}; - - copyConfig(c, this); - c = prepareConfig(c); - - if (c._a) { - var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); - this._isDSTShifted = this.isValid() && - compareArrays(c._a, other.toArray()) > 0; - } else { - this._isDSTShifted = false; - } - - return this._isDSTShifted; - } - - function isLocal () { - return this.isValid() ? !this._isUTC : false; - } - - function isUtcOffset () { - return this.isValid() ? this._isUTC : false; - } - - function isUtc () { - return this.isValid() ? this._isUTC && this._offset === 0 : false; - } - - // ASP.NET json date format regex - var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; - - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - // and further modified to allow for strings containing both week and day - var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; - - function createDuration (input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - diffRes; - - if (isDuration(input)) { - duration = { - ms : input._milliseconds, - d : input._days, - M : input._months - }; - } else if (isNumber(input)) { - duration = {}; - if (key) { - duration[key] = input; - } else { - duration.milliseconds = input; - } - } else if (!!(match = aspNetRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y : 0, - d : toInt(match[DATE]) * sign, - h : toInt(match[HOUR]) * sign, - m : toInt(match[MINUTE]) * sign, - s : toInt(match[SECOND]) * sign, - ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match - }; - } else if (!!(match = isoRegex.exec(input))) { - sign = (match[1] === '-') ? -1 : 1; - duration = { - y : parseIso(match[2], sign), - M : parseIso(match[3], sign), - w : parseIso(match[4], sign), - d : parseIso(match[5], sign), - h : parseIso(match[6], sign), - m : parseIso(match[7], sign), - s : parseIso(match[8], sign) - }; - } else if (duration == null) {// checks for null or undefined - duration = {}; - } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { - diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to)); - - duration = {}; - duration.ms = diffRes.milliseconds; - duration.M = diffRes.months; - } - - ret = new Duration(duration); - - if (isDuration(input) && hasOwnProp(input, '_locale')) { - ret._locale = input._locale; - } - - return ret; - } - - createDuration.fn = Duration.prototype; - createDuration.invalid = createInvalid$1; - - function parseIso (inp, sign) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - } - - function positiveMomentsDifference(base, other) { - var res = {}; - - res.months = other.month() - base.month() + - (other.year() - base.year()) * 12; - if (base.clone().add(res.months, 'M').isAfter(other)) { - --res.months; - } - - res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - - return res; - } - - function momentsDifference(base, other) { - var res; - if (!(base.isValid() && other.isValid())) { - return {milliseconds: 0, months: 0}; - } - - other = cloneWithOffset(other, base); - if (base.isBefore(other)) { - res = positiveMomentsDifference(base, other); - } else { - res = positiveMomentsDifference(other, base); - res.milliseconds = -res.milliseconds; - res.months = -res.months; - } - - return res; - } - - // TODO: remove 'name' arg after deprecation is removed - function createAdder(direction, name) { - return function (val, period) { - var dur, tmp; - //invert the arguments, but complain about it - if (period !== null && !isNaN(+period)) { - deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + - 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); - tmp = val; val = period; period = tmp; - } - - val = typeof val === 'string' ? +val : val; - dur = createDuration(val, period); - addSubtract(this, dur, direction); - return this; - }; - } - - function addSubtract (mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = absRound(duration._days), - months = absRound(duration._months); - - if (!mom.isValid()) { - // No op - return; - } - - updateOffset = updateOffset == null ? true : updateOffset; - - if (months) { - setMonth(mom, get(mom, 'Month') + months * isAdding); - } - if (days) { - set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); - } - if (milliseconds) { - mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); - } - if (updateOffset) { - hooks.updateOffset(mom, days || months); - } - } - - var add = createAdder(1, 'add'); - var subtract = createAdder(-1, 'subtract'); - - function getCalendarFormat(myMoment, now) { - var diff = myMoment.diff(now, 'days', true); - return diff < -6 ? 'sameElse' : - diff < -1 ? 'lastWeek' : - diff < 0 ? 'lastDay' : - diff < 1 ? 'sameDay' : - diff < 2 ? 'nextDay' : - diff < 7 ? 'nextWeek' : 'sameElse'; - } - - function calendar$1 (time, formats) { - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're local/utc/offset or not. - var now = time || createLocal(), - sod = cloneWithOffset(now, this).startOf('day'), - format = hooks.calendarFormat(this, sod) || 'sameElse'; - - var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); - - return this.format(output || this.localeData().calendar(format, this, createLocal(now))); - } - - function clone () { - return new Moment(this); - } - - function isAfter (input, units) { - var localInput = isMoment(input) ? input : createLocal(input); - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(units) || 'millisecond'; - if (units === 'millisecond') { - return this.valueOf() > localInput.valueOf(); - } else { - return localInput.valueOf() < this.clone().startOf(units).valueOf(); - } - } - - function isBefore (input, units) { - var localInput = isMoment(input) ? input : createLocal(input); - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(units) || 'millisecond'; - if (units === 'millisecond') { - return this.valueOf() < localInput.valueOf(); - } else { - return this.clone().endOf(units).valueOf() < localInput.valueOf(); - } - } - - function isBetween (from, to, units, inclusivity) { - var localFrom = isMoment(from) ? from : createLocal(from), - localTo = isMoment(to) ? to : createLocal(to); - if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) { - return false; - } - inclusivity = inclusivity || '()'; - return (inclusivity[0] === '(' ? this.isAfter(localFrom, units) : !this.isBefore(localFrom, units)) && - (inclusivity[1] === ')' ? this.isBefore(localTo, units) : !this.isAfter(localTo, units)); - } - - function isSame (input, units) { - var localInput = isMoment(input) ? input : createLocal(input), - inputMs; - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(units) || 'millisecond'; - if (units === 'millisecond') { - return this.valueOf() === localInput.valueOf(); - } else { - inputMs = localInput.valueOf(); - return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); - } - } - - function isSameOrAfter (input, units) { - return this.isSame(input, units) || this.isAfter(input, units); - } - - function isSameOrBefore (input, units) { - return this.isSame(input, units) || this.isBefore(input, units); - } - - function diff (input, units, asFloat) { - var that, - zoneDelta, - output; - - if (!this.isValid()) { - return NaN; - } - - that = cloneWithOffset(input, this); - - if (!that.isValid()) { - return NaN; - } - - zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; - - units = normalizeUnits(units); - - switch (units) { - case 'year': output = monthDiff(this, that) / 12; break; - case 'month': output = monthDiff(this, that); break; - case 'quarter': output = monthDiff(this, that) / 3; break; - case 'second': output = (this - that) / 1e3; break; // 1000 - case 'minute': output = (this - that) / 6e4; break; // 1000 * 60 - case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60 - case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst - case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst - default: output = this - that; - } - - return asFloat ? output : absFloor(output); - } - - function monthDiff (a, b) { - // difference in months - var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), - // b is in (anchor - 1 month, anchor + 1 month) - anchor = a.clone().add(wholeMonthDiff, 'months'), - anchor2, adjust; - - if (b - anchor < 0) { - anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor - anchor2); - } else { - anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor2 - anchor); - } - - //check for negative zero, return zero if negative zero - return -(wholeMonthDiff + adjust) || 0; - } - - hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; - hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; - - function toString () { - return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); - } - - function toISOString(keepOffset) { - if (!this.isValid()) { - return null; - } - var utc = keepOffset !== true; - var m = utc ? this.clone().utc() : this; - if (m.year() < 0 || m.year() > 9999) { - return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'); - } - if (isFunction(Date.prototype.toISOString)) { - // native implementation is ~50x faster, use it when we can - if (utc) { - return this.toDate().toISOString(); - } else { - return new Date(this.valueOf() + this.utcOffset() * 60 * 1000).toISOString().replace('Z', formatMoment(m, 'Z')); - } - } - return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'); - } - - /** - * Return a human readable representation of a moment that can - * also be evaluated to get a new moment which is the same - * - * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects - */ - function inspect () { - if (!this.isValid()) { - return 'moment.invalid(/* ' + this._i + ' */)'; - } - var func = 'moment'; - var zone = ''; - if (!this.isLocal()) { - func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; - zone = 'Z'; - } - var prefix = '[' + func + '("]'; - var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY'; - var datetime = '-MM-DD[T]HH:mm:ss.SSS'; - var suffix = zone + '[")]'; - - return this.format(prefix + year + datetime + suffix); - } - - function format (inputString) { - if (!inputString) { - inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat; - } - var output = formatMoment(this, inputString); - return this.localeData().postformat(output); - } - - function from (time, withoutSuffix) { - if (this.isValid() && - ((isMoment(time) && time.isValid()) || - createLocal(time).isValid())) { - return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); - } else { - return this.localeData().invalidDate(); - } - } - - function fromNow (withoutSuffix) { - return this.from(createLocal(), withoutSuffix); - } - - function to (time, withoutSuffix) { - if (this.isValid() && - ((isMoment(time) && time.isValid()) || - createLocal(time).isValid())) { - return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); - } else { - return this.localeData().invalidDate(); - } - } - - function toNow (withoutSuffix) { - return this.to(createLocal(), withoutSuffix); - } - - // If passed a locale key, it will set the locale for this - // instance. Otherwise, it will return the locale configuration - // variables for this instance. - function locale (key) { - var newLocaleData; - - if (key === undefined) { - return this._locale._abbr; - } else { - newLocaleData = getLocale(key); - if (newLocaleData != null) { - this._locale = newLocaleData; - } - return this; - } - } - - var lang = deprecate( - 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', - function (key) { - if (key === undefined) { - return this.localeData(); - } else { - return this.locale(key); - } - } - ); - - function localeData () { - return this._locale; - } - - var MS_PER_SECOND = 1000; - var MS_PER_MINUTE = 60 * MS_PER_SECOND; - var MS_PER_HOUR = 60 * MS_PER_MINUTE; - var MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; - - // actual modulo - handles negative numbers (for dates before 1970): - function mod$1(dividend, divisor) { - return (dividend % divisor + divisor) % divisor; - } - - function localStartOfDate(y, m, d) { - // the date constructor remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0) { - // preserve leap years using a full 400 year cycle, then reset - return new Date(y + 400, m, d) - MS_PER_400_YEARS; - } else { - return new Date(y, m, d).valueOf(); - } - } - - function utcStartOfDate(y, m, d) { - // Date.UTC remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0) { - // preserve leap years using a full 400 year cycle, then reset - return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; - } else { - return Date.UTC(y, m, d); - } - } - - function startOf (units) { - var time; - units = normalizeUnits(units); - if (units === undefined || units === 'millisecond' || !this.isValid()) { - return this; - } - - var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; - - switch (units) { - case 'year': - time = startOfDate(this.year(), 0, 1); - break; - case 'quarter': - time = startOfDate(this.year(), this.month() - this.month() % 3, 1); - break; - case 'month': - time = startOfDate(this.year(), this.month(), 1); - break; - case 'week': - time = startOfDate(this.year(), this.month(), this.date() - this.weekday()); - break; - case 'isoWeek': - time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1)); - break; - case 'day': - case 'date': - time = startOfDate(this.year(), this.month(), this.date()); - break; - case 'hour': - time = this._d.valueOf(); - time -= mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR); - break; - case 'minute': - time = this._d.valueOf(); - time -= mod$1(time, MS_PER_MINUTE); - break; - case 'second': - time = this._d.valueOf(); - time -= mod$1(time, MS_PER_SECOND); - break; - } - - this._d.setTime(time); - hooks.updateOffset(this, true); - return this; - } - - function endOf (units) { - var time; - units = normalizeUnits(units); - if (units === undefined || units === 'millisecond' || !this.isValid()) { - return this; - } - - var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; - - switch (units) { - case 'year': - time = startOfDate(this.year() + 1, 0, 1) - 1; - break; - case 'quarter': - time = startOfDate(this.year(), this.month() - this.month() % 3 + 3, 1) - 1; - break; - case 'month': - time = startOfDate(this.year(), this.month() + 1, 1) - 1; - break; - case 'week': - time = startOfDate(this.year(), this.month(), this.date() - this.weekday() + 7) - 1; - break; - case 'isoWeek': - time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1) + 7) - 1; - break; - case 'day': - case 'date': - time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; - break; - case 'hour': - time = this._d.valueOf(); - time += MS_PER_HOUR - mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR) - 1; - break; - case 'minute': - time = this._d.valueOf(); - time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1; - break; - case 'second': - time = this._d.valueOf(); - time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1; - break; - } - - this._d.setTime(time); - hooks.updateOffset(this, true); - return this; - } - - function valueOf () { - return this._d.valueOf() - ((this._offset || 0) * 60000); - } - - function unix () { - return Math.floor(this.valueOf() / 1000); - } - - function toDate () { - return new Date(this.valueOf()); - } - - function toArray () { - var m = this; - return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; - } - - function toObject () { - var m = this; - return { - years: m.year(), - months: m.month(), - date: m.date(), - hours: m.hours(), - minutes: m.minutes(), - seconds: m.seconds(), - milliseconds: m.milliseconds() - }; - } - - function toJSON () { - // new Date(NaN).toJSON() === null - return this.isValid() ? this.toISOString() : null; - } - - function isValid$2 () { - return isValid(this); - } - - function parsingFlags () { - return extend({}, getParsingFlags(this)); - } - - function invalidAt () { - return getParsingFlags(this).overflow; - } - - function creationData() { - return { - input: this._i, - format: this._f, - locale: this._locale, - isUTC: this._isUTC, - strict: this._strict - }; - } - - // FORMATTING - - addFormatToken(0, ['gg', 2], 0, function () { - return this.weekYear() % 100; - }); - - addFormatToken(0, ['GG', 2], 0, function () { - return this.isoWeekYear() % 100; - }); - - function addWeekYearFormatToken (token, getter) { - addFormatToken(0, [token, token.length], 0, getter); - } - - addWeekYearFormatToken('gggg', 'weekYear'); - addWeekYearFormatToken('ggggg', 'weekYear'); - addWeekYearFormatToken('GGGG', 'isoWeekYear'); - addWeekYearFormatToken('GGGGG', 'isoWeekYear'); - - // ALIASES - - addUnitAlias('weekYear', 'gg'); - addUnitAlias('isoWeekYear', 'GG'); - - // PRIORITY - - addUnitPriority('weekYear', 1); - addUnitPriority('isoWeekYear', 1); - - - // PARSING - - addRegexToken('G', matchSigned); - addRegexToken('g', matchSigned); - addRegexToken('GG', match1to2, match2); - addRegexToken('gg', match1to2, match2); - addRegexToken('GGGG', match1to4, match4); - addRegexToken('gggg', match1to4, match4); - addRegexToken('GGGGG', match1to6, match6); - addRegexToken('ggggg', match1to6, match6); - - addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { - week[token.substr(0, 2)] = toInt(input); - }); - - addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { - week[token] = hooks.parseTwoDigitYear(input); - }); - - // MOMENTS - - function getSetWeekYear (input) { - return getSetWeekYearHelper.call(this, - input, - this.week(), - this.weekday(), - this.localeData()._week.dow, - this.localeData()._week.doy); - } - - function getSetISOWeekYear (input) { - return getSetWeekYearHelper.call(this, - input, this.isoWeek(), this.isoWeekday(), 1, 4); - } - - function getISOWeeksInYear () { - return weeksInYear(this.year(), 1, 4); - } - - function getWeeksInYear () { - var weekInfo = this.localeData()._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); - } - - function getSetWeekYearHelper(input, week, weekday, dow, doy) { - var weeksTarget; - if (input == null) { - return weekOfYear(this, dow, doy).year; - } else { - weeksTarget = weeksInYear(input, dow, doy); - if (week > weeksTarget) { - week = weeksTarget; - } - return setWeekAll.call(this, input, week, weekday, dow, doy); - } - } - - function setWeekAll(weekYear, week, weekday, dow, doy) { - var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), - date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); - - this.year(date.getUTCFullYear()); - this.month(date.getUTCMonth()); - this.date(date.getUTCDate()); - return this; - } - - // FORMATTING - - addFormatToken('Q', 0, 'Qo', 'quarter'); - - // ALIASES - - addUnitAlias('quarter', 'Q'); - - // PRIORITY - - addUnitPriority('quarter', 7); - - // PARSING - - addRegexToken('Q', match1); - addParseToken('Q', function (input, array) { - array[MONTH] = (toInt(input) - 1) * 3; - }); - - // MOMENTS - - function getSetQuarter (input) { - return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); - } - - // FORMATTING - - addFormatToken('D', ['DD', 2], 'Do', 'date'); - - // ALIASES - - addUnitAlias('date', 'D'); - - // PRIORITY - addUnitPriority('date', 9); - - // PARSING - - addRegexToken('D', match1to2); - addRegexToken('DD', match1to2, match2); - addRegexToken('Do', function (isStrict, locale) { - // TODO: Remove "ordinalParse" fallback in next major release. - return isStrict ? - (locale._dayOfMonthOrdinalParse || locale._ordinalParse) : - locale._dayOfMonthOrdinalParseLenient; - }); - - addParseToken(['D', 'DD'], DATE); - addParseToken('Do', function (input, array) { - array[DATE] = toInt(input.match(match1to2)[0]); - }); - - // MOMENTS - - var getSetDayOfMonth = makeGetSet('Date', true); - - // FORMATTING - - addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); - - // ALIASES - - addUnitAlias('dayOfYear', 'DDD'); - - // PRIORITY - addUnitPriority('dayOfYear', 4); - - // PARSING - - addRegexToken('DDD', match1to3); - addRegexToken('DDDD', match3); - addParseToken(['DDD', 'DDDD'], function (input, array, config) { - config._dayOfYear = toInt(input); - }); - - // HELPERS - - // MOMENTS - - function getSetDayOfYear (input) { - var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; - return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); - } - - // FORMATTING - - addFormatToken('m', ['mm', 2], 0, 'minute'); - - // ALIASES - - addUnitAlias('minute', 'm'); - - // PRIORITY - - addUnitPriority('minute', 14); - - // PARSING - - addRegexToken('m', match1to2); - addRegexToken('mm', match1to2, match2); - addParseToken(['m', 'mm'], MINUTE); - - // MOMENTS - - var getSetMinute = makeGetSet('Minutes', false); - - // FORMATTING - - addFormatToken('s', ['ss', 2], 0, 'second'); - - // ALIASES - - addUnitAlias('second', 's'); - - // PRIORITY - - addUnitPriority('second', 15); - - // PARSING - - addRegexToken('s', match1to2); - addRegexToken('ss', match1to2, match2); - addParseToken(['s', 'ss'], SECOND); - - // MOMENTS - - var getSetSecond = makeGetSet('Seconds', false); - - // FORMATTING - - addFormatToken('S', 0, 0, function () { - return ~~(this.millisecond() / 100); - }); - - addFormatToken(0, ['SS', 2], 0, function () { - return ~~(this.millisecond() / 10); - }); - - addFormatToken(0, ['SSS', 3], 0, 'millisecond'); - addFormatToken(0, ['SSSS', 4], 0, function () { - return this.millisecond() * 10; - }); - addFormatToken(0, ['SSSSS', 5], 0, function () { - return this.millisecond() * 100; - }); - addFormatToken(0, ['SSSSSS', 6], 0, function () { - return this.millisecond() * 1000; - }); - addFormatToken(0, ['SSSSSSS', 7], 0, function () { - return this.millisecond() * 10000; - }); - addFormatToken(0, ['SSSSSSSS', 8], 0, function () { - return this.millisecond() * 100000; - }); - addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { - return this.millisecond() * 1000000; - }); - - - // ALIASES - - addUnitAlias('millisecond', 'ms'); - - // PRIORITY - - addUnitPriority('millisecond', 16); - - // PARSING - - addRegexToken('S', match1to3, match1); - addRegexToken('SS', match1to3, match2); - addRegexToken('SSS', match1to3, match3); - - var token; - for (token = 'SSSS'; token.length <= 9; token += 'S') { - addRegexToken(token, matchUnsigned); - } - - function parseMs(input, array) { - array[MILLISECOND] = toInt(('0.' + input) * 1000); - } - - for (token = 'S'; token.length <= 9; token += 'S') { - addParseToken(token, parseMs); - } - // MOMENTS - - var getSetMillisecond = makeGetSet('Milliseconds', false); - - // FORMATTING - - addFormatToken('z', 0, 0, 'zoneAbbr'); - addFormatToken('zz', 0, 0, 'zoneName'); - - // MOMENTS - - function getZoneAbbr () { - return this._isUTC ? 'UTC' : ''; - } - - function getZoneName () { - return this._isUTC ? 'Coordinated Universal Time' : ''; - } - - var proto = Moment.prototype; - - proto.add = add; - proto.calendar = calendar$1; - proto.clone = clone; - proto.diff = diff; - proto.endOf = endOf; - proto.format = format; - proto.from = from; - proto.fromNow = fromNow; - proto.to = to; - proto.toNow = toNow; - proto.get = stringGet; - proto.invalidAt = invalidAt; - proto.isAfter = isAfter; - proto.isBefore = isBefore; - proto.isBetween = isBetween; - proto.isSame = isSame; - proto.isSameOrAfter = isSameOrAfter; - proto.isSameOrBefore = isSameOrBefore; - proto.isValid = isValid$2; - proto.lang = lang; - proto.locale = locale; - proto.localeData = localeData; - proto.max = prototypeMax; - proto.min = prototypeMin; - proto.parsingFlags = parsingFlags; - proto.set = stringSet; - proto.startOf = startOf; - proto.subtract = subtract; - proto.toArray = toArray; - proto.toObject = toObject; - proto.toDate = toDate; - proto.toISOString = toISOString; - proto.inspect = inspect; - proto.toJSON = toJSON; - proto.toString = toString; - proto.unix = unix; - proto.valueOf = valueOf; - proto.creationData = creationData; - proto.year = getSetYear; - proto.isLeapYear = getIsLeapYear; - proto.weekYear = getSetWeekYear; - proto.isoWeekYear = getSetISOWeekYear; - proto.quarter = proto.quarters = getSetQuarter; - proto.month = getSetMonth; - proto.daysInMonth = getDaysInMonth; - proto.week = proto.weeks = getSetWeek; - proto.isoWeek = proto.isoWeeks = getSetISOWeek; - proto.weeksInYear = getWeeksInYear; - proto.isoWeeksInYear = getISOWeeksInYear; - proto.date = getSetDayOfMonth; - proto.day = proto.days = getSetDayOfWeek; - proto.weekday = getSetLocaleDayOfWeek; - proto.isoWeekday = getSetISODayOfWeek; - proto.dayOfYear = getSetDayOfYear; - proto.hour = proto.hours = getSetHour; - proto.minute = proto.minutes = getSetMinute; - proto.second = proto.seconds = getSetSecond; - proto.millisecond = proto.milliseconds = getSetMillisecond; - proto.utcOffset = getSetOffset; - proto.utc = setOffsetToUTC; - proto.local = setOffsetToLocal; - proto.parseZone = setOffsetToParsedOffset; - proto.hasAlignedHourOffset = hasAlignedHourOffset; - proto.isDST = isDaylightSavingTime; - proto.isLocal = isLocal; - proto.isUtcOffset = isUtcOffset; - proto.isUtc = isUtc; - proto.isUTC = isUtc; - proto.zoneAbbr = getZoneAbbr; - proto.zoneName = getZoneName; - proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); - proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); - proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); - proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); - proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); - - function createUnix (input) { - return createLocal(input * 1000); - } - - function createInZone () { - return createLocal.apply(null, arguments).parseZone(); - } - - function preParsePostFormat (string) { - return string; - } - - var proto$1 = Locale.prototype; - - proto$1.calendar = calendar; - proto$1.longDateFormat = longDateFormat; - proto$1.invalidDate = invalidDate; - proto$1.ordinal = ordinal; - proto$1.preparse = preParsePostFormat; - proto$1.postformat = preParsePostFormat; - proto$1.relativeTime = relativeTime; - proto$1.pastFuture = pastFuture; - proto$1.set = set; - - proto$1.months = localeMonths; - proto$1.monthsShort = localeMonthsShort; - proto$1.monthsParse = localeMonthsParse; - proto$1.monthsRegex = monthsRegex; - proto$1.monthsShortRegex = monthsShortRegex; - proto$1.week = localeWeek; - proto$1.firstDayOfYear = localeFirstDayOfYear; - proto$1.firstDayOfWeek = localeFirstDayOfWeek; - - proto$1.weekdays = localeWeekdays; - proto$1.weekdaysMin = localeWeekdaysMin; - proto$1.weekdaysShort = localeWeekdaysShort; - proto$1.weekdaysParse = localeWeekdaysParse; - - proto$1.weekdaysRegex = weekdaysRegex; - proto$1.weekdaysShortRegex = weekdaysShortRegex; - proto$1.weekdaysMinRegex = weekdaysMinRegex; - - proto$1.isPM = localeIsPM; - proto$1.meridiem = localeMeridiem; - - function get$1 (format, index, field, setter) { - var locale = getLocale(); - var utc = createUTC().set(setter, index); - return locale[field](utc, format); - } - - function listMonthsImpl (format, index, field) { - if (isNumber(format)) { - index = format; - format = undefined; - } - - format = format || ''; - - if (index != null) { - return get$1(format, index, field, 'month'); - } - - var i; - var out = []; - for (i = 0; i < 12; i++) { - out[i] = get$1(format, i, field, 'month'); - } - return out; - } - - // () - // (5) - // (fmt, 5) - // (fmt) - // (true) - // (true, 5) - // (true, fmt, 5) - // (true, fmt) - function listWeekdaysImpl (localeSorted, format, index, field) { - if (typeof localeSorted === 'boolean') { - if (isNumber(format)) { - index = format; - format = undefined; - } - - format = format || ''; - } else { - format = localeSorted; - index = format; - localeSorted = false; - - if (isNumber(format)) { - index = format; - format = undefined; - } - - format = format || ''; - } - - var locale = getLocale(), - shift = localeSorted ? locale._week.dow : 0; - - if (index != null) { - return get$1(format, (index + shift) % 7, field, 'day'); - } - - var i; - var out = []; - for (i = 0; i < 7; i++) { - out[i] = get$1(format, (i + shift) % 7, field, 'day'); - } - return out; - } - - function listMonths (format, index) { - return listMonthsImpl(format, index, 'months'); - } - - function listMonthsShort (format, index) { - return listMonthsImpl(format, index, 'monthsShort'); - } - - function listWeekdays (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); - } - - function listWeekdaysShort (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); - } - - function listWeekdaysMin (localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); - } - - getSetGlobalLocale('en', { - dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, - ordinal : function (number) { - var b = number % 10, - output = (toInt(number % 100 / 10) === 1) ? 'th' : - (b === 1) ? 'st' : - (b === 2) ? 'nd' : - (b === 3) ? 'rd' : 'th'; - return number + output; - } - }); - - // Side effect imports - - hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); - hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); - - var mathAbs = Math.abs; - - function abs () { - var data = this._data; - - this._milliseconds = mathAbs(this._milliseconds); - this._days = mathAbs(this._days); - this._months = mathAbs(this._months); - - data.milliseconds = mathAbs(data.milliseconds); - data.seconds = mathAbs(data.seconds); - data.minutes = mathAbs(data.minutes); - data.hours = mathAbs(data.hours); - data.months = mathAbs(data.months); - data.years = mathAbs(data.years); - - return this; - } - - function addSubtract$1 (duration, input, value, direction) { - var other = createDuration(input, value); - - duration._milliseconds += direction * other._milliseconds; - duration._days += direction * other._days; - duration._months += direction * other._months; - - return duration._bubble(); - } - - // supports only 2.0-style add(1, 's') or add(duration) - function add$1 (input, value) { - return addSubtract$1(this, input, value, 1); - } - - // supports only 2.0-style subtract(1, 's') or subtract(duration) - function subtract$1 (input, value) { - return addSubtract$1(this, input, value, -1); - } - - function absCeil (number) { - if (number < 0) { - return Math.floor(number); - } else { - return Math.ceil(number); - } - } - - function bubble () { - var milliseconds = this._milliseconds; - var days = this._days; - var months = this._months; - var data = this._data; - var seconds, minutes, hours, years, monthsFromDays; - - // if we have a mix of positive and negative values, bubble down first - // check: https://github.com/moment/moment/issues/2166 - if (!((milliseconds >= 0 && days >= 0 && months >= 0) || - (milliseconds <= 0 && days <= 0 && months <= 0))) { - milliseconds += absCeil(monthsToDays(months) + days) * 864e5; - days = 0; - months = 0; - } - - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; - - seconds = absFloor(milliseconds / 1000); - data.seconds = seconds % 60; - - minutes = absFloor(seconds / 60); - data.minutes = minutes % 60; - - hours = absFloor(minutes / 60); - data.hours = hours % 24; - - days += absFloor(hours / 24); - - // convert days to months - monthsFromDays = absFloor(daysToMonths(days)); - months += monthsFromDays; - days -= absCeil(monthsToDays(monthsFromDays)); - - // 12 months -> 1 year - years = absFloor(months / 12); - months %= 12; - - data.days = days; - data.months = months; - data.years = years; - - return this; - } - - function daysToMonths (days) { - // 400 years have 146097 days (taking into account leap year rules) - // 400 years have 12 months === 4800 - return days * 4800 / 146097; - } - - function monthsToDays (months) { - // the reverse of daysToMonths - return months * 146097 / 4800; - } - - function as (units) { - if (!this.isValid()) { - return NaN; - } - var days; - var months; - var milliseconds = this._milliseconds; - - units = normalizeUnits(units); - - if (units === 'month' || units === 'quarter' || units === 'year') { - days = this._days + milliseconds / 864e5; - months = this._months + daysToMonths(days); - switch (units) { - case 'month': return months; - case 'quarter': return months / 3; - case 'year': return months / 12; - } - } else { - // handle milliseconds separately because of floating point math errors (issue #1867) - days = this._days + Math.round(monthsToDays(this._months)); - switch (units) { - case 'week' : return days / 7 + milliseconds / 6048e5; - case 'day' : return days + milliseconds / 864e5; - case 'hour' : return days * 24 + milliseconds / 36e5; - case 'minute' : return days * 1440 + milliseconds / 6e4; - case 'second' : return days * 86400 + milliseconds / 1000; - // Math.floor prevents floating point math errors here - case 'millisecond': return Math.floor(days * 864e5) + milliseconds; - default: throw new Error('Unknown unit ' + units); - } - } - } - - // TODO: Use this.as('ms')? - function valueOf$1 () { - if (!this.isValid()) { - return NaN; - } - return ( - this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6 - ); - } - - function makeAs (alias) { - return function () { - return this.as(alias); - }; - } - - var asMilliseconds = makeAs('ms'); - var asSeconds = makeAs('s'); - var asMinutes = makeAs('m'); - var asHours = makeAs('h'); - var asDays = makeAs('d'); - var asWeeks = makeAs('w'); - var asMonths = makeAs('M'); - var asQuarters = makeAs('Q'); - var asYears = makeAs('y'); - - function clone$1 () { - return createDuration(this); - } - - function get$2 (units) { - units = normalizeUnits(units); - return this.isValid() ? this[units + 's']() : NaN; - } - - function makeGetter(name) { - return function () { - return this.isValid() ? this._data[name] : NaN; - }; - } - - var milliseconds = makeGetter('milliseconds'); - var seconds = makeGetter('seconds'); - var minutes = makeGetter('minutes'); - var hours = makeGetter('hours'); - var days = makeGetter('days'); - var months = makeGetter('months'); - var years = makeGetter('years'); - - function weeks () { - return absFloor(this.days() / 7); - } - - var round = Math.round; - var thresholds = { - ss: 44, // a few seconds to seconds - s : 45, // seconds to minute - m : 45, // minutes to hour - h : 22, // hours to day - d : 26, // days to month - M : 11 // months to year - }; - - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { - return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); - } - - function relativeTime$1 (posNegDuration, withoutSuffix, locale) { - var duration = createDuration(posNegDuration).abs(); - var seconds = round(duration.as('s')); - var minutes = round(duration.as('m')); - var hours = round(duration.as('h')); - var days = round(duration.as('d')); - var months = round(duration.as('M')); - var years = round(duration.as('y')); - - var a = seconds <= thresholds.ss && ['s', seconds] || - seconds < thresholds.s && ['ss', seconds] || - minutes <= 1 && ['m'] || - minutes < thresholds.m && ['mm', minutes] || - hours <= 1 && ['h'] || - hours < thresholds.h && ['hh', hours] || - days <= 1 && ['d'] || - days < thresholds.d && ['dd', days] || - months <= 1 && ['M'] || - months < thresholds.M && ['MM', months] || - years <= 1 && ['y'] || ['yy', years]; - - a[2] = withoutSuffix; - a[3] = +posNegDuration > 0; - a[4] = locale; - return substituteTimeAgo.apply(null, a); - } - - // This function allows you to set the rounding function for relative time strings - function getSetRelativeTimeRounding (roundingFunction) { - if (roundingFunction === undefined) { - return round; - } - if (typeof(roundingFunction) === 'function') { - round = roundingFunction; - return true; - } - return false; - } - - // This function allows you to set a threshold for relative time strings - function getSetRelativeTimeThreshold (threshold, limit) { - if (thresholds[threshold] === undefined) { - return false; - } - if (limit === undefined) { - return thresholds[threshold]; - } - thresholds[threshold] = limit; - if (threshold === 's') { - thresholds.ss = limit - 1; - } - return true; - } - - function humanize (withSuffix) { - if (!this.isValid()) { - return this.localeData().invalidDate(); - } - - var locale = this.localeData(); - var output = relativeTime$1(this, !withSuffix, locale); - - if (withSuffix) { - output = locale.pastFuture(+this, output); - } - - return locale.postformat(output); - } - - var abs$1 = Math.abs; - - function sign(x) { - return ((x > 0) - (x < 0)) || +x; - } - - function toISOString$1() { - // for ISO strings we do not use the normal bubbling rules: - // * milliseconds bubble up until they become hours - // * days do not bubble at all - // * months bubble up until they become years - // This is because there is no context-free conversion between hours and days - // (think of clock changes) - // and also not between days and months (28-31 days per month) - if (!this.isValid()) { - return this.localeData().invalidDate(); - } - - var seconds = abs$1(this._milliseconds) / 1000; - var days = abs$1(this._days); - var months = abs$1(this._months); - var minutes, hours, years; - - // 3600 seconds -> 60 minutes -> 1 hour - minutes = absFloor(seconds / 60); - hours = absFloor(minutes / 60); - seconds %= 60; - minutes %= 60; - - // 12 months -> 1 year - years = absFloor(months / 12); - months %= 12; - - - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - var Y = years; - var M = months; - var D = days; - var h = hours; - var m = minutes; - var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : ''; - var total = this.asSeconds(); - - if (!total) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - var totalSign = total < 0 ? '-' : ''; - var ymSign = sign(this._months) !== sign(total) ? '-' : ''; - var daysSign = sign(this._days) !== sign(total) ? '-' : ''; - var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : ''; - - return totalSign + 'P' + - (Y ? ymSign + Y + 'Y' : '') + - (M ? ymSign + M + 'M' : '') + - (D ? daysSign + D + 'D' : '') + - ((h || m || s) ? 'T' : '') + - (h ? hmsSign + h + 'H' : '') + - (m ? hmsSign + m + 'M' : '') + - (s ? hmsSign + s + 'S' : ''); - } - - var proto$2 = Duration.prototype; - - proto$2.isValid = isValid$1; - proto$2.abs = abs; - proto$2.add = add$1; - proto$2.subtract = subtract$1; - proto$2.as = as; - proto$2.asMilliseconds = asMilliseconds; - proto$2.asSeconds = asSeconds; - proto$2.asMinutes = asMinutes; - proto$2.asHours = asHours; - proto$2.asDays = asDays; - proto$2.asWeeks = asWeeks; - proto$2.asMonths = asMonths; - proto$2.asQuarters = asQuarters; - proto$2.asYears = asYears; - proto$2.valueOf = valueOf$1; - proto$2._bubble = bubble; - proto$2.clone = clone$1; - proto$2.get = get$2; - proto$2.milliseconds = milliseconds; - proto$2.seconds = seconds; - proto$2.minutes = minutes; - proto$2.hours = hours; - proto$2.days = days; - proto$2.weeks = weeks; - proto$2.months = months; - proto$2.years = years; - proto$2.humanize = humanize; - proto$2.toISOString = toISOString$1; - proto$2.toString = toISOString$1; - proto$2.toJSON = toISOString$1; - proto$2.locale = locale; - proto$2.localeData = localeData; - - proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1); - proto$2.lang = lang; - - // Side effect imports - - // FORMATTING - - addFormatToken('X', 0, 0, 'unix'); - addFormatToken('x', 0, 0, 'valueOf'); - - // PARSING - - addRegexToken('x', matchSigned); - addRegexToken('X', matchTimestamp); - addParseToken('X', function (input, array, config) { - config._d = new Date(parseFloat(input, 10) * 1000); - }); - addParseToken('x', function (input, array, config) { - config._d = new Date(toInt(input)); - }); - - // Side effect imports - - - hooks.version = '2.24.0'; - - setHookCallback(createLocal); - - hooks.fn = proto; - hooks.min = min; - hooks.max = max; - hooks.now = now; - hooks.utc = createUTC; - hooks.unix = createUnix; - hooks.months = listMonths; - hooks.isDate = isDate; - hooks.locale = getSetGlobalLocale; - hooks.invalid = createInvalid; - hooks.duration = createDuration; - hooks.isMoment = isMoment; - hooks.weekdays = listWeekdays; - hooks.parseZone = createInZone; - hooks.localeData = getLocale; - hooks.isDuration = isDuration; - hooks.monthsShort = listMonthsShort; - hooks.weekdaysMin = listWeekdaysMin; - hooks.defineLocale = defineLocale; - hooks.updateLocale = updateLocale; - hooks.locales = listLocales; - hooks.weekdaysShort = listWeekdaysShort; - hooks.normalizeUnits = normalizeUnits; - hooks.relativeTimeRounding = getSetRelativeTimeRounding; - hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; - hooks.calendarFormat = getCalendarFormat; - hooks.prototype = proto; - - // currently HTML5 input type only supports 24-hour formats - hooks.HTML5_FMT = { - DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" /> - DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" /> - DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" /> - DATE: 'YYYY-MM-DD', // <input type="date" /> - TIME: 'HH:mm', // <input type="time" /> - TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" /> - TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" /> - WEEK: 'GGGG-[W]WW', // <input type="week" /> - MONTH: 'YYYY-MM' // <input type="month" /> - }; - - return hooks; - -}))); -}); - -var FORMATS = { - datetime: 'MMM D, YYYY, h:mm:ss a', - millisecond: 'h:mm:ss.SSS a', - second: 'h:mm:ss a', - minute: 'h:mm a', - hour: 'hA', - day: 'MMM D', - week: 'll', - month: 'MMM YYYY', - quarter: '[Q]Q - YYYY', - year: 'YYYY' -}; - -core_adapters._date.override(typeof moment === 'function' ? { - _id: 'moment', // DEBUG ONLY - - formats: function() { - return FORMATS; - }, - - parse: function(value, format) { - if (typeof value === 'string' && typeof format === 'string') { - value = moment(value, format); - } else if (!(value instanceof moment)) { - value = moment(value); - } - return value.isValid() ? value.valueOf() : null; - }, - - format: function(time, format) { - return moment(time).format(format); - }, - - add: function(time, amount, unit) { - return moment(time).add(amount, unit).valueOf(); - }, - - diff: function(max, min, unit) { - return moment(max).diff(moment(min), unit); - }, - - startOf: function(time, unit, weekday) { - time = moment(time); - if (unit === 'isoWeek') { - return time.isoWeekday(weekday).valueOf(); - } - return time.startOf(unit).valueOf(); - }, - - endOf: function(time, unit) { - return moment(time).endOf(unit).valueOf(); - }, - - // DEPRECATIONS - - /** - * Provided for backward compatibility with scale.getValueForPixel(). - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ - _create: function(time) { - return moment(time); - }, -} : {}); - -core_defaults._set('global', { - plugins: { - filler: { - propagate: true - } - } -}); - -var mappers = { - dataset: function(source) { - var index = source.fill; - var chart = source.chart; - var meta = chart.getDatasetMeta(index); - var visible = meta && chart.isDatasetVisible(index); - var points = (visible && meta.dataset._children) || []; - var length = points.length || 0; - - return !length ? null : function(point, i) { - return (i < length && points[i]._view) || null; - }; - }, - - boundary: function(source) { - var boundary = source.boundary; - var x = boundary ? boundary.x : null; - var y = boundary ? boundary.y : null; - - if (helpers$1.isArray(boundary)) { - return function(point, i) { - return boundary[i]; - }; - } - - return function(point) { - return { - x: x === null ? point.x : x, - y: y === null ? point.y : y, - }; - }; - } -}; - -// @todo if (fill[0] === '#') -function decodeFill(el, index, count) { - var model = el._model || {}; - var fill = model.fill; - var target; - - if (fill === undefined) { - fill = !!model.backgroundColor; - } - - if (fill === false || fill === null) { - return false; - } - - if (fill === true) { - return 'origin'; - } - - target = parseFloat(fill, 10); - if (isFinite(target) && Math.floor(target) === target) { - if (fill[0] === '-' || fill[0] === '+') { - target = index + target; - } - - if (target === index || target < 0 || target >= count) { - return false; - } - - return target; - } - - switch (fill) { - // compatibility - case 'bottom': - return 'start'; - case 'top': - return 'end'; - case 'zero': - return 'origin'; - // supported boundaries - case 'origin': - case 'start': - case 'end': - return fill; - // invalid fill values - default: - return false; - } -} - -function computeLinearBoundary(source) { - var model = source.el._model || {}; - var scale = source.el._scale || {}; - var fill = source.fill; - var target = null; - var horizontal; - - if (isFinite(fill)) { - return null; - } - - // Backward compatibility: until v3, we still need to support boundary values set on - // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and - // controllers might still use it (e.g. the Smith chart). - - if (fill === 'start') { - target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; - } else if (fill === 'end') { - target = model.scaleTop === undefined ? scale.top : model.scaleTop; - } else if (model.scaleZero !== undefined) { - target = model.scaleZero; - } else if (scale.getBasePixel) { - target = scale.getBasePixel(); - } - - if (target !== undefined && target !== null) { - if (target.x !== undefined && target.y !== undefined) { - return target; - } - - if (helpers$1.isFinite(target)) { - horizontal = scale.isHorizontal(); - return { - x: horizontal ? target : null, - y: horizontal ? null : target - }; - } - } - - return null; -} - -function computeCircularBoundary(source) { - var scale = source.el._scale; - var options = scale.options; - var length = scale.chart.data.labels.length; - var fill = source.fill; - var target = []; - var start, end, center, i, point; - - if (!length) { - return null; - } - - start = options.ticks.reverse ? scale.max : scale.min; - end = options.ticks.reverse ? scale.min : scale.max; - center = scale.getPointPositionForValue(0, start); - for (i = 0; i < length; ++i) { - point = fill === 'start' || fill === 'end' - ? scale.getPointPositionForValue(i, fill === 'start' ? start : end) - : scale.getBasePosition(i); - if (options.gridLines.circular) { - point.cx = center.x; - point.cy = center.y; - point.angle = scale.getIndexAngle(i) - Math.PI / 2; - } - target.push(point); - } - return target; -} - -function computeBoundary(source) { - var scale = source.el._scale || {}; - - if (scale.getPointPositionForValue) { - return computeCircularBoundary(source); - } - return computeLinearBoundary(source); -} - -function resolveTarget(sources, index, propagate) { - var source = sources[index]; - var fill = source.fill; - var visited = [index]; - var target; - - if (!propagate) { - return fill; - } - - while (fill !== false && visited.indexOf(fill) === -1) { - if (!isFinite(fill)) { - return fill; - } - - target = sources[fill]; - if (!target) { - return false; - } - - if (target.visible) { - return fill; - } - - visited.push(fill); - fill = target.fill; - } - - return false; -} - -function createMapper(source) { - var fill = source.fill; - var type = 'dataset'; - - if (fill === false) { - return null; - } - - if (!isFinite(fill)) { - type = 'boundary'; - } - - return mappers[type](source); -} - -function isDrawable(point) { - return point && !point.skip; -} - -function drawArea(ctx, curve0, curve1, len0, len1) { - var i, cx, cy, r; - - if (!len0 || !len1) { - return; - } - - // building first area curve (normal) - ctx.moveTo(curve0[0].x, curve0[0].y); - for (i = 1; i < len0; ++i) { - helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); - } - - if (curve1[0].angle !== undefined) { - cx = curve1[0].cx; - cy = curve1[0].cy; - r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2)); - for (i = len1 - 1; i > 0; --i) { - ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true); - } - return; - } - - // joining the two area curves - ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); - - // building opposite area curve (reverse) - for (i = len1 - 1; i > 0; --i) { - helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); - } -} - -function doFill(ctx, points, mapper, view, color, loop) { - var count = points.length; - var span = view.spanGaps; - var curve0 = []; - var curve1 = []; - var len0 = 0; - var len1 = 0; - var i, ilen, index, p0, p1, d0, d1, loopOffset; - - ctx.beginPath(); - - for (i = 0, ilen = count; i < ilen; ++i) { - index = i % count; - p0 = points[index]._view; - p1 = mapper(p0, index, view); - d0 = isDrawable(p0); - d1 = isDrawable(p1); - - if (loop && loopOffset === undefined && d0) { - loopOffset = i + 1; - ilen = count + loopOffset; - } - - if (d0 && d1) { - len0 = curve0.push(p0); - len1 = curve1.push(p1); - } else if (len0 && len1) { - if (!span) { - drawArea(ctx, curve0, curve1, len0, len1); - len0 = len1 = 0; - curve0 = []; - curve1 = []; - } else { - if (d0) { - curve0.push(p0); - } - if (d1) { - curve1.push(p1); - } - } - } - } - - drawArea(ctx, curve0, curve1, len0, len1); - - ctx.closePath(); - ctx.fillStyle = color; - ctx.fill(); -} - -var plugin_filler = { - id: 'filler', - - afterDatasetsUpdate: function(chart, options) { - var count = (chart.data.datasets || []).length; - var propagate = options.propagate; - var sources = []; - var meta, i, el, source; - - for (i = 0; i < count; ++i) { - meta = chart.getDatasetMeta(i); - el = meta.dataset; - source = null; - - if (el && el._model && el instanceof elements.Line) { - source = { - visible: chart.isDatasetVisible(i), - fill: decodeFill(el, i, count), - chart: chart, - el: el - }; - } - - meta.$filler = source; - sources.push(source); - } - - for (i = 0; i < count; ++i) { - source = sources[i]; - if (!source) { - continue; - } - - source.fill = resolveTarget(sources, i, propagate); - source.boundary = computeBoundary(source); - source.mapper = createMapper(source); - } - }, - - beforeDatasetsDraw: function(chart) { - var metasets = chart._getSortedVisibleDatasetMetas(); - var ctx = chart.ctx; - var meta, i, el, view, points, mapper, color; - - for (i = metasets.length - 1; i >= 0; --i) { - meta = metasets[i].$filler; - - if (!meta || !meta.visible) { - continue; - } - - el = meta.el; - view = el._view; - points = el._children || []; - mapper = meta.mapper; - color = view.backgroundColor || core_defaults.global.defaultColor; - - if (mapper && color && points.length) { - helpers$1.canvas.clipArea(ctx, chart.chartArea); - doFill(ctx, points, mapper, view, color, el._loop); - helpers$1.canvas.unclipArea(ctx); - } - } - } -}; - -var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter; -var noop$1 = helpers$1.noop; -var valueOrDefault$e = helpers$1.valueOrDefault; - -core_defaults._set('global', { - legend: { - display: true, - position: 'top', - align: 'center', - fullWidth: true, - reverse: false, - weight: 1000, - - // a callback that will handle - onClick: function(e, legendItem) { - var index = legendItem.datasetIndex; - var ci = this.chart; - var meta = ci.getDatasetMeta(index); - - // See controller.isDatasetVisible comment - meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; - - // We hid a dataset ... rerender the chart - ci.update(); - }, - - onHover: null, - onLeave: null, - - labels: { - boxWidth: 40, - padding: 10, - // Generates labels shown in the legend - // Valid properties to return: - // text : text to display - // fillStyle : fill of coloured box - // strokeStyle: stroke of coloured box - // hidden : if this legend item refers to a hidden item - // lineCap : cap style for line - // lineDash - // lineDashOffset : - // lineJoin : - // lineWidth : - generateLabels: function(chart) { - var datasets = chart.data.datasets; - var options = chart.options.legend || {}; - var usePointStyle = options.labels && options.labels.usePointStyle; - - return chart._getSortedDatasetMetas().map(function(meta) { - var style = meta.controller.getStyle(usePointStyle ? 0 : undefined); - - return { - text: datasets[meta.index].label, - fillStyle: style.backgroundColor, - hidden: !chart.isDatasetVisible(meta.index), - lineCap: style.borderCapStyle, - lineDash: style.borderDash, - lineDashOffset: style.borderDashOffset, - lineJoin: style.borderJoinStyle, - lineWidth: style.borderWidth, - strokeStyle: style.borderColor, - pointStyle: style.pointStyle, - rotation: style.rotation, - - // Below is extra data used for toggling the datasets - datasetIndex: meta.index - }; - }, this); - } - } - }, - - legendCallback: function(chart) { - var list = document.createElement('ul'); - var datasets = chart.data.datasets; - var i, ilen, listItem, listItemSpan; - - list.setAttribute('class', chart.id + '-legend'); - - for (i = 0, ilen = datasets.length; i < ilen; i++) { - listItem = list.appendChild(document.createElement('li')); - listItemSpan = listItem.appendChild(document.createElement('span')); - listItemSpan.style.backgroundColor = datasets[i].backgroundColor; - if (datasets[i].label) { - listItem.appendChild(document.createTextNode(datasets[i].label)); - } - } - - return list.outerHTML; - } -}); - -/** - * Helper function to get the box width based on the usePointStyle option - * @param {object} labelopts - the label options on the legend - * @param {number} fontSize - the label font size - * @return {number} width of the color box area - */ -function getBoxWidth(labelOpts, fontSize) { - return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ? - fontSize : - labelOpts.boxWidth; -} - -/** - * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! - */ -var Legend = core_element.extend({ - - initialize: function(config) { - var me = this; - helpers$1.extend(me, config); - - // Contains hit boxes for each dataset (in dataset order) - me.legendHitBoxes = []; - - /** - * @private - */ - me._hoveredItem = null; - - // Are we in doughnut mode which has a different data type - me.doughnutMode = false; - }, - - // These methods are ordered by lifecycle. Utilities then follow. - // Any function defined here is inherited by all legend types. - // Any function can be extended by the legend type - - beforeUpdate: noop$1, - update: function(maxWidth, maxHeight, margins) { - var me = this; - - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); - - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = margins; - - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - // Labels - me.beforeBuildLabels(); - me.buildLabels(); - me.afterBuildLabels(); - - // Fit - me.beforeFit(); - me.fit(); - me.afterFit(); - // - me.afterUpdate(); - - return me.minSize; - }, - afterUpdate: noop$1, - - // - - beforeSetDimensions: noop$1, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; - - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } - - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; - - // Reset minSize - me.minSize = { - width: 0, - height: 0 - }; - }, - afterSetDimensions: noop$1, - - // - - beforeBuildLabels: noop$1, - buildLabels: function() { - var me = this; - var labelOpts = me.options.labels || {}; - var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || []; - - if (labelOpts.filter) { - legendItems = legendItems.filter(function(item) { - return labelOpts.filter(item, me.chart.data); - }); - } - - if (me.options.reverse) { - legendItems.reverse(); - } - - me.legendItems = legendItems; - }, - afterBuildLabels: noop$1, - - // - - beforeFit: noop$1, - fit: function() { - var me = this; - var opts = me.options; - var labelOpts = opts.labels; - var display = opts.display; - - var ctx = me.ctx; - - var labelFont = helpers$1.options._parseFont(labelOpts); - var fontSize = labelFont.size; - - // Reset hit boxes - var hitboxes = me.legendHitBoxes = []; - - var minSize = me.minSize; - var isHorizontal = me.isHorizontal(); - - if (isHorizontal) { - minSize.width = me.maxWidth; // fill all the width - minSize.height = display ? 10 : 0; - } else { - minSize.width = display ? 10 : 0; - minSize.height = me.maxHeight; // fill all the height - } - - // Increase sizes here - if (!display) { - me.width = minSize.width = me.height = minSize.height = 0; - return; - } - ctx.font = labelFont.string; - - if (isHorizontal) { - // Labels - - // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one - var lineWidths = me.lineWidths = [0]; - var totalHeight = 0; - - ctx.textAlign = 'left'; - ctx.textBaseline = 'middle'; - - helpers$1.each(me.legendItems, function(legendItem, i) { - var boxWidth = getBoxWidth(labelOpts, fontSize); - var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; - - if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) { - totalHeight += fontSize + labelOpts.padding; - lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; - } - - // Store the hitbox width and height here. Final position will be updated in `draw` - hitboxes[i] = { - left: 0, - top: 0, - width: width, - height: fontSize - }; - - lineWidths[lineWidths.length - 1] += width + labelOpts.padding; - }); - - minSize.height += totalHeight; - - } else { - var vPadding = labelOpts.padding; - var columnWidths = me.columnWidths = []; - var columnHeights = me.columnHeights = []; - var totalWidth = labelOpts.padding; - var currentColWidth = 0; - var currentColHeight = 0; - - helpers$1.each(me.legendItems, function(legendItem, i) { - var boxWidth = getBoxWidth(labelOpts, fontSize); - var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; - - // If too tall, go to new column - if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) { - totalWidth += currentColWidth + labelOpts.padding; - columnWidths.push(currentColWidth); // previous column width - columnHeights.push(currentColHeight); - currentColWidth = 0; - currentColHeight = 0; - } - - // Get max width - currentColWidth = Math.max(currentColWidth, itemWidth); - currentColHeight += fontSize + vPadding; - - // Store the hitbox width and height here. Final position will be updated in `draw` - hitboxes[i] = { - left: 0, - top: 0, - width: itemWidth, - height: fontSize - }; - }); - - totalWidth += currentColWidth; - columnWidths.push(currentColWidth); - columnHeights.push(currentColHeight); - minSize.width += totalWidth; - } - - me.width = minSize.width; - me.height = minSize.height; - }, - afterFit: noop$1, - - // Shared Methods - isHorizontal: function() { - return this.options.position === 'top' || this.options.position === 'bottom'; - }, - - // Actually draw the legend on the canvas - draw: function() { - var me = this; - var opts = me.options; - var labelOpts = opts.labels; - var globalDefaults = core_defaults.global; - var defaultColor = globalDefaults.defaultColor; - var lineDefault = globalDefaults.elements.line; - var legendHeight = me.height; - var columnHeights = me.columnHeights; - var legendWidth = me.width; - var lineWidths = me.lineWidths; - - if (!opts.display) { - return; - } - - var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width); - var ctx = me.ctx; - var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor); - var labelFont = helpers$1.options._parseFont(labelOpts); - var fontSize = labelFont.size; - var cursor; - - // Canvas setup - ctx.textAlign = rtlHelper.textAlign('left'); - ctx.textBaseline = 'middle'; - ctx.lineWidth = 0.5; - ctx.strokeStyle = fontColor; // for strikethrough effect - ctx.fillStyle = fontColor; // render in correct colour - ctx.font = labelFont.string; - - var boxWidth = getBoxWidth(labelOpts, fontSize); - var hitboxes = me.legendHitBoxes; - - // current position - var drawLegendBox = function(x, y, legendItem) { - if (isNaN(boxWidth) || boxWidth <= 0) { - return; - } - - // Set the ctx for the box - ctx.save(); - - var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth); - ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor); - ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle); - ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset); - ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle); - ctx.lineWidth = lineWidth; - ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor); - - if (ctx.setLineDash) { - // IE 9 and 10 do not support line dash - ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash)); - } - - if (labelOpts && labelOpts.usePointStyle) { - // Recalculate x and y for drawPoint() because its expecting - // x and y to be center of figure (instead of top left) - var radius = boxWidth * Math.SQRT2 / 2; - var centerX = rtlHelper.xPlus(x, boxWidth / 2); - var centerY = y + fontSize / 2; - - // Draw pointStyle as legend symbol - helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation); - } else { - // Draw box as legend symbol - ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); - if (lineWidth !== 0) { - ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); - } - } - - ctx.restore(); - }; - - var fillText = function(x, y, legendItem, textWidth) { - var halfFontSize = fontSize / 2; - var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize); - var yMiddle = y + halfFontSize; - - ctx.fillText(legendItem.text, xLeft, yMiddle); - - if (legendItem.hidden) { - // Strikethrough the text if hidden - ctx.beginPath(); - ctx.lineWidth = 2; - ctx.moveTo(xLeft, yMiddle); - ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle); - ctx.stroke(); - } - }; - - var alignmentOffset = function(dimension, blockSize) { - switch (opts.align) { - case 'start': - return labelOpts.padding; - case 'end': - return dimension - blockSize; - default: // center - return (dimension - blockSize + labelOpts.padding) / 2; - } - }; - - // Horizontal - var isHorizontal = me.isHorizontal(); - if (isHorizontal) { - cursor = { - x: me.left + alignmentOffset(legendWidth, lineWidths[0]), - y: me.top + labelOpts.padding, - line: 0 - }; - } else { - cursor = { - x: me.left + labelOpts.padding, - y: me.top + alignmentOffset(legendHeight, columnHeights[0]), - line: 0 - }; - } - - helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection); - - var itemHeight = fontSize + labelOpts.padding; - helpers$1.each(me.legendItems, function(legendItem, i) { - var textWidth = ctx.measureText(legendItem.text).width; - var width = boxWidth + (fontSize / 2) + textWidth; - var x = cursor.x; - var y = cursor.y; - - rtlHelper.setWidth(me.minSize.width); - - // Use (me.left + me.minSize.width) and (me.top + me.minSize.height) - // instead of me.right and me.bottom because me.width and me.height - // may have been changed since me.minSize was calculated - if (isHorizontal) { - if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) { - y = cursor.y += itemHeight; - cursor.line++; - x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]); - } - } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) { - x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; - cursor.line++; - y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]); - } - - var realX = rtlHelper.x(x); - - drawLegendBox(realX, y, legendItem); - - hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width); - hitboxes[i].top = y; - - // Fill the actual label - fillText(realX, y, legendItem, textWidth); - - if (isHorizontal) { - cursor.x += width + labelOpts.padding; - } else { - cursor.y += itemHeight; - } - }); - - helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection); - }, - - /** - * @private - */ - _getLegendItemAt: function(x, y) { - var me = this; - var i, hitBox, lh; - - if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { - // See if we are touching one of the dataset boxes - lh = me.legendHitBoxes; - for (i = 0; i < lh.length; ++i) { - hitBox = lh[i]; - - if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { - // Touching an element - return me.legendItems[i]; - } - } - } - - return null; - }, - - /** - * Handle an event - * @private - * @param {IEvent} event - The event to handle - */ - handleEvent: function(e) { - var me = this; - var opts = me.options; - var type = e.type === 'mouseup' ? 'click' : e.type; - var hoveredItem; - - if (type === 'mousemove') { - if (!opts.onHover && !opts.onLeave) { - return; - } - } else if (type === 'click') { - if (!opts.onClick) { - return; - } - } else { - return; - } - - // Chart event already has relative position in it - hoveredItem = me._getLegendItemAt(e.x, e.y); - - if (type === 'click') { - if (hoveredItem && opts.onClick) { - // use e.native for backwards compatibility - opts.onClick.call(me, e.native, hoveredItem); - } - } else { - if (opts.onLeave && hoveredItem !== me._hoveredItem) { - if (me._hoveredItem) { - opts.onLeave.call(me, e.native, me._hoveredItem); - } - me._hoveredItem = hoveredItem; - } - - if (opts.onHover && hoveredItem) { - // use e.native for backwards compatibility - opts.onHover.call(me, e.native, hoveredItem); - } - } - } -}); - -function createNewLegendAndAttach(chart, legendOpts) { - var legend = new Legend({ - ctx: chart.ctx, - options: legendOpts, - chart: chart - }); - - core_layouts.configure(chart, legend, legendOpts); - core_layouts.addBox(chart, legend); - chart.legend = legend; -} - -var plugin_legend = { - id: 'legend', - - /** - * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making - * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of - * the plugin, which one will be re-exposed in the chart.js file. - * https://github.com/chartjs/Chart.js/pull/2640 - * @private - */ - _element: Legend, - - beforeInit: function(chart) { - var legendOpts = chart.options.legend; - - if (legendOpts) { - createNewLegendAndAttach(chart, legendOpts); - } - }, - - beforeUpdate: function(chart) { - var legendOpts = chart.options.legend; - var legend = chart.legend; - - if (legendOpts) { - helpers$1.mergeIf(legendOpts, core_defaults.global.legend); - - if (legend) { - core_layouts.configure(chart, legend, legendOpts); - legend.options = legendOpts; - } else { - createNewLegendAndAttach(chart, legendOpts); - } - } else if (legend) { - core_layouts.removeBox(chart, legend); - delete chart.legend; - } - }, - - afterEvent: function(chart, e) { - var legend = chart.legend; - if (legend) { - legend.handleEvent(e); - } - } -}; - -var noop$2 = helpers$1.noop; - -core_defaults._set('global', { - title: { - display: false, - fontStyle: 'bold', - fullWidth: true, - padding: 10, - position: 'top', - text: '', - weight: 2000 // by default greater than legend (1000) to be above - } -}); - -/** - * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! - */ -var Title = core_element.extend({ - initialize: function(config) { - var me = this; - helpers$1.extend(me, config); - - // Contains hit boxes for each dataset (in dataset order) - me.legendHitBoxes = []; - }, - - // These methods are ordered by lifecycle. Utilities then follow. - - beforeUpdate: noop$2, - update: function(maxWidth, maxHeight, margins) { - var me = this; - - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); - - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = margins; - - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - // Labels - me.beforeBuildLabels(); - me.buildLabels(); - me.afterBuildLabels(); - - // Fit - me.beforeFit(); - me.fit(); - me.afterFit(); - // - me.afterUpdate(); - - return me.minSize; - - }, - afterUpdate: noop$2, - - // - - beforeSetDimensions: noop$2, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; - - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } - - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; - - // Reset minSize - me.minSize = { - width: 0, - height: 0 - }; - }, - afterSetDimensions: noop$2, - - // - - beforeBuildLabels: noop$2, - buildLabels: noop$2, - afterBuildLabels: noop$2, - - // - - beforeFit: noop$2, - fit: function() { - var me = this; - var opts = me.options; - var minSize = me.minSize = {}; - var isHorizontal = me.isHorizontal(); - var lineCount, textSize; - - if (!opts.display) { - me.width = minSize.width = me.height = minSize.height = 0; - return; - } - - lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1; - textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2; - - me.width = minSize.width = isHorizontal ? me.maxWidth : textSize; - me.height = minSize.height = isHorizontal ? textSize : me.maxHeight; - }, - afterFit: noop$2, - - // Shared Methods - isHorizontal: function() { - var pos = this.options.position; - return pos === 'top' || pos === 'bottom'; - }, - - // Actually draw the title block on the canvas - draw: function() { - var me = this; - var ctx = me.ctx; - var opts = me.options; - - if (!opts.display) { - return; - } - - var fontOpts = helpers$1.options._parseFont(opts); - var lineHeight = fontOpts.lineHeight; - var offset = lineHeight / 2 + opts.padding; - var rotation = 0; - var top = me.top; - var left = me.left; - var bottom = me.bottom; - var right = me.right; - var maxWidth, titleX, titleY; - - ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour - ctx.font = fontOpts.string; - - // Horizontal - if (me.isHorizontal()) { - titleX = left + ((right - left) / 2); // midpoint of the width - titleY = top + offset; - maxWidth = right - left; - } else { - titleX = opts.position === 'left' ? left + offset : right - offset; - titleY = top + ((bottom - top) / 2); - maxWidth = bottom - top; - rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); - } - - ctx.save(); - ctx.translate(titleX, titleY); - ctx.rotate(rotation); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - - var text = opts.text; - if (helpers$1.isArray(text)) { - var y = 0; - for (var i = 0; i < text.length; ++i) { - ctx.fillText(text[i], 0, y, maxWidth); - y += lineHeight; - } - } else { - ctx.fillText(text, 0, 0, maxWidth); - } - - ctx.restore(); - } -}); - -function createNewTitleBlockAndAttach(chart, titleOpts) { - var title = new Title({ - ctx: chart.ctx, - options: titleOpts, - chart: chart - }); - - core_layouts.configure(chart, title, titleOpts); - core_layouts.addBox(chart, title); - chart.titleBlock = title; -} - -var plugin_title = { - id: 'title', - - /** - * Backward compatibility: since 2.1.5, the title is registered as a plugin, making - * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of - * the plugin, which one will be re-exposed in the chart.js file. - * https://github.com/chartjs/Chart.js/pull/2640 - * @private - */ - _element: Title, - - beforeInit: function(chart) { - var titleOpts = chart.options.title; - - if (titleOpts) { - createNewTitleBlockAndAttach(chart, titleOpts); - } - }, - - beforeUpdate: function(chart) { - var titleOpts = chart.options.title; - var titleBlock = chart.titleBlock; - - if (titleOpts) { - helpers$1.mergeIf(titleOpts, core_defaults.global.title); - - if (titleBlock) { - core_layouts.configure(chart, titleBlock, titleOpts); - titleBlock.options = titleOpts; - } else { - createNewTitleBlockAndAttach(chart, titleOpts); - } - } else if (titleBlock) { - core_layouts.removeBox(chart, titleBlock); - delete chart.titleBlock; - } - } -}; - -var plugins = {}; -var filler = plugin_filler; -var legend = plugin_legend; -var title = plugin_title; -plugins.filler = filler; -plugins.legend = legend; -plugins.title = title; - -/** - * @namespace Chart - */ - - -core_controller.helpers = helpers$1; - -// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! -core_helpers(); - -core_controller._adapters = core_adapters; -core_controller.Animation = core_animation; -core_controller.animationService = core_animations; -core_controller.controllers = controllers; -core_controller.DatasetController = core_datasetController; -core_controller.defaults = core_defaults; -core_controller.Element = core_element; -core_controller.elements = elements; -core_controller.Interaction = core_interaction; -core_controller.layouts = core_layouts; -core_controller.platform = platform; -core_controller.plugins = core_plugins; -core_controller.Scale = core_scale; -core_controller.scaleService = core_scaleService; -core_controller.Ticks = core_ticks; -core_controller.Tooltip = core_tooltip; - -// Register built-in scales - -core_controller.helpers.each(scales, function(scale, type) { - core_controller.scaleService.registerScaleType(type, scale, scale._defaults); -}); - -// Load to register built-in adapters (as side effects) - - -// Loading built-in plugins - -for (var k in plugins) { - if (plugins.hasOwnProperty(k)) { - core_controller.plugins.register(plugins[k]); - } -} - -core_controller.platform.initialize(); - -var src = core_controller; -if (typeof window !== 'undefined') { - window.Chart = core_controller; -} - -// DEPRECATIONS - -/** - * Provided for backward compatibility, not available anymore - * @namespace Chart.Chart - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ -core_controller.Chart = core_controller; - -/** - * Provided for backward compatibility, not available anymore - * @namespace Chart.Legend - * @deprecated since version 2.1.5 - * @todo remove at version 3 - * @private - */ -core_controller.Legend = plugins.legend._element; - -/** - * Provided for backward compatibility, not available anymore - * @namespace Chart.Title - * @deprecated since version 2.1.5 - * @todo remove at version 3 - * @private - */ -core_controller.Title = plugins.title._element; - -/** - * Provided for backward compatibility, use Chart.plugins instead - * @namespace Chart.pluginService - * @deprecated since version 2.1.5 - * @todo remove at version 3 - * @private - */ -core_controller.pluginService = core_controller.plugins; - -/** - * Provided for backward compatibility, inheriting from Chart.PlugingBase has no - * effect, instead simply create/register plugins via plain JavaScript objects. - * @interface Chart.PluginBase - * @deprecated since version 2.5.0 - * @todo remove at version 3 - * @private - */ -core_controller.PluginBase = core_controller.Element.extend({}); - -/** - * Provided for backward compatibility, use Chart.helpers.canvas instead. - * @namespace Chart.canvasHelpers - * @deprecated since version 2.6.0 - * @todo remove at version 3 - * @private - */ -core_controller.canvasHelpers = core_controller.helpers.canvas; - -/** - * Provided for backward compatibility, use Chart.layouts instead. - * @namespace Chart.layoutService - * @deprecated since version 2.7.3 - * @todo remove at version 3 - * @private - */ -core_controller.layoutService = core_controller.layouts; - -/** - * Provided for backward compatibility, not available anymore. - * @namespace Chart.LinearScaleBase - * @deprecated since version 2.8 - * @todo remove at version 3 - * @private - */ -core_controller.LinearScaleBase = scale_linearbase; - -/** - * Provided for backward compatibility, instead we should create a new Chart - * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`). - * @deprecated since version 2.8.0 - * @todo remove at version 3 - */ -core_controller.helpers.each( - [ - 'Bar', - 'Bubble', - 'Doughnut', - 'Line', - 'PolarArea', - 'Radar', - 'Scatter' - ], - function(klass) { - core_controller[klass] = function(ctx, cfg) { - return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, { - type: klass.charAt(0).toLowerCase() + klass.slice(1) - })); - }; - } -); - -return src; - -}))); diff --git a/lib/web/chartjs/Chart.bundle.min.js b/lib/web/chartjs/Chart.bundle.min.js deleted file mode 100644 index 55d9eb03f821a..0000000000000 --- a/lib/web/chartjs/Chart.bundle.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Chart.js v2.9.3 - * https://www.chartjs.org - * (c) 2019 Chart.js Contributors - * Released under the MIT License - */ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Chart=e()}(this,(function(){"use strict";"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function t(){throw new Error("Dynamic requires are not currently supported by rollup-plugin-commonjs")}function e(t,e){return t(e={exports:{}},e.exports),e.exports}var n={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},i=e((function(t){var e={};for(var i in n)n.hasOwnProperty(i)&&(e[n[i]]=i);var a=t.exports={rgb:{channels:3,labels:"rgb"},hsl:{channels:3,labels:"hsl"},hsv:{channels:3,labels:"hsv"},hwb:{channels:3,labels:"hwb"},cmyk:{channels:4,labels:"cmyk"},xyz:{channels:3,labels:"xyz"},lab:{channels:3,labels:"lab"},lch:{channels:3,labels:"lch"},hex:{channels:1,labels:["hex"]},keyword:{channels:1,labels:["keyword"]},ansi16:{channels:1,labels:["ansi16"]},ansi256:{channels:1,labels:["ansi256"]},hcg:{channels:3,labels:["h","c","g"]},apple:{channels:3,labels:["r16","g16","b16"]},gray:{channels:1,labels:["gray"]}};for(var r in a)if(a.hasOwnProperty(r)){if(!("channels"in a[r]))throw new Error("missing channels property: "+r);if(!("labels"in a[r]))throw new Error("missing channel labels property: "+r);if(a[r].labels.length!==a[r].channels)throw new Error("channel and label counts mismatch: "+r);var o=a[r].channels,s=a[r].labels;delete a[r].channels,delete a[r].labels,Object.defineProperty(a[r],"channels",{value:o}),Object.defineProperty(a[r],"labels",{value:s})}a.rgb.hsl=function(t){var e,n,i=t[0]/255,a=t[1]/255,r=t[2]/255,o=Math.min(i,a,r),s=Math.max(i,a,r),l=s-o;return s===o?e=0:i===s?e=(a-r)/l:a===s?e=2+(r-i)/l:r===s&&(e=4+(i-a)/l),(e=Math.min(60*e,360))<0&&(e+=360),n=(o+s)/2,[e,100*(s===o?0:n<=.5?l/(s+o):l/(2-s-o)),100*n]},a.rgb.hsv=function(t){var e,n,i,a,r,o=t[0]/255,s=t[1]/255,l=t[2]/255,u=Math.max(o,s,l),d=u-Math.min(o,s,l),h=function(t){return(u-t)/6/d+.5};return 0===d?a=r=0:(r=d/u,e=h(o),n=h(s),i=h(l),o===u?a=i-n:s===u?a=1/3+e-i:l===u&&(a=2/3+n-e),a<0?a+=1:a>1&&(a-=1)),[360*a,100*r,100*u]},a.rgb.hwb=function(t){var e=t[0],n=t[1],i=t[2];return[a.rgb.hsl(t)[0],100*(1/255*Math.min(e,Math.min(n,i))),100*(i=1-1/255*Math.max(e,Math.max(n,i)))]},a.rgb.cmyk=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255;return[100*((1-n-(e=Math.min(1-n,1-i,1-a)))/(1-e)||0),100*((1-i-e)/(1-e)||0),100*((1-a-e)/(1-e)||0),100*e]},a.rgb.keyword=function(t){var i=e[t];if(i)return i;var a,r,o,s=1/0;for(var l in n)if(n.hasOwnProperty(l)){var u=n[l],d=(r=t,o=u,Math.pow(r[0]-o[0],2)+Math.pow(r[1]-o[1],2)+Math.pow(r[2]-o[2],2));d<s&&(s=d,a=l)}return a},a.keyword.rgb=function(t){return n[t]},a.rgb.xyz=function(t){var e=t[0]/255,n=t[1]/255,i=t[2]/255;return[100*(.4124*(e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)+.1805*(i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92)),100*(.2126*e+.7152*n+.0722*i),100*(.0193*e+.1192*n+.9505*i)]},a.rgb.lab=function(t){var e=a.rgb.xyz(t),n=e[0],i=e[1],r=e[2];return i/=100,r/=108.883,n=(n/=95.047)>.008856?Math.pow(n,1/3):7.787*n+16/116,[116*(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116)-16,500*(n-i),200*(i-(r=r>.008856?Math.pow(r,1/3):7.787*r+16/116))]},a.hsl.rgb=function(t){var e,n,i,a,r,o=t[0]/360,s=t[1]/100,l=t[2]/100;if(0===s)return[r=255*l,r,r];e=2*l-(n=l<.5?l*(1+s):l+s-l*s),a=[0,0,0];for(var u=0;u<3;u++)(i=o+1/3*-(u-1))<0&&i++,i>1&&i--,r=6*i<1?e+6*(n-e)*i:2*i<1?n:3*i<2?e+(n-e)*(2/3-i)*6:e,a[u]=255*r;return a},a.hsl.hsv=function(t){var e=t[0],n=t[1]/100,i=t[2]/100,a=n,r=Math.max(i,.01);return n*=(i*=2)<=1?i:2-i,a*=r<=1?r:2-r,[e,100*(0===i?2*a/(r+a):2*n/(i+n)),100*((i+n)/2)]},a.hsv.rgb=function(t){var e=t[0]/60,n=t[1]/100,i=t[2]/100,a=Math.floor(e)%6,r=e-Math.floor(e),o=255*i*(1-n),s=255*i*(1-n*r),l=255*i*(1-n*(1-r));switch(i*=255,a){case 0:return[i,l,o];case 1:return[s,i,o];case 2:return[o,i,l];case 3:return[o,s,i];case 4:return[l,o,i];case 5:return[i,o,s]}},a.hsv.hsl=function(t){var e,n,i,a=t[0],r=t[1]/100,o=t[2]/100,s=Math.max(o,.01);return i=(2-r)*o,n=r*s,[a,100*(n=(n/=(e=(2-r)*s)<=1?e:2-e)||0),100*(i/=2)]},a.hwb.rgb=function(t){var e,n,i,a,r,o,s,l=t[0]/360,u=t[1]/100,d=t[2]/100,h=u+d;switch(h>1&&(u/=h,d/=h),i=6*l-(e=Math.floor(6*l)),0!=(1&e)&&(i=1-i),a=u+i*((n=1-d)-u),e){default:case 6:case 0:r=n,o=a,s=u;break;case 1:r=a,o=n,s=u;break;case 2:r=u,o=n,s=a;break;case 3:r=u,o=a,s=n;break;case 4:r=a,o=u,s=n;break;case 5:r=n,o=u,s=a}return[255*r,255*o,255*s]},a.cmyk.rgb=function(t){var e=t[0]/100,n=t[1]/100,i=t[2]/100,a=t[3]/100;return[255*(1-Math.min(1,e*(1-a)+a)),255*(1-Math.min(1,n*(1-a)+a)),255*(1-Math.min(1,i*(1-a)+a))]},a.xyz.rgb=function(t){var e,n,i,a=t[0]/100,r=t[1]/100,o=t[2]/100;return n=-.9689*a+1.8758*r+.0415*o,i=.0557*a+-.204*r+1.057*o,e=(e=3.2406*a+-1.5372*r+-.4986*o)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:12.92*i,[255*(e=Math.min(Math.max(0,e),1)),255*(n=Math.min(Math.max(0,n),1)),255*(i=Math.min(Math.max(0,i),1))]},a.xyz.lab=function(t){var e=t[0],n=t[1],i=t[2];return n/=100,i/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(e-n),200*(n-(i=i>.008856?Math.pow(i,1/3):7.787*i+16/116))]},a.lab.xyz=function(t){var e,n,i,a=t[0];e=t[1]/500+(n=(a+16)/116),i=n-t[2]/200;var r=Math.pow(n,3),o=Math.pow(e,3),s=Math.pow(i,3);return n=r>.008856?r:(n-16/116)/7.787,e=o>.008856?o:(e-16/116)/7.787,i=s>.008856?s:(i-16/116)/7.787,[e*=95.047,n*=100,i*=108.883]},a.lab.lch=function(t){var e,n=t[0],i=t[1],a=t[2];return(e=360*Math.atan2(a,i)/2/Math.PI)<0&&(e+=360),[n,Math.sqrt(i*i+a*a),e]},a.lch.lab=function(t){var e,n=t[0],i=t[1];return e=t[2]/360*2*Math.PI,[n,i*Math.cos(e),i*Math.sin(e)]},a.rgb.ansi16=function(t){var e=t[0],n=t[1],i=t[2],r=1 in arguments?arguments[1]:a.rgb.hsv(t)[2];if(0===(r=Math.round(r/50)))return 30;var o=30+(Math.round(i/255)<<2|Math.round(n/255)<<1|Math.round(e/255));return 2===r&&(o+=60),o},a.hsv.ansi16=function(t){return a.rgb.ansi16(a.hsv.rgb(t),t[2])},a.rgb.ansi256=function(t){var e=t[0],n=t[1],i=t[2];return e===n&&n===i?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(n/255*5)+Math.round(i/255*5)},a.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var n=.5*(1+~~(t>50));return[(1&e)*n*255,(e>>1&1)*n*255,(e>>2&1)*n*255]},a.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var n;return t-=16,[Math.floor(t/36)/5*255,Math.floor((n=t%36)/6)/5*255,n%6/5*255]},a.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},a.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var n=e[0];3===e[0].length&&(n=n.split("").map((function(t){return t+t})).join(""));var i=parseInt(n,16);return[i>>16&255,i>>8&255,255&i]},a.rgb.hcg=function(t){var e,n=t[0]/255,i=t[1]/255,a=t[2]/255,r=Math.max(Math.max(n,i),a),o=Math.min(Math.min(n,i),a),s=r-o;return e=s<=0?0:r===n?(i-a)/s%6:r===i?2+(a-n)/s:4+(n-i)/s+4,e/=6,[360*(e%=1),100*s,100*(s<1?o/(1-s):0)]},a.hsl.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=1,a=0;return(i=n<.5?2*e*n:2*e*(1-n))<1&&(a=(n-.5*i)/(1-i)),[t[0],100*i,100*a]},a.hsv.hcg=function(t){var e=t[1]/100,n=t[2]/100,i=e*n,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.hcg.rgb=function(t){var e=t[0]/360,n=t[1]/100,i=t[2]/100;if(0===n)return[255*i,255*i,255*i];var a,r=[0,0,0],o=e%1*6,s=o%1,l=1-s;switch(Math.floor(o)){case 0:r[0]=1,r[1]=s,r[2]=0;break;case 1:r[0]=l,r[1]=1,r[2]=0;break;case 2:r[0]=0,r[1]=1,r[2]=s;break;case 3:r[0]=0,r[1]=l,r[2]=1;break;case 4:r[0]=s,r[1]=0,r[2]=1;break;default:r[0]=1,r[1]=0,r[2]=l}return a=(1-n)*i,[255*(n*r[0]+a),255*(n*r[1]+a),255*(n*r[2]+a)]},a.hcg.hsv=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e),i=0;return n>0&&(i=e/n),[t[0],100*i,100*n]},a.hcg.hsl=function(t){var e=t[1]/100,n=t[2]/100*(1-e)+.5*e,i=0;return n>0&&n<.5?i=e/(2*n):n>=.5&&n<1&&(i=e/(2*(1-n))),[t[0],100*i,100*n]},a.hcg.hwb=function(t){var e=t[1]/100,n=e+t[2]/100*(1-e);return[t[0],100*(n-e),100*(1-n)]},a.hwb.hcg=function(t){var e=t[1]/100,n=1-t[2]/100,i=n-e,a=0;return i<1&&(a=(n-i)/(1-i)),[t[0],100*i,100*a]},a.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},a.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},a.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},a.gray.hsl=a.gray.hsv=function(t){return[0,0,t[0]]},a.gray.hwb=function(t){return[0,100,t[0]]},a.gray.cmyk=function(t){return[0,0,0,t[0]]},a.gray.lab=function(t){return[t[0],0,0]},a.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),n=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(n.length)+n},a.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}}));i.rgb,i.hsl,i.hsv,i.hwb,i.cmyk,i.xyz,i.lab,i.lch,i.hex,i.keyword,i.ansi16,i.ansi256,i.hcg,i.apple,i.gray;function a(t){var e=function(){for(var t={},e=Object.keys(i),n=e.length,a=0;a<n;a++)t[e[a]]={distance:-1,parent:null};return t}(),n=[t];for(e[t].distance=0;n.length;)for(var a=n.pop(),r=Object.keys(i[a]),o=r.length,s=0;s<o;s++){var l=r[s],u=e[l];-1===u.distance&&(u.distance=e[a].distance+1,u.parent=a,n.unshift(l))}return e}function r(t,e){return function(n){return e(t(n))}}function o(t,e){for(var n=[e[t].parent,t],a=i[e[t].parent][t],o=e[t].parent;e[o].parent;)n.unshift(e[o].parent),a=r(i[e[o].parent][o],a),o=e[o].parent;return a.conversion=n,a}var s={};Object.keys(i).forEach((function(t){s[t]={},Object.defineProperty(s[t],"channels",{value:i[t].channels}),Object.defineProperty(s[t],"labels",{value:i[t].labels});var e=function(t){for(var e=a(t),n={},i=Object.keys(e),r=i.length,s=0;s<r;s++){var l=i[s];null!==e[l].parent&&(n[l]=o(l,e))}return n}(t);Object.keys(e).forEach((function(n){var i=e[n];s[t][n]=function(t){var e=function(e){if(null==e)return e;arguments.length>1&&(e=Array.prototype.slice.call(arguments));var n=t(e);if("object"==typeof n)for(var i=n.length,a=0;a<i;a++)n[a]=Math.round(n[a]);return n};return"conversion"in t&&(e.conversion=t.conversion),e}(i),s[t][n].raw=function(t){var e=function(e){return null==e?e:(arguments.length>1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}(i)}))}));var l=s,u={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},d={getRgba:h,getHsla:c,getRgb:function(t){var e=h(t);return e&&e.slice(0,3)},getHsl:function(t){var e=c(t);return e&&e.slice(0,3)},getHwb:f,getAlpha:function(t){var e=h(t);if(e)return e[3];if(e=c(t))return e[3];if(e=f(t))return e[3]},hexString:function(t,e){e=void 0!==e&&3===t.length?e:t[3];return"#"+b(t[0])+b(t[1])+b(t[2])+(e>=0&&e<1?b(Math.round(255*e)):"")},rgbString:function(t,e){if(e<1||t[3]&&t[3]<1)return g(t,e);return"rgb("+t[0]+", "+t[1]+", "+t[2]+")"},rgbaString:g,percentString:function(t,e){if(e<1||t[3]&&t[3]<1)return m(t,e);var n=Math.round(t[0]/255*100),i=Math.round(t[1]/255*100),a=Math.round(t[2]/255*100);return"rgb("+n+"%, "+i+"%, "+a+"%)"},percentaString:m,hslString:function(t,e){if(e<1||t[3]&&t[3]<1)return p(t,e);return"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"},hslaString:p,hwbString:function(t,e){void 0===e&&(e=void 0!==t[3]?t[3]:1);return"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"},keyword:function(t){return y[t.slice(0,3)]}};function h(t){if(t){var e=[0,0,0],n=1,i=t.match(/^#([a-fA-F0-9]{3,4})$/i),a="";if(i){a=(i=i[1])[3];for(var r=0;r<e.length;r++)e[r]=parseInt(i[r]+i[r],16);a&&(n=Math.round(parseInt(a+a,16)/255*100)/100)}else if(i=t.match(/^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i)){a=i[2],i=i[1];for(r=0;r<e.length;r++)e[r]=parseInt(i.slice(2*r,2*r+2),16);a&&(n=Math.round(parseInt(a,16)/255*100)/100)}else if(i=t.match(/^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i)){for(r=0;r<e.length;r++)e[r]=parseInt(i[r+1]);n=parseFloat(i[4])}else if(i=t.match(/^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i)){for(r=0;r<e.length;r++)e[r]=Math.round(2.55*parseFloat(i[r+1]));n=parseFloat(i[4])}else if(i=t.match(/(\w+)/)){if("transparent"==i[1])return[0,0,0,0];if(!(e=u[i[1]]))return}for(r=0;r<e.length;r++)e[r]=v(e[r],0,255);return n=n||0==n?v(n,0,1):1,e[3]=n,e}}function c(t){if(t){var e=t.match(/^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/);if(e){var n=parseFloat(e[4]);return[v(parseInt(e[1]),0,360),v(parseFloat(e[2]),0,100),v(parseFloat(e[3]),0,100),v(isNaN(n)?1:n,0,1)]}}}function f(t){if(t){var e=t.match(/^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/);if(e){var n=parseFloat(e[4]);return[v(parseInt(e[1]),0,360),v(parseFloat(e[2]),0,100),v(parseFloat(e[3]),0,100),v(isNaN(n)?1:n,0,1)]}}}function g(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function m(t,e){return"rgba("+Math.round(t[0]/255*100)+"%, "+Math.round(t[1]/255*100)+"%, "+Math.round(t[2]/255*100)+"%, "+(e||t[3]||1)+")"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function v(t,e,n){return Math.min(Math.max(e,t),n)}function b(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var y={};for(var x in u)y[u[x]]=x;var _=function(t){return t instanceof _?t:this instanceof _?(this.valid=!1,this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},void("string"==typeof t?(e=d.getRgba(t))?this.setValues("rgb",e):(e=d.getHsla(t))?this.setValues("hsl",e):(e=d.getHwb(t))&&this.setValues("hwb",e):"object"==typeof t&&(void 0!==(e=t).r||void 0!==e.red?this.setValues("rgb",e):void 0!==e.l||void 0!==e.lightness?this.setValues("hsl",e):void 0!==e.v||void 0!==e.value?this.setValues("hsv",e):void 0!==e.w||void 0!==e.whiteness?this.setValues("hwb",e):void 0===e.c&&void 0===e.cyan||this.setValues("cmyk",e)))):new _(t);var e};_.prototype={isValid:function(){return this.valid},rgb:function(){return this.setSpace("rgb",arguments)},hsl:function(){return this.setSpace("hsl",arguments)},hsv:function(){return this.setSpace("hsv",arguments)},hwb:function(){return this.setSpace("hwb",arguments)},cmyk:function(){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){var t=this.values;return 1!==t.alpha?t.hwb.concat([t.alpha]):t.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values;return t.rgb.concat([t.alpha])},hslaArray:function(){var t=this.values;return t.hsl.concat([t.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return t&&(t=(t%=360)<0?360+t:t),this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return d.hexString(this.values.rgb)},rgbString:function(){return d.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return d.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return d.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return d.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return d.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return d.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return d.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){var t=this.values.rgb;return t[0]<<16|t[1]<<8|t[2]},luminosity:function(){for(var t=this.values.rgb,e=[],n=0;n<t.length;n++){var i=t[n]/255;e[n]=i<=.03928?i/12.92:Math.pow((i+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),n=t.luminosity();return e>n?(e+.05)/(n+.05):(n+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,n=(e[0]+t)%360;return e[0]=n<0?360+n:n,this.setValues("hsl",e),this},mix:function(t,e){var n=t,i=void 0===e?.5:e,a=2*i-1,r=this.alpha()-n.alpha(),o=((a*r==-1?a:(a+r)/(1+a*r))+1)/2,s=1-o;return this.rgb(o*this.red()+s*n.red(),o*this.green()+s*n.green(),o*this.blue()+s*n.blue()).alpha(this.alpha()*i+n.alpha()*(1-i))},toJSON:function(){return this.rgb()},clone:function(){var t,e,n=new _,i=this.values,a=n.values;for(var r in i)i.hasOwnProperty(r)&&(t=i[r],"[object Array]"===(e={}.toString.call(t))?a[r]=t.slice(0):"[object Number]"===e?a[r]=t:console.error("unexpected color value:",t));return n}},_.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},_.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},_.prototype.getValues=function(t){for(var e=this.values,n={},i=0;i<t.length;i++)n[t.charAt(i)]=e[t][i];return 1!==e.alpha&&(n.a=e.alpha),n},_.prototype.setValues=function(t,e){var n,i,a=this.values,r=this.spaces,o=this.maxes,s=1;if(this.valid=!0,"alpha"===t)s=e;else if(e.length)a[t]=e.slice(0,t.length),s=e[t.length];else if(void 0!==e[t.charAt(0)]){for(n=0;n<t.length;n++)a[t][n]=e[t.charAt(n)];s=e.a}else if(void 0!==e[r[t][0]]){var u=r[t];for(n=0;n<t.length;n++)a[t][n]=e[u[n]];s=e.alpha}if(a.alpha=Math.max(0,Math.min(1,void 0===s?a.alpha:s)),"alpha"===t)return!1;for(n=0;n<t.length;n++)i=Math.max(0,Math.min(o[t][n],a[t][n])),a[t][n]=Math.round(i);for(var d in r)d!==t&&(a[d]=l[t][d](a[t]));return!0},_.prototype.setSpace=function(t,e){var n=e[0];return void 0===n?this.getValues(t):("number"==typeof n&&(n=Array.prototype.slice.call(e)),this.setValues(t,n),this)},_.prototype.setChannel=function(t,e,n){var i=this.values[t];return void 0===n?i[e]:n===i[e]?this:(i[e]=n,this.setValues(t,i),this)},"undefined"!=typeof window&&(window.Color=_);var w,k=_,M={noop:function(){},uid:(w=0,function(){return w++}),isNullOrUndef:function(t){return null==t},isArray:function(t){if(Array.isArray&&Array.isArray(t))return!0;var e=Object.prototype.toString.call(t);return"[object"===e.substr(0,7)&&"Array]"===e.substr(-6)},isObject:function(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)},isFinite:function(t){return("number"==typeof t||t instanceof Number)&&isFinite(t)},valueOrDefault:function(t,e){return void 0===t?e:t},valueAtIndexOrDefault:function(t,e,n){return M.valueOrDefault(M.isArray(t)?t[e]:t,n)},callback:function(t,e,n){if(t&&"function"==typeof t.call)return t.apply(n,e)},each:function(t,e,n,i){var a,r,o;if(M.isArray(t))if(r=t.length,i)for(a=r-1;a>=0;a--)e.call(n,t[a],a);else for(a=0;a<r;a++)e.call(n,t[a],a);else if(M.isObject(t))for(r=(o=Object.keys(t)).length,a=0;a<r;a++)e.call(n,t[o[a]],o[a])},arrayEquals:function(t,e){var n,i,a,r;if(!t||!e||t.length!==e.length)return!1;for(n=0,i=t.length;n<i;++n)if(a=t[n],r=e[n],a instanceof Array&&r instanceof Array){if(!M.arrayEquals(a,r))return!1}else if(a!==r)return!1;return!0},clone:function(t){if(M.isArray(t))return t.map(M.clone);if(M.isObject(t)){for(var e={},n=Object.keys(t),i=n.length,a=0;a<i;++a)e[n[a]]=M.clone(t[n[a]]);return e}return t},_merger:function(t,e,n,i){var a=e[t],r=n[t];M.isObject(a)&&M.isObject(r)?M.merge(a,r,i):e[t]=M.clone(r)},_mergerIf:function(t,e,n){var i=e[t],a=n[t];M.isObject(i)&&M.isObject(a)?M.mergeIf(i,a):e.hasOwnProperty(t)||(e[t]=M.clone(a))},merge:function(t,e,n){var i,a,r,o,s,l=M.isArray(e)?e:[e],u=l.length;if(!M.isObject(t))return t;for(i=(n=n||{}).merger||M._merger,a=0;a<u;++a)if(e=l[a],M.isObject(e))for(s=0,o=(r=Object.keys(e)).length;s<o;++s)i(r[s],t,e,n);return t},mergeIf:function(t,e){return M.merge(t,e,{merger:M._mergerIf})},extend:Object.assign||function(t){return M.merge(t,[].slice.call(arguments,1),{merger:function(t,e,n){e[t]=n[t]}})},inherits:function(t){var e=this,n=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},i=function(){this.constructor=n};return i.prototype=e.prototype,n.prototype=new i,n.extend=M.inherits,t&&M.extend(n.prototype,t),n.__super__=e.prototype,n},_deprecated:function(t,e,n,i){void 0!==e&&console.warn(t+': "'+n+'" is deprecated. Please use "'+i+'" instead')}},S=M;M.callCallback=M.callback,M.indexOf=function(t,e,n){return Array.prototype.indexOf.call(t,e,n)},M.getValueOrDefault=M.valueOrDefault,M.getValueAtIndexOrDefault=M.valueAtIndexOrDefault;var D={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return(t-=1)*t*t+1},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-((t-=1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return t*t*t*t*t},easeOutQuint:function(t){return(t-=1)*t*t*t*t+1},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return 1-Math.cos(t*(Math.PI/2))},easeOutSine:function(t){return Math.sin(t*(Math.PI/2))},easeInOutSine:function(t){return-.5*(Math.cos(Math.PI*t)-1)},easeInExpo:function(t){return 0===t?0:Math.pow(2,10*(t-1))},easeOutExpo:function(t){return 1===t?1:1-Math.pow(2,-10*t)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(2-Math.pow(2,-10*--t))},easeInCirc:function(t){return t>=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),-i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n))},easeOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:1===t?1:(n||(n=.3),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),i*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/n)+1)},easeInOutElastic:function(t){var e=1.70158,n=0,i=1;return 0===t?0:2==(t/=.5)?1:(n||(n=.45),i<1?(i=1,e=n/4):e=n/(2*Math.PI)*Math.asin(1/i),t<1?i*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*-.5:i*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/n)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-D.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*D.easeInBounce(2*t):.5*D.easeOutBounce(2*t-1)+.5}},C={effects:D};S.easingEffects=D;var P=Math.PI,T=P/180,O=2*P,A=P/2,F=P/4,I=2*P/3,L={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,n,i,a,r){if(r){var o=Math.min(r,a/2,i/2),s=e+o,l=n+o,u=e+i-o,d=n+a-o;t.moveTo(e,l),s<u&&l<d?(t.arc(s,l,o,-P,-A),t.arc(u,l,o,-A,0),t.arc(u,d,o,0,A),t.arc(s,d,o,A,P)):s<u?(t.moveTo(s,n),t.arc(u,l,o,-A,A),t.arc(s,l,o,A,P+A)):l<d?(t.arc(s,l,o,-P,0),t.arc(s,d,o,0,P)):t.arc(s,l,o,-P,P),t.closePath(),t.moveTo(e,n)}else t.rect(e,n,i,a)},drawPoint:function(t,e,n,i,a,r){var o,s,l,u,d,h=(r||0)*T;if(e&&"object"==typeof e&&("[object HTMLImageElement]"===(o=e.toString())||"[object HTMLCanvasElement]"===o))return t.save(),t.translate(i,a),t.rotate(h),t.drawImage(e,-e.width/2,-e.height/2,e.width,e.height),void t.restore();if(!(isNaN(n)||n<=0)){switch(t.beginPath(),e){default:t.arc(i,a,n,0,O),t.closePath();break;case"triangle":t.moveTo(i+Math.sin(h)*n,a-Math.cos(h)*n),h+=I,t.lineTo(i+Math.sin(h)*n,a-Math.cos(h)*n),h+=I,t.lineTo(i+Math.sin(h)*n,a-Math.cos(h)*n),t.closePath();break;case"rectRounded":u=n-(d=.516*n),s=Math.cos(h+F)*u,l=Math.sin(h+F)*u,t.arc(i-s,a-l,d,h-P,h-A),t.arc(i+l,a-s,d,h-A,h),t.arc(i+s,a+l,d,h,h+A),t.arc(i-l,a+s,d,h+A,h+P),t.closePath();break;case"rect":if(!r){u=Math.SQRT1_2*n,t.rect(i-u,a-u,2*u,2*u);break}h+=F;case"rectRot":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+l,a-s),t.lineTo(i+s,a+l),t.lineTo(i-l,a+s),t.closePath();break;case"crossRot":h+=F;case"cross":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s);break;case"star":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s),h+=F,s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l),t.moveTo(i+l,a-s),t.lineTo(i-l,a+s);break;case"line":s=Math.cos(h)*n,l=Math.sin(h)*n,t.moveTo(i-s,a-l),t.lineTo(i+s,a+l);break;case"dash":t.moveTo(i,a),t.lineTo(i+Math.cos(h)*n,a+Math.sin(h)*n)}t.fill(),t.stroke()}},_isPointInArea:function(t,e){return t.x>e.left-1e-6&&t.x<e.right+1e-6&&t.y>e.top-1e-6&&t.y<e.bottom+1e-6},clipArea:function(t,e){t.save(),t.beginPath(),t.rect(e.left,e.top,e.right-e.left,e.bottom-e.top),t.clip()},unclipArea:function(t){t.restore()},lineTo:function(t,e,n,i){var a=n.steppedLine;if(a){if("middle"===a){var r=(e.x+n.x)/2;t.lineTo(r,i?n.y:e.y),t.lineTo(r,i?e.y:n.y)}else"after"===a&&!i||"after"!==a&&i?t.lineTo(e.x,n.y):t.lineTo(n.x,e.y);t.lineTo(n.x,n.y)}else n.tension?t.bezierCurveTo(i?e.controlPointPreviousX:e.controlPointNextX,i?e.controlPointPreviousY:e.controlPointNextY,i?n.controlPointNextX:n.controlPointPreviousX,i?n.controlPointNextY:n.controlPointPreviousY,n.x,n.y):t.lineTo(n.x,n.y)}},R=L;S.clear=L.clear,S.drawRoundedRectangle=function(t){t.beginPath(),L.roundedRect.apply(L,arguments)};var N={_set:function(t,e){return S.merge(this[t]||(this[t]={}),e)}};N._set("global",{defaultColor:"rgba(0,0,0,0.1)",defaultFontColor:"#666",defaultFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",defaultFontSize:12,defaultFontStyle:"normal",defaultLineHeight:1.2,showLines:!0});var W=N,Y=S.valueOrDefault;var z={toLineHeight:function(t,e){var n=(""+t).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/);if(!n||"normal"===n[1])return 1.2*e;switch(t=+n[2],n[3]){case"px":return t;case"%":t/=100}return e*t},toPadding:function(t){var e,n,i,a;return S.isObject(t)?(e=+t.top||0,n=+t.right||0,i=+t.bottom||0,a=+t.left||0):e=n=i=a=+t||0,{top:e,right:n,bottom:i,left:a,height:e+i,width:a+n}},_parseFont:function(t){var e=W.global,n=Y(t.fontSize,e.defaultFontSize),i={family:Y(t.fontFamily,e.defaultFontFamily),lineHeight:S.options.toLineHeight(Y(t.lineHeight,e.defaultLineHeight),n),size:n,style:Y(t.fontStyle,e.defaultFontStyle),weight:null,string:""};return i.string=function(t){return!t||S.isNullOrUndef(t.size)||S.isNullOrUndef(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}(i),i},resolve:function(t,e,n,i){var a,r,o,s=!0;for(a=0,r=t.length;a<r;++a)if(void 0!==(o=t[a])&&(void 0!==e&&"function"==typeof o&&(o=o(e),s=!1),void 0!==n&&S.isArray(o)&&(o=o[n],s=!1),void 0!==o))return i&&!s&&(i.cacheable=!1),o}},E={_factorize:function(t){var e,n=[],i=Math.sqrt(t);for(e=1;e<i;e++)t%e==0&&(n.push(e),n.push(t/e));return i===(0|i)&&n.push(i),n.sort((function(t,e){return t-e})).pop(),n},log10:Math.log10||function(t){var e=Math.log(t)*Math.LOG10E,n=Math.round(e);return t===Math.pow(10,n)?n:e}},V=E;S.log10=E.log10;var H=S,B=C,j=R,U=z,G=V,q={getRtlAdapter:function(t,e,n){return t?function(t,e){return{x:function(n){return t+t+e-n},setWidth:function(t){e=t},textAlign:function(t){return"center"===t?t:"right"===t?"left":"right"},xPlus:function(t,e){return t-e},leftForLtr:function(t,e){return t-e}}}(e,n):{x:function(t){return t},setWidth:function(t){},textAlign:function(t){return t},xPlus:function(t,e){return t+e},leftForLtr:function(t,e){return t}}},overrideTextDirection:function(t,e){var n,i;"ltr"!==e&&"rtl"!==e||(i=[(n=t.canvas.style).getPropertyValue("direction"),n.getPropertyPriority("direction")],n.setProperty("direction",e,"important"),t.prevTextDirection=i)},restoreTextDirection:function(t){var e=t.prevTextDirection;void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}};H.easing=B,H.canvas=j,H.options=U,H.math=G,H.rtl=q;var Z=function(t){H.extend(this,t),this.initialize.apply(this,arguments)};H.extend(Z.prototype,{_type:void 0,initialize:function(){this.hidden=!1},pivot:function(){var t=this;return t._view||(t._view=H.extend({},t._model)),t._start={},t},transition:function(t){var e=this,n=e._model,i=e._start,a=e._view;return n&&1!==t?(a||(a=e._view={}),i||(i=e._start={}),function(t,e,n,i){var a,r,o,s,l,u,d,h,c,f=Object.keys(n);for(a=0,r=f.length;a<r;++a)if(u=n[o=f[a]],e.hasOwnProperty(o)||(e[o]=u),(s=e[o])!==u&&"_"!==o[0]){if(t.hasOwnProperty(o)||(t[o]=s),(d=typeof u)===typeof(l=t[o]))if("string"===d){if((h=k(l)).valid&&(c=k(u)).valid){e[o]=c.mix(h,i).rgbString();continue}}else if(H.isFinite(l)&&H.isFinite(u)){e[o]=l+(u-l)*i;continue}e[o]=u}}(i,a,n,t),e):(e._view=H.extend({},n),e._start=null,e)},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return H.isNumber(this._model.x)&&H.isNumber(this._model.y)}}),Z.extend=H.inherits;var $=Z,X=$.extend({chart:null,currentStep:0,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),K=X;Object.defineProperty(X.prototype,"animationObject",{get:function(){return this}}),Object.defineProperty(X.prototype,"chartInstance",{get:function(){return this.chart},set:function(t){this.chart=t}}),W._set("global",{animation:{duration:1e3,easing:"easeOutQuart",onProgress:H.noop,onComplete:H.noop}});var J={animations:[],request:null,addAnimation:function(t,e,n,i){var a,r,o=this.animations;for(e.chart=t,e.startTime=Date.now(),e.duration=n,i||(t.animating=!0),a=0,r=o.length;a<r;++a)if(o[a].chart===t)return void(o[a]=e);o.push(e),1===o.length&&this.requestAnimationFrame()},cancelAnimation:function(t){var e=H.findIndex(this.animations,(function(e){return e.chart===t}));-1!==e&&(this.animations.splice(e,1),t.animating=!1)},requestAnimationFrame:function(){var t=this;null===t.request&&(t.request=H.requestAnimFrame.call(window,(function(){t.request=null,t.startDigest()})))},startDigest:function(){this.advance(),this.animations.length>0&&this.requestAnimationFrame()},advance:function(){for(var t,e,n,i,a=this.animations,r=0;r<a.length;)e=(t=a[r]).chart,n=t.numSteps,i=Math.floor((Date.now()-t.startTime)/t.duration*n)+1,t.currentStep=Math.min(i,n),H.callback(t.render,[e,t],e),H.callback(t.onAnimationProgress,[t],e),t.currentStep>=n?(H.callback(t.onAnimationComplete,[t],e),e.animating=!1,a.splice(r,1)):++r}},Q=H.options.resolve,tt=["push","pop","shift","splice","unshift"];function et(t,e){var n=t._chartjs;if(n){var i=n.listeners,a=i.indexOf(e);-1!==a&&i.splice(a,1),i.length>0||(tt.forEach((function(e){delete t[e]})),delete t._chartjs)}}var nt=function(t,e){this.initialize(t,e)};H.extend(nt.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(t,e){var n=this;n.chart=t,n.index=e,n.linkScales(),n.addElements(),n._type=n.getMeta().type},updateIndex:function(t){this.index=t},linkScales:function(){var t=this.getMeta(),e=this.chart,n=e.scales,i=this.getDataset(),a=e.options.scales;null!==t.xAxisID&&t.xAxisID in n&&!i.xAxisID||(t.xAxisID=i.xAxisID||a.xAxes[0].id),null!==t.yAxisID&&t.yAxisID in n&&!i.yAxisID||(t.yAxisID=i.yAxisID||a.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&et(this._data,this)},createMetaDataset:function(){var t=this.datasetElementType;return t&&new t({_chart:this.chart,_datasetIndex:this.index})},createMetaData:function(t){var e=this.dataElementType;return e&&new e({_chart:this.chart,_datasetIndex:this.index,_index:t})},addElements:function(){var t,e,n=this.getMeta(),i=this.getDataset().data||[],a=n.data;for(t=0,e=i.length;t<e;++t)a[t]=a[t]||this.createMetaData(t);n.dataset=n.dataset||this.createMetaDataset()},addElementAndReset:function(t){var e=this.createMetaData(t);this.getMeta().data.splice(t,0,e),this.updateElement(e,t,!0)},buildOrUpdateElements:function(){var t,e,n=this,i=n.getDataset(),a=i.data||(i.data=[]);n._data!==a&&(n._data&&et(n._data,n),a&&Object.isExtensible(a)&&(e=n,(t=a)._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),tt.forEach((function(e){var n="onData"+e.charAt(0).toUpperCase()+e.slice(1),i=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value:function(){var e=Array.prototype.slice.call(arguments),a=i.apply(this,e);return H.each(t._chartjs.listeners,(function(t){"function"==typeof t[n]&&t[n].apply(t,e)})),a}})})))),n._data=a),n.resyncElements()},_configure:function(){this._config=H.merge({},[this.chart.options.datasets[this._type],this.getDataset()],{merger:function(t,e,n){"_meta"!==t&&"data"!==t&&H._merger(t,e,n)}})},_update:function(t){this._configure(),this._cachedDataOpts=null,this.update(t)},update:H.noop,transition:function(t){for(var e=this.getMeta(),n=e.data||[],i=n.length,a=0;a<i;++a)n[a].transition(t);e.dataset&&e.dataset.transition(t)},draw:function(){var t=this.getMeta(),e=t.data||[],n=e.length,i=0;for(t.dataset&&t.dataset.draw();i<n;++i)e[i].draw()},getStyle:function(t){var e,n=this.getMeta(),i=n.dataset;return this._configure(),i&&void 0===t?e=this._resolveDatasetElementOptions(i||{}):(t=t||0,e=this._resolveDataElementOptions(n.data[t]||{},t)),!1!==e.fill&&null!==e.fill||(e.backgroundColor=e.borderColor),e},_resolveDatasetElementOptions:function(t,e){var n,i,a,r,o=this,s=o.chart,l=o._config,u=t.custom||{},d=s.options.elements[o.datasetElementType.prototype._type]||{},h=o._datasetElementOptions,c={},f={chart:s,dataset:o.getDataset(),datasetIndex:o.index,hover:e};for(n=0,i=h.length;n<i;++n)a=h[n],r=e?"hover"+a.charAt(0).toUpperCase()+a.slice(1):a,c[a]=Q([u[r],l[r],d[r]],f);return c},_resolveDataElementOptions:function(t,e){var n=this,i=t&&t.custom,a=n._cachedDataOpts;if(a&&!i)return a;var r,o,s,l,u=n.chart,d=n._config,h=u.options.elements[n.dataElementType.prototype._type]||{},c=n._dataElementOptions,f={},g={chart:u,dataIndex:e,dataset:n.getDataset(),datasetIndex:n.index},m={cacheable:!i};if(i=i||{},H.isArray(c))for(o=0,s=c.length;o<s;++o)f[l=c[o]]=Q([i[l],d[l],h[l]],g,e,m);else for(o=0,s=(r=Object.keys(c)).length;o<s;++o)f[l=r[o]]=Q([i[l],d[c[l]],d[l],h[l]],g,e,m);return m.cacheable&&(n._cachedDataOpts=Object.freeze(f)),f},removeHoverStyle:function(t){H.merge(t._model,t.$previousStyle||{}),delete t.$previousStyle},setHoverStyle:function(t){var e=this.chart.data.datasets[t._datasetIndex],n=t._index,i=t.custom||{},a=t._model,r=H.getHoverColor;t.$previousStyle={backgroundColor:a.backgroundColor,borderColor:a.borderColor,borderWidth:a.borderWidth},a.backgroundColor=Q([i.hoverBackgroundColor,e.hoverBackgroundColor,r(a.backgroundColor)],void 0,n),a.borderColor=Q([i.hoverBorderColor,e.hoverBorderColor,r(a.borderColor)],void 0,n),a.borderWidth=Q([i.hoverBorderWidth,e.hoverBorderWidth,a.borderWidth],void 0,n)},_removeDatasetHoverStyle:function(){var t=this.getMeta().dataset;t&&this.removeHoverStyle(t)},_setDatasetHoverStyle:function(){var t,e,n,i,a,r,o=this.getMeta().dataset,s={};if(o){for(r=o._model,a=this._resolveDatasetElementOptions(o,!0),t=0,e=(i=Object.keys(a)).length;t<e;++t)s[n=i[t]]=r[n],r[n]=a[n];o.$previousStyle=s}},resyncElements:function(){var t=this.getMeta(),e=this.getDataset().data,n=t.data.length,i=e.length;i<n?t.data.splice(i,n-i):i>n&&this.insertElements(n,i-n)},insertElements:function(t,e){for(var n=0;n<e;++n)this.addElementAndReset(t+n)},onDataPush:function(){var t=arguments.length;this.insertElements(this.getDataset().data.length-t,t)},onDataPop:function(){this.getMeta().data.pop()},onDataShift:function(){this.getMeta().data.shift()},onDataSplice:function(t,e){this.getMeta().data.splice(t,e),this.insertElements(t,arguments.length-2)},onDataUnshift:function(){this.insertElements(0,arguments.length)}}),nt.extend=H.inherits;var it=nt,at=2*Math.PI;function rt(t,e){var n=e.startAngle,i=e.endAngle,a=e.pixelMargin,r=a/e.outerRadius,o=e.x,s=e.y;t.beginPath(),t.arc(o,s,e.outerRadius,n-r,i+r),e.innerRadius>a?(r=a/e.innerRadius,t.arc(o,s,e.innerRadius-a,i+r,n-r,!0)):t.arc(o,s,a,i+Math.PI/2,n-Math.PI/2),t.closePath(),t.clip()}function ot(t,e,n){var i="inner"===e.borderAlign;i?(t.lineWidth=2*e.borderWidth,t.lineJoin="round"):(t.lineWidth=e.borderWidth,t.lineJoin="bevel"),n.fullCircles&&function(t,e,n,i){var a,r=n.endAngle;for(i&&(n.endAngle=n.startAngle+at,rt(t,n),n.endAngle=r,n.endAngle===n.startAngle&&n.fullCircles&&(n.endAngle+=at,n.fullCircles--)),t.beginPath(),t.arc(n.x,n.y,n.innerRadius,n.startAngle+at,n.startAngle,!0),a=0;a<n.fullCircles;++a)t.stroke();for(t.beginPath(),t.arc(n.x,n.y,e.outerRadius,n.startAngle,n.startAngle+at),a=0;a<n.fullCircles;++a)t.stroke()}(t,e,n,i),i&&rt(t,n),t.beginPath(),t.arc(n.x,n.y,e.outerRadius,n.startAngle,n.endAngle),t.arc(n.x,n.y,n.innerRadius,n.endAngle,n.startAngle,!0),t.closePath(),t.stroke()}W._set("global",{elements:{arc:{backgroundColor:W.global.defaultColor,borderColor:"#fff",borderWidth:2,borderAlign:"center"}}});var st=$.extend({_type:"arc",inLabelRange:function(t){var e=this._view;return!!e&&Math.pow(t-e.x,2)<Math.pow(e.radius+e.hoverRadius,2)},inRange:function(t,e){var n=this._view;if(n){for(var i=H.getAngleFromPoint(n,{x:t,y:e}),a=i.angle,r=i.distance,o=n.startAngle,s=n.endAngle;s<o;)s+=at;for(;a>s;)a-=at;for(;a<o;)a+=at;var l=a>=o&&a<=s,u=r>=n.innerRadius&&r<=n.outerRadius;return l&&u}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,n=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,n=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*n,y:t.y+Math.sin(e)*n}},draw:function(){var t,e=this._chart.ctx,n=this._view,i="inner"===n.borderAlign?.33:0,a={x:n.x,y:n.y,innerRadius:n.innerRadius,outerRadius:Math.max(n.outerRadius-i,0),pixelMargin:i,startAngle:n.startAngle,endAngle:n.endAngle,fullCircles:Math.floor(n.circumference/at)};if(e.save(),e.fillStyle=n.backgroundColor,e.strokeStyle=n.borderColor,a.fullCircles){for(a.endAngle=a.startAngle+at,e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),t=0;t<a.fullCircles;++t)e.fill();a.endAngle=a.startAngle+n.circumference%at}e.beginPath(),e.arc(a.x,a.y,a.outerRadius,a.startAngle,a.endAngle),e.arc(a.x,a.y,a.innerRadius,a.endAngle,a.startAngle,!0),e.closePath(),e.fill(),n.borderWidth&&ot(e,n,a),e.restore()}}),lt=H.valueOrDefault,ut=W.global.defaultColor;W._set("global",{elements:{line:{tension:.4,backgroundColor:ut,borderWidth:3,borderColor:ut,borderCapStyle:"butt",borderDash:[],borderDashOffset:0,borderJoinStyle:"miter",capBezierPoints:!0,fill:!0}}});var dt=$.extend({_type:"line",draw:function(){var t,e,n,i=this,a=i._view,r=i._chart.ctx,o=a.spanGaps,s=i._children.slice(),l=W.global,u=l.elements.line,d=-1,h=i._loop;if(s.length){if(i._loop){for(t=0;t<s.length;++t)if(e=H.previousItem(s,t),!s[t]._view.skip&&e._view.skip){s=s.slice(t).concat(s.slice(0,t)),h=o;break}h&&s.push(s[0])}for(r.save(),r.lineCap=a.borderCapStyle||u.borderCapStyle,r.setLineDash&&r.setLineDash(a.borderDash||u.borderDash),r.lineDashOffset=lt(a.borderDashOffset,u.borderDashOffset),r.lineJoin=a.borderJoinStyle||u.borderJoinStyle,r.lineWidth=lt(a.borderWidth,u.borderWidth),r.strokeStyle=a.borderColor||l.defaultColor,r.beginPath(),(n=s[0]._view).skip||(r.moveTo(n.x,n.y),d=0),t=1;t<s.length;++t)n=s[t]._view,e=-1===d?H.previousItem(s,t):s[d],n.skip||(d!==t-1&&!o||-1===d?r.moveTo(n.x,n.y):H.canvas.lineTo(r,e._view,n),d=t);h&&r.closePath(),r.stroke(),r.restore()}}}),ht=H.valueOrDefault,ct=W.global.defaultColor;function ft(t){var e=this._view;return!!e&&Math.abs(t-e.x)<e.radius+e.hitRadius}W._set("global",{elements:{point:{radius:3,pointStyle:"circle",backgroundColor:ct,borderColor:ct,borderWidth:1,hitRadius:1,hoverRadius:4,hoverBorderWidth:1}}});var gt=$.extend({_type:"point",inRange:function(t,e){var n=this._view;return!!n&&Math.pow(t-n.x,2)+Math.pow(e-n.y,2)<Math.pow(n.hitRadius+n.radius,2)},inLabelRange:ft,inXRange:ft,inYRange:function(t){var e=this._view;return!!e&&Math.abs(t-e.y)<e.radius+e.hitRadius},getCenterPoint:function(){var t=this._view;return{x:t.x,y:t.y}},getArea:function(){return Math.PI*Math.pow(this._view.radius,2)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y,padding:t.radius+t.borderWidth}},draw:function(t){var e=this._view,n=this._chart.ctx,i=e.pointStyle,a=e.rotation,r=e.radius,o=e.x,s=e.y,l=W.global,u=l.defaultColor;e.skip||(void 0===t||H.canvas._isPointInArea(e,t))&&(n.strokeStyle=e.borderColor||u,n.lineWidth=ht(e.borderWidth,l.elements.point.borderWidth),n.fillStyle=e.backgroundColor||u,H.canvas.drawPoint(n,i,r,o,s,a))}}),mt=W.global.defaultColor;function pt(t){return t&&void 0!==t.width}function vt(t){var e,n,i,a,r;return pt(t)?(r=t.width/2,e=t.x-r,n=t.x+r,i=Math.min(t.y,t.base),a=Math.max(t.y,t.base)):(r=t.height/2,e=Math.min(t.x,t.base),n=Math.max(t.x,t.base),i=t.y-r,a=t.y+r),{left:e,top:i,right:n,bottom:a}}function bt(t,e,n){return t===e?n:t===n?e:t}function yt(t,e,n){var i,a,r,o,s=t.borderWidth,l=function(t){var e=t.borderSkipped,n={};return e?(t.horizontal?t.base>t.x&&(e=bt(e,"left","right")):t.base<t.y&&(e=bt(e,"bottom","top")),n[e]=!0,n):n}(t);return H.isObject(s)?(i=+s.top||0,a=+s.right||0,r=+s.bottom||0,o=+s.left||0):i=a=r=o=+s||0,{t:l.top||i<0?0:i>n?n:i,r:l.right||a<0?0:a>e?e:a,b:l.bottom||r<0?0:r>n?n:r,l:l.left||o<0?0:o>e?e:o}}function xt(t,e,n){var i=null===e,a=null===n,r=!(!t||i&&a)&&vt(t);return r&&(i||e>=r.left&&e<=r.right)&&(a||n>=r.top&&n<=r.bottom)}W._set("global",{elements:{rectangle:{backgroundColor:mt,borderColor:mt,borderSkipped:"bottom",borderWidth:0}}});var _t=$.extend({_type:"rectangle",draw:function(){var t=this._chart.ctx,e=this._view,n=function(t){var e=vt(t),n=e.right-e.left,i=e.bottom-e.top,a=yt(t,n/2,i/2);return{outer:{x:e.left,y:e.top,w:n,h:i},inner:{x:e.left+a.l,y:e.top+a.t,w:n-a.l-a.r,h:i-a.t-a.b}}}(e),i=n.outer,a=n.inner;t.fillStyle=e.backgroundColor,t.fillRect(i.x,i.y,i.w,i.h),i.w===a.w&&i.h===a.h||(t.save(),t.beginPath(),t.rect(i.x,i.y,i.w,i.h),t.clip(),t.fillStyle=e.borderColor,t.rect(a.x,a.y,a.w,a.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return xt(this._view,t,e)},inLabelRange:function(t,e){var n=this._view;return pt(n)?xt(n,t,null):xt(n,null,e)},inXRange:function(t){return xt(this._view,t,null)},inYRange:function(t){return xt(this._view,null,t)},getCenterPoint:function(){var t,e,n=this._view;return pt(n)?(t=n.x,e=(n.y+n.base)/2):(t=(n.x+n.base)/2,e=n.y),{x:t,y:e}},getArea:function(){var t=this._view;return pt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),wt={},kt=st,Mt=dt,St=gt,Dt=_t;wt.Arc=kt,wt.Line=Mt,wt.Point=St,wt.Rectangle=Dt;var Ct=H._deprecated,Pt=H.valueOrDefault;function Tt(t,e,n){var i,a,r=n.barThickness,o=e.stackCount,s=e.pixels[t],l=H.isNullOrUndef(r)?function(t,e){var n,i,a,r,o=t._length;for(a=1,r=e.length;a<r;++a)o=Math.min(o,Math.abs(e[a]-e[a-1]));for(a=0,r=t.getTicks().length;a<r;++a)i=t.getPixelForTick(a),o=a>0?Math.min(o,Math.abs(i-n)):o,n=i;return o}(e.scale,e.pixels):-1;return H.isNullOrUndef(r)?(i=l*n.categoryPercentage,a=n.barPercentage):(i=r*o,a=1),{chunk:i/o,ratio:a,start:s-i/2}}W._set("bar",{hover:{mode:"label"},scales:{xAxes:[{type:"category",offset:!0,gridLines:{offsetGridLines:!0}}],yAxes:[{type:"linear"}]}}),W._set("global",{datasets:{bar:{categoryPercentage:.8,barPercentage:.9}}});var Ot=it.extend({dataElementType:wt.Rectangle,_dataElementOptions:["backgroundColor","borderColor","borderSkipped","borderWidth","barPercentage","barThickness","categoryPercentage","maxBarThickness","minBarLength"],initialize:function(){var t,e,n=this;it.prototype.initialize.apply(n,arguments),(t=n.getMeta()).stack=n.getDataset().stack,t.bar=!0,e=n._getIndexScale().options,Ct("bar chart",e.barPercentage,"scales.[x/y]Axes.barPercentage","dataset.barPercentage"),Ct("bar chart",e.barThickness,"scales.[x/y]Axes.barThickness","dataset.barThickness"),Ct("bar chart",e.categoryPercentage,"scales.[x/y]Axes.categoryPercentage","dataset.categoryPercentage"),Ct("bar chart",n._getValueScale().options.minBarLength,"scales.[x/y]Axes.minBarLength","dataset.minBarLength"),Ct("bar chart",e.maxBarThickness,"scales.[x/y]Axes.maxBarThickness","dataset.maxBarThickness")},update:function(t){var e,n,i=this.getMeta().data;for(this._ruler=this.getRuler(),e=0,n=i.length;e<n;++e)this.updateElement(i[e],e,t)},updateElement:function(t,e,n){var i=this,a=i.getMeta(),r=i.getDataset(),o=i._resolveDataElementOptions(t,e);t._xScale=i.getScaleForId(a.xAxisID),t._yScale=i.getScaleForId(a.yAxisID),t._datasetIndex=i.index,t._index=e,t._model={backgroundColor:o.backgroundColor,borderColor:o.borderColor,borderSkipped:o.borderSkipped,borderWidth:o.borderWidth,datasetLabel:r.label,label:i.chart.data.labels[e]},H.isArray(r.data[e])&&(t._model.borderSkipped=null),i._updateElementGeometry(t,e,n,o),t.pivot()},_updateElementGeometry:function(t,e,n,i){var a=this,r=t._model,o=a._getValueScale(),s=o.getBasePixel(),l=o.isHorizontal(),u=a._ruler||a.getRuler(),d=a.calculateBarValuePixels(a.index,e,i),h=a.calculateBarIndexPixels(a.index,e,u,i);r.horizontal=l,r.base=n?s:d.base,r.x=l?n?s:d.head:h.center,r.y=l?h.center:n?s:d.head,r.height=l?h.size:void 0,r.width=l?void 0:h.size},_getStacks:function(t){var e,n,i=this._getIndexScale(),a=i._getMatchingVisibleMetas(this._type),r=i.options.stacked,o=a.length,s=[];for(e=0;e<o&&(n=a[e],(!1===r||-1===s.indexOf(n.stack)||void 0===r&&void 0===n.stack)&&s.push(n.stack),n.index!==t);++e);return s},getStackCount:function(){return this._getStacks().length},getStackIndex:function(t,e){var n=this._getStacks(t),i=void 0!==e?n.indexOf(e):-1;return-1===i?n.length-1:i},getRuler:function(){var t,e,n=this._getIndexScale(),i=[];for(t=0,e=this.getMeta().data.length;t<e;++t)i.push(n.getPixelForValue(null,t,this.index));return{pixels:i,start:n._startPixel,end:n._endPixel,stackCount:this.getStackCount(),scale:n}},calculateBarValuePixels:function(t,e,n){var i,a,r,o,s,l,u,d=this.chart,h=this._getValueScale(),c=h.isHorizontal(),f=d.data.datasets,g=h._getMatchingVisibleMetas(this._type),m=h._parseValue(f[t].data[e]),p=n.minBarLength,v=h.options.stacked,b=this.getMeta().stack,y=void 0===m.start?0:m.max>=0&&m.min>=0?m.min:m.max,x=void 0===m.start?m.end:m.max>=0&&m.min>=0?m.max-m.min:m.min-m.max,_=g.length;if(v||void 0===v&&void 0!==b)for(i=0;i<_&&(a=g[i]).index!==t;++i)a.stack===b&&(r=void 0===(u=h._parseValue(f[a.index].data[e])).start?u.end:u.min>=0&&u.max>=0?u.max:u.min,(m.min<0&&r<0||m.max>=0&&r>0)&&(y+=r));return o=h.getPixelForValue(y),l=(s=h.getPixelForValue(y+x))-o,void 0!==p&&Math.abs(l)<p&&(l=p,s=x>=0&&!c||x<0&&c?o-p:o+p),{size:l,base:o,head:s,center:s+l/2}},calculateBarIndexPixels:function(t,e,n,i){var a="flex"===i.barThickness?function(t,e,n){var i,a=e.pixels,r=a[t],o=t>0?a[t-1]:null,s=t<a.length-1?a[t+1]:null,l=n.categoryPercentage;return null===o&&(o=r-(null===s?e.end-e.start:s-r)),null===s&&(s=r+r-o),i=r-(r-Math.min(o,s))/2*l,{chunk:Math.abs(s-o)/2*l/e.stackCount,ratio:n.barPercentage,start:i}}(e,n,i):Tt(e,n,i),r=this.getStackIndex(t,this.getMeta().stack),o=a.start+a.chunk*r+a.chunk/2,s=Math.min(Pt(i.maxBarThickness,1/0),a.chunk*a.ratio);return{base:o-s/2,head:o+s/2,center:o,size:s}},draw:function(){var t=this.chart,e=this._getValueScale(),n=this.getMeta().data,i=this.getDataset(),a=n.length,r=0;for(H.canvas.clipArea(t.ctx,t.chartArea);r<a;++r){var o=e._parseValue(i.data[r]);isNaN(o.min)||isNaN(o.max)||n[r].draw()}H.canvas.unclipArea(t.ctx)},_resolveDataElementOptions:function(){var t=this,e=H.extend({},it.prototype._resolveDataElementOptions.apply(t,arguments)),n=t._getIndexScale().options,i=t._getValueScale().options;return e.barPercentage=Pt(n.barPercentage,e.barPercentage),e.barThickness=Pt(n.barThickness,e.barThickness),e.categoryPercentage=Pt(n.categoryPercentage,e.categoryPercentage),e.maxBarThickness=Pt(n.maxBarThickness,e.maxBarThickness),e.minBarLength=Pt(i.minBarLength,e.minBarLength),e}}),At=H.valueOrDefault,Ft=H.options.resolve;W._set("bubble",{hover:{mode:"single"},scales:{xAxes:[{type:"linear",position:"bottom",id:"x-axis-0"}],yAxes:[{type:"linear",position:"left",id:"y-axis-0"}]},tooltips:{callbacks:{title:function(){return""},label:function(t,e){var n=e.datasets[t.datasetIndex].label||"",i=e.datasets[t.datasetIndex].data[t.index];return n+": ("+t.xLabel+", "+t.yLabel+", "+i.r+")"}}}});var It=it.extend({dataElementType:wt.Point,_dataElementOptions:["backgroundColor","borderColor","borderWidth","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth","hoverRadius","hitRadius","pointStyle","rotation"],update:function(t){var e=this,n=e.getMeta().data;H.each(n,(function(n,i){e.updateElement(n,i,t)}))},updateElement:function(t,e,n){var i=this,a=i.getMeta(),r=t.custom||{},o=i.getScaleForId(a.xAxisID),s=i.getScaleForId(a.yAxisID),l=i._resolveDataElementOptions(t,e),u=i.getDataset().data[e],d=i.index,h=n?o.getPixelForDecimal(.5):o.getPixelForValue("object"==typeof u?u:NaN,e,d),c=n?s.getBasePixel():s.getPixelForValue(u,e,d);t._xScale=o,t._yScale=s,t._options=l,t._datasetIndex=d,t._index=e,t._model={backgroundColor:l.backgroundColor,borderColor:l.borderColor,borderWidth:l.borderWidth,hitRadius:l.hitRadius,pointStyle:l.pointStyle,rotation:l.rotation,radius:n?0:l.radius,skip:r.skip||isNaN(h)||isNaN(c),x:h,y:c},t.pivot()},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=At(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=At(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=At(n.hoverBorderWidth,n.borderWidth),e.radius=n.radius+n.hoverRadius},_resolveDataElementOptions:function(t,e){var n=this,i=n.chart,a=n.getDataset(),r=t.custom||{},o=a.data[e]||{},s=it.prototype._resolveDataElementOptions.apply(n,arguments),l={chart:i,dataIndex:e,dataset:a,datasetIndex:n.index};return n._cachedDataOpts===s&&(s=H.extend({},s)),s.radius=Ft([r.radius,o.r,n._config.radius,i.options.elements.point.radius],l,e),s}}),Lt=H.valueOrDefault,Rt=Math.PI,Nt=2*Rt,Wt=Rt/2;W._set("doughnut",{animation:{animateRotate:!0,animateScale:!1},hover:{mode:"single"},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data,o=r.datasets,s=r.labels;if(a.setAttribute("class",t.id+"-legend"),o.length)for(e=0,n=o[0].data.length;e<n;++e)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=o[0].backgroundColor[e],s[e]&&i.appendChild(document.createTextNode(s[e]));return a.outerHTML},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((function(n,i){var a=t.getDatasetMeta(0),r=a.controller.getStyle(i);return{text:n,fillStyle:r.backgroundColor,strokeStyle:r.borderColor,lineWidth:r.borderWidth,hidden:isNaN(e.datasets[0].data[i])||a.data[i].hidden,index:i}})):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n<i;++n)(a=o.getDatasetMeta(n)).data[r]&&(a.data[r].hidden=!a.data[r].hidden);o.update()}},cutoutPercentage:50,rotation:-Wt,circumference:Nt,tooltips:{callbacks:{title:function(){return""},label:function(t,e){var n=e.labels[t.index],i=": "+e.datasets[t.datasetIndex].data[t.index];return H.isArray(n)?(n=n.slice())[0]+=i:n+=i,n}}}});var Yt=it.extend({dataElementType:wt.Arc,linkScales:H.noop,_dataElementOptions:["backgroundColor","borderColor","borderWidth","borderAlign","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth"],getRingIndex:function(t){for(var e=0,n=0;n<t;++n)this.chart.isDatasetVisible(n)&&++e;return e},update:function(t){var e,n,i,a,r=this,o=r.chart,s=o.chartArea,l=o.options,u=1,d=1,h=0,c=0,f=r.getMeta(),g=f.data,m=l.cutoutPercentage/100||0,p=l.circumference,v=r._getRingWeight(r.index);if(p<Nt){var b=l.rotation%Nt,y=(b+=b>=Rt?-Nt:b<-Rt?Nt:0)+p,x=Math.cos(b),_=Math.sin(b),w=Math.cos(y),k=Math.sin(y),M=b<=0&&y>=0||y>=Nt,S=b<=Wt&&y>=Wt||y>=Nt+Wt,D=b<=-Wt&&y>=-Wt||y>=Rt+Wt,C=b===-Rt||y>=Rt?-1:Math.min(x,x*m,w,w*m),P=D?-1:Math.min(_,_*m,k,k*m),T=M?1:Math.max(x,x*m,w,w*m),O=S?1:Math.max(_,_*m,k,k*m);u=(T-C)/2,d=(O-P)/2,h=-(T+C)/2,c=-(O+P)/2}for(i=0,a=g.length;i<a;++i)g[i]._options=r._resolveDataElementOptions(g[i],i);for(o.borderWidth=r.getMaxBorderWidth(),e=(s.right-s.left-o.borderWidth)/u,n=(s.bottom-s.top-o.borderWidth)/d,o.outerRadius=Math.max(Math.min(e,n)/2,0),o.innerRadius=Math.max(o.outerRadius*m,0),o.radiusLength=(o.outerRadius-o.innerRadius)/(r._getVisibleDatasetWeightTotal()||1),o.offsetX=h*o.outerRadius,o.offsetY=c*o.outerRadius,f.total=r.calculateTotal(),r.outerRadius=o.outerRadius-o.radiusLength*r._getRingWeightOffset(r.index),r.innerRadius=Math.max(r.outerRadius-o.radiusLength*v,0),i=0,a=g.length;i<a;++i)r.updateElement(g[i],i,t)},updateElement:function(t,e,n){var i=this,a=i.chart,r=a.chartArea,o=a.options,s=o.animation,l=(r.left+r.right)/2,u=(r.top+r.bottom)/2,d=o.rotation,h=o.rotation,c=i.getDataset(),f=n&&s.animateRotate?0:t.hidden?0:i.calculateCircumference(c.data[e])*(o.circumference/Nt),g=n&&s.animateScale?0:i.innerRadius,m=n&&s.animateScale?0:i.outerRadius,p=t._options||{};H.extend(t,{_datasetIndex:i.index,_index:e,_model:{backgroundColor:p.backgroundColor,borderColor:p.borderColor,borderWidth:p.borderWidth,borderAlign:p.borderAlign,x:l+a.offsetX,y:u+a.offsetY,startAngle:d,endAngle:h,circumference:f,outerRadius:m,innerRadius:g,label:H.valueAtIndexOrDefault(c.label,e,a.data.labels[e])}});var v=t._model;n&&s.animateRotate||(v.startAngle=0===e?o.rotation:i.getMeta().data[e-1]._model.endAngle,v.endAngle=v.startAngle+v.circumference),t.pivot()},calculateTotal:function(){var t,e=this.getDataset(),n=this.getMeta(),i=0;return H.each(n.data,(function(n,a){t=e.data[a],isNaN(t)||n.hidden||(i+=Math.abs(t))})),i},calculateCircumference:function(t){var e=this.getMeta().total;return e>0&&!isNaN(t)?Nt*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,n,i,a,r,o,s,l,u=0,d=this.chart;if(!t)for(e=0,n=d.data.datasets.length;e<n;++e)if(d.isDatasetVisible(e)){t=(i=d.getDatasetMeta(e)).data,e!==this.index&&(r=i.controller);break}if(!t)return 0;for(e=0,n=t.length;e<n;++e)a=t[e],r?(r._configure(),o=r._resolveDataElementOptions(a,e)):o=a._options,"inner"!==o.borderAlign&&(s=o.borderWidth,u=(l=o.hoverBorderWidth)>(u=s>u?s:u)?l:u);return u},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=Lt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Lt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Lt(n.hoverBorderWidth,n.borderWidth)},_getRingWeightOffset:function(t){for(var e=0,n=0;n<t;++n)this.chart.isDatasetVisible(n)&&(e+=this._getRingWeight(n));return e},_getRingWeight:function(t){return Math.max(Lt(this.chart.data.datasets[t].weight,1),0)},_getVisibleDatasetWeightTotal:function(){return this._getRingWeightOffset(this.chart.data.datasets.length)}});W._set("horizontalBar",{hover:{mode:"index",axis:"y"},scales:{xAxes:[{type:"linear",position:"bottom"}],yAxes:[{type:"category",position:"left",offset:!0,gridLines:{offsetGridLines:!0}}]},elements:{rectangle:{borderSkipped:"left"}},tooltips:{mode:"index",axis:"y"}}),W._set("global",{datasets:{horizontalBar:{categoryPercentage:.8,barPercentage:.9}}});var zt=Ot.extend({_getValueScaleId:function(){return this.getMeta().xAxisID},_getIndexScaleId:function(){return this.getMeta().yAxisID}}),Et=H.valueOrDefault,Vt=H.options.resolve,Ht=H.canvas._isPointInArea;function Bt(t,e){var n=t&&t.options.ticks||{},i=n.reverse,a=void 0===n.min?e:0,r=void 0===n.max?e:0;return{start:i?r:a,end:i?a:r}}function jt(t,e,n){var i=n/2,a=Bt(t,i),r=Bt(e,i);return{top:r.end,right:a.end,bottom:r.start,left:a.start}}function Ut(t){var e,n,i,a;return H.isObject(t)?(e=t.top,n=t.right,i=t.bottom,a=t.left):e=n=i=a=t,{top:e,right:n,bottom:i,left:a}}W._set("line",{showLines:!0,spanGaps:!1,hover:{mode:"label"},scales:{xAxes:[{type:"category",id:"x-axis-0"}],yAxes:[{type:"linear",id:"y-axis-0"}]}});var Gt=it.extend({datasetElementType:wt.Line,dataElementType:wt.Point,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth","cubicInterpolationMode","fill"],_dataElementOptions:{backgroundColor:"pointBackgroundColor",borderColor:"pointBorderColor",borderWidth:"pointBorderWidth",hitRadius:"pointHitRadius",hoverBackgroundColor:"pointHoverBackgroundColor",hoverBorderColor:"pointHoverBorderColor",hoverBorderWidth:"pointHoverBorderWidth",hoverRadius:"pointHoverRadius",pointStyle:"pointStyle",radius:"pointRadius",rotation:"pointRotation"},update:function(t){var e,n,i=this,a=i.getMeta(),r=a.dataset,o=a.data||[],s=i.chart.options,l=i._config,u=i._showLine=Et(l.showLine,s.showLines);for(i._xScale=i.getScaleForId(a.xAxisID),i._yScale=i.getScaleForId(a.yAxisID),u&&(void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),r._scale=i._yScale,r._datasetIndex=i.index,r._children=o,r._model=i._resolveDatasetElementOptions(r),r.pivot()),e=0,n=o.length;e<n;++e)i.updateElement(o[e],e,t);for(u&&0!==r._model.tension&&i.updateBezierControlPoints(),e=0,n=o.length;e<n;++e)o[e].pivot()},updateElement:function(t,e,n){var i,a,r=this,o=r.getMeta(),s=t.custom||{},l=r.getDataset(),u=r.index,d=l.data[e],h=r._xScale,c=r._yScale,f=o.dataset._model,g=r._resolveDataElementOptions(t,e);i=h.getPixelForValue("object"==typeof d?d:NaN,e,u),a=n?c.getBasePixel():r.calculatePointY(d,e,u),t._xScale=h,t._yScale=c,t._options=g,t._datasetIndex=u,t._index=e,t._model={x:i,y:a,skip:s.skip||isNaN(i)||isNaN(a),radius:g.radius,pointStyle:g.pointStyle,rotation:g.rotation,backgroundColor:g.backgroundColor,borderColor:g.borderColor,borderWidth:g.borderWidth,tension:Et(s.tension,f?f.tension:0),steppedLine:!!f&&f.steppedLine,hitRadius:g.hitRadius}},_resolveDatasetElementOptions:function(t){var e=this,n=e._config,i=t.custom||{},a=e.chart.options,r=a.elements.line,o=it.prototype._resolveDatasetElementOptions.apply(e,arguments);return o.spanGaps=Et(n.spanGaps,a.spanGaps),o.tension=Et(n.lineTension,r.tension),o.steppedLine=Vt([i.steppedLine,n.steppedLine,r.stepped]),o.clip=Ut(Et(n.clip,jt(e._xScale,e._yScale,o.borderWidth))),o},calculatePointY:function(t,e,n){var i,a,r,o,s,l,u,d=this.chart,h=this._yScale,c=0,f=0;if(h.options.stacked){for(s=+h.getRightValue(t),u=(l=d._getSortedVisibleDatasetMetas()).length,i=0;i<u&&(r=l[i]).index!==n;++i)a=d.data.datasets[r.index],"line"===r.type&&r.yAxisID===h.id&&((o=+h.getRightValue(a.data[e]))<0?f+=o||0:c+=o||0);return s<0?h.getPixelForValue(f+s):h.getPixelForValue(c+s)}return h.getPixelForValue(t)},updateBezierControlPoints:function(){var t,e,n,i,a=this.chart,r=this.getMeta(),o=r.dataset._model,s=a.chartArea,l=r.data||[];function u(t,e,n){return Math.max(Math.min(t,n),e)}if(o.spanGaps&&(l=l.filter((function(t){return!t._model.skip}))),"monotone"===o.cubicInterpolationMode)H.splineCurveMonotone(l);else for(t=0,e=l.length;t<e;++t)n=l[t]._model,i=H.splineCurve(H.previousItem(l,t)._model,n,H.nextItem(l,t)._model,o.tension),n.controlPointPreviousX=i.previous.x,n.controlPointPreviousY=i.previous.y,n.controlPointNextX=i.next.x,n.controlPointNextY=i.next.y;if(a.options.elements.line.capBezierPoints)for(t=0,e=l.length;t<e;++t)n=l[t]._model,Ht(n,s)&&(t>0&&Ht(l[t-1]._model,s)&&(n.controlPointPreviousX=u(n.controlPointPreviousX,s.left,s.right),n.controlPointPreviousY=u(n.controlPointPreviousY,s.top,s.bottom)),t<l.length-1&&Ht(l[t+1]._model,s)&&(n.controlPointNextX=u(n.controlPointNextX,s.left,s.right),n.controlPointNextY=u(n.controlPointNextY,s.top,s.bottom)))},draw:function(){var t,e=this.chart,n=this.getMeta(),i=n.data||[],a=e.chartArea,r=e.canvas,o=0,s=i.length;for(this._showLine&&(t=n.dataset._model.clip,H.canvas.clipArea(e.ctx,{left:!1===t.left?0:a.left-t.left,right:!1===t.right?r.width:a.right+t.right,top:!1===t.top?0:a.top-t.top,bottom:!1===t.bottom?r.height:a.bottom+t.bottom}),n.dataset.draw(),H.canvas.unclipArea(e.ctx));o<s;++o)i[o].draw(a)},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=Et(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Et(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Et(n.hoverBorderWidth,n.borderWidth),e.radius=Et(n.hoverRadius,n.radius)}}),qt=H.options.resolve;W._set("polarArea",{scale:{type:"radialLinear",angleLines:{display:!1},gridLines:{circular:!0},pointLabels:{display:!1},ticks:{beginAtZero:!0}},animation:{animateRotate:!0,animateScale:!0},startAngle:-.5*Math.PI,legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data,o=r.datasets,s=r.labels;if(a.setAttribute("class",t.id+"-legend"),o.length)for(e=0,n=o[0].data.length;e<n;++e)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=o[0].backgroundColor[e],s[e]&&i.appendChild(document.createTextNode(s[e]));return a.outerHTML},legend:{labels:{generateLabels:function(t){var e=t.data;return e.labels.length&&e.datasets.length?e.labels.map((function(n,i){var a=t.getDatasetMeta(0),r=a.controller.getStyle(i);return{text:n,fillStyle:r.backgroundColor,strokeStyle:r.borderColor,lineWidth:r.borderWidth,hidden:isNaN(e.datasets[0].data[i])||a.data[i].hidden,index:i}})):[]}},onClick:function(t,e){var n,i,a,r=e.index,o=this.chart;for(n=0,i=(o.data.datasets||[]).length;n<i;++n)(a=o.getDatasetMeta(n)).data[r].hidden=!a.data[r].hidden;o.update()}},tooltips:{callbacks:{title:function(){return""},label:function(t,e){return e.labels[t.index]+": "+t.yLabel}}}});var Zt=it.extend({dataElementType:wt.Arc,linkScales:H.noop,_dataElementOptions:["backgroundColor","borderColor","borderWidth","borderAlign","hoverBackgroundColor","hoverBorderColor","hoverBorderWidth"],_getIndexScaleId:function(){return this.chart.scale.id},_getValueScaleId:function(){return this.chart.scale.id},update:function(t){var e,n,i,a=this,r=a.getDataset(),o=a.getMeta(),s=a.chart.options.startAngle||0,l=a._starts=[],u=a._angles=[],d=o.data;for(a._updateRadius(),o.count=a.countVisibleElements(),e=0,n=r.data.length;e<n;e++)l[e]=s,i=a._computeAngle(e),u[e]=i,s+=i;for(e=0,n=d.length;e<n;++e)d[e]._options=a._resolveDataElementOptions(d[e],e),a.updateElement(d[e],e,t)},_updateRadius:function(){var t=this,e=t.chart,n=e.chartArea,i=e.options,a=Math.min(n.right-n.left,n.bottom-n.top);e.outerRadius=Math.max(a/2,0),e.innerRadius=Math.max(i.cutoutPercentage?e.outerRadius/100*i.cutoutPercentage:1,0),e.radiusLength=(e.outerRadius-e.innerRadius)/e.getVisibleDatasetCount(),t.outerRadius=e.outerRadius-e.radiusLength*t.index,t.innerRadius=t.outerRadius-e.radiusLength},updateElement:function(t,e,n){var i=this,a=i.chart,r=i.getDataset(),o=a.options,s=o.animation,l=a.scale,u=a.data.labels,d=l.xCenter,h=l.yCenter,c=o.startAngle,f=t.hidden?0:l.getDistanceFromCenterForValue(r.data[e]),g=i._starts[e],m=g+(t.hidden?0:i._angles[e]),p=s.animateScale?0:l.getDistanceFromCenterForValue(r.data[e]),v=t._options||{};H.extend(t,{_datasetIndex:i.index,_index:e,_scale:l,_model:{backgroundColor:v.backgroundColor,borderColor:v.borderColor,borderWidth:v.borderWidth,borderAlign:v.borderAlign,x:d,y:h,innerRadius:0,outerRadius:n?p:f,startAngle:n&&s.animateRotate?c:g,endAngle:n&&s.animateRotate?c:m,label:H.valueAtIndexOrDefault(u,e,u[e])}}),t.pivot()},countVisibleElements:function(){var t=this.getDataset(),e=this.getMeta(),n=0;return H.each(e.data,(function(e,i){isNaN(t.data[i])||e.hidden||n++})),n},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor,a=H.valueOrDefault;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=a(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=a(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=a(n.hoverBorderWidth,n.borderWidth)},_computeAngle:function(t){var e=this,n=this.getMeta().count,i=e.getDataset(),a=e.getMeta();if(isNaN(i.data[t])||a.data[t].hidden)return 0;var r={chart:e.chart,dataIndex:t,dataset:i,datasetIndex:e.index};return qt([e.chart.options.elements.arc.angle,2*Math.PI/n],r,t)}});W._set("pie",H.clone(W.doughnut)),W._set("pie",{cutoutPercentage:0});var $t=Yt,Xt=H.valueOrDefault;W._set("radar",{spanGaps:!1,scale:{type:"radialLinear"},elements:{line:{fill:"start",tension:0}}});var Kt=it.extend({datasetElementType:wt.Line,dataElementType:wt.Point,linkScales:H.noop,_datasetElementOptions:["backgroundColor","borderWidth","borderColor","borderCapStyle","borderDash","borderDashOffset","borderJoinStyle","fill"],_dataElementOptions:{backgroundColor:"pointBackgroundColor",borderColor:"pointBorderColor",borderWidth:"pointBorderWidth",hitRadius:"pointHitRadius",hoverBackgroundColor:"pointHoverBackgroundColor",hoverBorderColor:"pointHoverBorderColor",hoverBorderWidth:"pointHoverBorderWidth",hoverRadius:"pointHoverRadius",pointStyle:"pointStyle",radius:"pointRadius",rotation:"pointRotation"},_getIndexScaleId:function(){return this.chart.scale.id},_getValueScaleId:function(){return this.chart.scale.id},update:function(t){var e,n,i=this,a=i.getMeta(),r=a.dataset,o=a.data||[],s=i.chart.scale,l=i._config;for(void 0!==l.tension&&void 0===l.lineTension&&(l.lineTension=l.tension),r._scale=s,r._datasetIndex=i.index,r._children=o,r._loop=!0,r._model=i._resolveDatasetElementOptions(r),r.pivot(),e=0,n=o.length;e<n;++e)i.updateElement(o[e],e,t);for(i.updateBezierControlPoints(),e=0,n=o.length;e<n;++e)o[e].pivot()},updateElement:function(t,e,n){var i=this,a=t.custom||{},r=i.getDataset(),o=i.chart.scale,s=o.getPointPositionForValue(e,r.data[e]),l=i._resolveDataElementOptions(t,e),u=i.getMeta().dataset._model,d=n?o.xCenter:s.x,h=n?o.yCenter:s.y;t._scale=o,t._options=l,t._datasetIndex=i.index,t._index=e,t._model={x:d,y:h,skip:a.skip||isNaN(d)||isNaN(h),radius:l.radius,pointStyle:l.pointStyle,rotation:l.rotation,backgroundColor:l.backgroundColor,borderColor:l.borderColor,borderWidth:l.borderWidth,tension:Xt(a.tension,u?u.tension:0),hitRadius:l.hitRadius}},_resolveDatasetElementOptions:function(){var t=this,e=t._config,n=t.chart.options,i=it.prototype._resolveDatasetElementOptions.apply(t,arguments);return i.spanGaps=Xt(e.spanGaps,n.spanGaps),i.tension=Xt(e.lineTension,n.elements.line.tension),i},updateBezierControlPoints:function(){var t,e,n,i,a=this.getMeta(),r=this.chart.chartArea,o=a.data||[];function s(t,e,n){return Math.max(Math.min(t,n),e)}for(a.dataset._model.spanGaps&&(o=o.filter((function(t){return!t._model.skip}))),t=0,e=o.length;t<e;++t)n=o[t]._model,i=H.splineCurve(H.previousItem(o,t,!0)._model,n,H.nextItem(o,t,!0)._model,n.tension),n.controlPointPreviousX=s(i.previous.x,r.left,r.right),n.controlPointPreviousY=s(i.previous.y,r.top,r.bottom),n.controlPointNextX=s(i.next.x,r.left,r.right),n.controlPointNextY=s(i.next.y,r.top,r.bottom)},setHoverStyle:function(t){var e=t._model,n=t._options,i=H.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth,radius:e.radius},e.backgroundColor=Xt(n.hoverBackgroundColor,i(n.backgroundColor)),e.borderColor=Xt(n.hoverBorderColor,i(n.borderColor)),e.borderWidth=Xt(n.hoverBorderWidth,n.borderWidth),e.radius=Xt(n.hoverRadius,n.radius)}});W._set("scatter",{hover:{mode:"single"},scales:{xAxes:[{id:"x-axis-1",type:"linear",position:"bottom"}],yAxes:[{id:"y-axis-1",type:"linear",position:"left"}]},tooltips:{callbacks:{title:function(){return""},label:function(t){return"("+t.xLabel+", "+t.yLabel+")"}}}}),W._set("global",{datasets:{scatter:{showLine:!1}}});var Jt={bar:Ot,bubble:It,doughnut:Yt,horizontalBar:zt,line:Gt,polarArea:Zt,pie:$t,radar:Kt,scatter:Gt};function Qt(t,e){return t.native?{x:t.x,y:t.y}:H.getRelativePosition(t,e)}function te(t,e){var n,i,a,r,o,s,l=t._getSortedVisibleDatasetMetas();for(i=0,r=l.length;i<r;++i)for(a=0,o=(n=l[i].data).length;a<o;++a)(s=n[a])._view.skip||e(s)}function ee(t,e){var n=[];return te(t,(function(t){t.inRange(e.x,e.y)&&n.push(t)})),n}function ne(t,e,n,i){var a=Number.POSITIVE_INFINITY,r=[];return te(t,(function(t){if(!n||t.inRange(e.x,e.y)){var o=t.getCenterPoint(),s=i(e,o);s<a?(r=[t],a=s):s===a&&r.push(t)}})),r}function ie(t){var e=-1!==t.indexOf("x"),n=-1!==t.indexOf("y");return function(t,i){var a=e?Math.abs(t.x-i.x):0,r=n?Math.abs(t.y-i.y):0;return Math.sqrt(Math.pow(a,2)+Math.pow(r,2))}}function ae(t,e,n){var i=Qt(e,t);n.axis=n.axis||"x";var a=ie(n.axis),r=n.intersect?ee(t,i):ne(t,i,!1,a),o=[];return r.length?(t._getSortedVisibleDatasetMetas().forEach((function(t){var e=t.data[r[0]._index];e&&!e._view.skip&&o.push(e)})),o):[]}var re={modes:{single:function(t,e){var n=Qt(e,t),i=[];return te(t,(function(t){if(t.inRange(n.x,n.y))return i.push(t),i})),i.slice(0,1)},label:ae,index:ae,dataset:function(t,e,n){var i=Qt(e,t);n.axis=n.axis||"xy";var a=ie(n.axis),r=n.intersect?ee(t,i):ne(t,i,!1,a);return r.length>0&&(r=t.getDatasetMeta(r[0]._datasetIndex).data),r},"x-axis":function(t,e){return ae(t,e,{intersect:!1})},point:function(t,e){return ee(t,Qt(e,t))},nearest:function(t,e,n){var i=Qt(e,t);n.axis=n.axis||"xy";var a=ie(n.axis);return ne(t,i,n.intersect,a)},x:function(t,e,n){var i=Qt(e,t),a=[],r=!1;return te(t,(function(t){t.inXRange(i.x)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a},y:function(t,e,n){var i=Qt(e,t),a=[],r=!1;return te(t,(function(t){t.inYRange(i.y)&&a.push(t),t.inRange(i.x,i.y)&&(r=!0)})),n.intersect&&!r&&(a=[]),a}}},oe=H.extend;function se(t,e){return H.where(t,(function(t){return t.pos===e}))}function le(t,e){return t.sort((function(t,n){var i=e?n:t,a=e?t:n;return i.weight===a.weight?i.index-a.index:i.weight-a.weight}))}function ue(t,e,n,i){return Math.max(t[n],e[n])+Math.max(t[i],e[i])}function de(t,e,n){var i,a,r=n.box,o=t.maxPadding;if(n.size&&(t[n.pos]-=n.size),n.size=n.horizontal?r.height:r.width,t[n.pos]+=n.size,r.getPadding){var s=r.getPadding();o.top=Math.max(o.top,s.top),o.left=Math.max(o.left,s.left),o.bottom=Math.max(o.bottom,s.bottom),o.right=Math.max(o.right,s.right)}if(i=e.outerWidth-ue(o,t,"left","right"),a=e.outerHeight-ue(o,t,"top","bottom"),i!==t.w||a!==t.h)return t.w=i,t.h=a,n.horizontal?i!==t.w:a!==t.h}function he(t,e){var n=e.maxPadding;function i(t){var i={left:0,top:0,right:0,bottom:0};return t.forEach((function(t){i[t]=Math.max(e[t],n[t])})),i}return i(t?["left","right"]:["top","bottom"])}function ce(t,e,n){var i,a,r,o,s,l,u=[];for(i=0,a=t.length;i<a;++i)(o=(r=t[i]).box).update(r.width||e.w,r.height||e.h,he(r.horizontal,e)),de(e,n,r)&&(l=!0,u.length&&(s=!0)),o.fullWidth||u.push(r);return s&&ce(u,e,n)||l}function fe(t,e,n){var i,a,r,o,s=n.padding,l=e.x,u=e.y;for(i=0,a=t.length;i<a;++i)o=(r=t[i]).box,r.horizontal?(o.left=o.fullWidth?s.left:e.left,o.right=o.fullWidth?n.outerWidth-s.right:e.left+e.w,o.top=u,o.bottom=u+o.height,o.width=o.right-o.left,u=o.bottom):(o.left=l,o.right=l+o.width,o.top=e.top,o.bottom=e.top+e.h,o.height=o.bottom-o.top,l=o.right);e.x=l,e.y=u}W._set("global",{layout:{padding:{top:0,right:0,bottom:0,left:0}}});var ge,me={defaults:{},addBox:function(t,e){t.boxes||(t.boxes=[]),e.fullWidth=e.fullWidth||!1,e.position=e.position||"top",e.weight=e.weight||0,e._layers=e._layers||function(){return[{z:0,draw:function(){e.draw.apply(e,arguments)}}]},t.boxes.push(e)},removeBox:function(t,e){var n=t.boxes?t.boxes.indexOf(e):-1;-1!==n&&t.boxes.splice(n,1)},configure:function(t,e,n){for(var i,a=["fullWidth","position","weight"],r=a.length,o=0;o<r;++o)i=a[o],n.hasOwnProperty(i)&&(e[i]=n[i])},update:function(t,e,n){if(t){var i=t.options.layout||{},a=H.options.toPadding(i.padding),r=e-a.width,o=n-a.height,s=function(t){var e=function(t){var e,n,i,a=[];for(e=0,n=(t||[]).length;e<n;++e)i=t[e],a.push({index:e,box:i,pos:i.position,horizontal:i.isHorizontal(),weight:i.weight});return a}(t),n=le(se(e,"left"),!0),i=le(se(e,"right")),a=le(se(e,"top"),!0),r=le(se(e,"bottom"));return{leftAndTop:n.concat(a),rightAndBottom:i.concat(r),chartArea:se(e,"chartArea"),vertical:n.concat(i),horizontal:a.concat(r)}}(t.boxes),l=s.vertical,u=s.horizontal,d=Object.freeze({outerWidth:e,outerHeight:n,padding:a,availableWidth:r,vBoxMaxWidth:r/2/l.length,hBoxMaxHeight:o/2}),h=oe({maxPadding:oe({},a),w:r,h:o,x:a.left,y:a.top},a);!function(t,e){var n,i,a;for(n=0,i=t.length;n<i;++n)(a=t[n]).width=a.horizontal?a.box.fullWidth&&e.availableWidth:e.vBoxMaxWidth,a.height=a.horizontal&&e.hBoxMaxHeight}(l.concat(u),d),ce(l,h,d),ce(u,h,d)&&ce(l,h,d),function(t){var e=t.maxPadding;function n(n){var i=Math.max(e[n]-t[n],0);return t[n]+=i,i}t.y+=n("top"),t.x+=n("left"),n("right"),n("bottom")}(h),fe(s.leftAndTop,h,d),h.x+=h.w,h.y+=h.h,fe(s.rightAndBottom,h,d),t.chartArea={left:h.left,top:h.top,right:h.left+h.w,bottom:h.top+h.h},H.each(s.chartArea,(function(e){var n=e.box;oe(n,t.chartArea),n.update(h.w,h.h)}))}}},pe=(ge=Object.freeze({__proto__:null,default:"@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0}"}))&&ge.default||ge,ve="$chartjs",be="chartjs-size-monitor",ye="chartjs-render-monitor",xe="chartjs-render-animation",_e=["animationstart","webkitAnimationStart"],we={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function ke(t,e){var n=H.getStyle(t,e),i=n&&n.match(/^(\d+)(\.\d+)?px$/);return i?Number(i[1]):void 0}var Me=!!function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}()&&{passive:!0};function Se(t,e,n){t.addEventListener(e,n,Me)}function De(t,e,n){t.removeEventListener(e,n,Me)}function Ce(t,e,n,i,a){return{type:t,chart:e,native:a||null,x:void 0!==n?n:null,y:void 0!==i?i:null}}function Pe(t){var e=document.createElement("div");return e.className=t||"",e}function Te(t,e,n){var i,a,r,o,s=t[ve]||(t[ve]={}),l=s.resizer=function(t){var e=Pe(be),n=Pe(be+"-expand"),i=Pe(be+"-shrink");n.appendChild(Pe()),i.appendChild(Pe()),e.appendChild(n),e.appendChild(i),e._reset=function(){n.scrollLeft=1e6,n.scrollTop=1e6,i.scrollLeft=1e6,i.scrollTop=1e6};var a=function(){e._reset(),t()};return Se(n,"scroll",a.bind(n,"expand")),Se(i,"scroll",a.bind(i,"shrink")),e}((i=function(){if(s.resizer){var i=n.options.maintainAspectRatio&&t.parentNode,a=i?i.clientWidth:0;e(Ce("resize",n)),i&&i.clientWidth<a&&n.canvas&&e(Ce("resize",n))}},r=!1,o=[],function(){o=Array.prototype.slice.call(arguments),a=a||this,r||(r=!0,H.requestAnimFrame.call(window,(function(){r=!1,i.apply(a,o)})))}));!function(t,e){var n=t[ve]||(t[ve]={}),i=n.renderProxy=function(t){t.animationName===xe&&e()};H.each(_e,(function(e){Se(t,e,i)})),n.reflow=!!t.offsetParent,t.classList.add(ye)}(t,(function(){if(s.resizer){var e=t.parentNode;e&&e!==l.parentNode&&e.insertBefore(l,e.firstChild),l._reset()}}))}function Oe(t){var e=t[ve]||{},n=e.resizer;delete e.resizer,function(t){var e=t[ve]||{},n=e.renderProxy;n&&(H.each(_e,(function(e){De(t,e,n)})),delete e.renderProxy),t.classList.remove(ye)}(t),n&&n.parentNode&&n.parentNode.removeChild(n)}var Ae={disableCSSInjection:!1,_enabled:"undefined"!=typeof window&&"undefined"!=typeof document,_ensureLoaded:function(t){if(!this.disableCSSInjection){var e=t.getRootNode?t.getRootNode():document;!function(t,e){var n=t[ve]||(t[ve]={});if(!n.containsStyles){n.containsStyles=!0,e="/* Chart.js */\n"+e;var i=document.createElement("style");i.setAttribute("type","text/css"),i.appendChild(document.createTextNode(e)),t.appendChild(i)}}(e.host?e:document.head,pe)}},acquireContext:function(t,e){"string"==typeof t?t=document.getElementById(t):t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas);var n=t&&t.getContext&&t.getContext("2d");return n&&n.canvas===t?(this._ensureLoaded(t),function(t,e){var n=t.style,i=t.getAttribute("height"),a=t.getAttribute("width");if(t[ve]={initial:{height:i,width:a,style:{display:n.display,height:n.height,width:n.width}}},n.display=n.display||"block",null===a||""===a){var r=ke(t,"width");void 0!==r&&(t.width=r)}if(null===i||""===i)if(""===t.style.height)t.height=t.width/(e.options.aspectRatio||2);else{var o=ke(t,"height");void 0!==r&&(t.height=o)}}(t,e),n):null},releaseContext:function(t){var e=t.canvas;if(e[ve]){var n=e[ve].initial;["height","width"].forEach((function(t){var i=n[t];H.isNullOrUndef(i)?e.removeAttribute(t):e.setAttribute(t,i)})),H.each(n.style||{},(function(t,n){e.style[n]=t})),e.width=e.width,delete e[ve]}},addEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=n[ve]||(n[ve]={});Se(i,e,(a.proxies||(a.proxies={}))[t.id+"_"+e]=function(e){n(function(t,e){var n=we[t.type]||t.type,i=H.getRelativePosition(t,e);return Ce(n,e,i.x,i.y,t)}(e,t))})}else Te(i,n,t)},removeEventListener:function(t,e,n){var i=t.canvas;if("resize"!==e){var a=((n[ve]||{}).proxies||{})[t.id+"_"+e];a&&De(i,e,a)}else Oe(i)}};H.addEvent=Se,H.removeEvent=De;var Fe=Ae._enabled?Ae:{acquireContext:function(t){return t&&t.canvas&&(t=t.canvas),t&&t.getContext("2d")||null}},Ie=H.extend({initialize:function(){},acquireContext:function(){},releaseContext:function(){},addEventListener:function(){},removeEventListener:function(){}},Fe);W._set("global",{plugins:{}});var Le={_plugins:[],_cacheId:0,register:function(t){var e=this._plugins;[].concat(t).forEach((function(t){-1===e.indexOf(t)&&e.push(t)})),this._cacheId++},unregister:function(t){var e=this._plugins;[].concat(t).forEach((function(t){var n=e.indexOf(t);-1!==n&&e.splice(n,1)})),this._cacheId++},clear:function(){this._plugins=[],this._cacheId++},count:function(){return this._plugins.length},getAll:function(){return this._plugins},notify:function(t,e,n){var i,a,r,o,s,l=this.descriptors(t),u=l.length;for(i=0;i<u;++i)if("function"==typeof(s=(r=(a=l[i]).plugin)[e])&&((o=[t].concat(n||[])).push(a.options),!1===s.apply(r,o)))return!1;return!0},descriptors:function(t){var e=t.$plugins||(t.$plugins={});if(e.id===this._cacheId)return e.descriptors;var n=[],i=[],a=t&&t.config||{},r=a.options&&a.options.plugins||{};return this._plugins.concat(a.plugins||[]).forEach((function(t){if(-1===n.indexOf(t)){var e=t.id,a=r[e];!1!==a&&(!0===a&&(a=H.clone(W.global.plugins[e])),n.push(t),i.push({plugin:t,options:a||{}}))}})),e.descriptors=i,e.id=this._cacheId,i},_invalidate:function(t){delete t.$plugins}},Re={constructors:{},defaults:{},registerScaleType:function(t,e,n){this.constructors[t]=e,this.defaults[t]=H.clone(n)},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0},getScaleDefaults:function(t){return this.defaults.hasOwnProperty(t)?H.merge({},[W.scale,this.defaults[t]]):{}},updateScaleDefaults:function(t,e){this.defaults.hasOwnProperty(t)&&(this.defaults[t]=H.extend(this.defaults[t],e))},addScalesToLayout:function(t){H.each(t.scales,(function(e){e.fullWidth=e.options.fullWidth,e.position=e.options.position,e.weight=e.options.weight,me.addBox(t,e)}))}},Ne=H.valueOrDefault,We=H.rtl.getRtlAdapter;W._set("global",{tooltips:{enabled:!0,custom:null,mode:"nearest",position:"average",intersect:!0,backgroundColor:"rgba(0,0,0,0.8)",titleFontStyle:"bold",titleSpacing:2,titleMarginBottom:6,titleFontColor:"#fff",titleAlign:"left",bodySpacing:2,bodyFontColor:"#fff",bodyAlign:"left",footerFontStyle:"bold",footerSpacing:2,footerMarginTop:6,footerFontColor:"#fff",footerAlign:"left",yPadding:6,xPadding:6,caretPadding:2,caretSize:5,cornerRadius:6,multiKeyBackground:"#fff",displayColors:!0,borderColor:"rgba(0,0,0,0)",borderWidth:0,callbacks:{beforeTitle:H.noop,title:function(t,e){var n="",i=e.labels,a=i?i.length:0;if(t.length>0){var r=t[0];r.label?n=r.label:r.xLabel?n=r.xLabel:a>0&&r.index<a&&(n=i[r.index])}return n},afterTitle:H.noop,beforeBody:H.noop,beforeLabel:H.noop,label:function(t,e){var n=e.datasets[t.datasetIndex].label||"";return n&&(n+=": "),H.isNullOrUndef(t.value)?n+=t.yLabel:n+=t.value,n},labelColor:function(t,e){var n=e.getDatasetMeta(t.datasetIndex).data[t.index]._view;return{borderColor:n.borderColor,backgroundColor:n.backgroundColor}},labelTextColor:function(){return this._options.bodyFontColor},afterLabel:H.noop,afterBody:H.noop,beforeFooter:H.noop,footer:H.noop,afterFooter:H.noop}}});var Ye={average:function(t){if(!t.length)return!1;var e,n,i=0,a=0,r=0;for(e=0,n=t.length;e<n;++e){var o=t[e];if(o&&o.hasValue()){var s=o.tooltipPosition();i+=s.x,a+=s.y,++r}}return{x:i/r,y:a/r}},nearest:function(t,e){var n,i,a,r=e.x,o=e.y,s=Number.POSITIVE_INFINITY;for(n=0,i=t.length;n<i;++n){var l=t[n];if(l&&l.hasValue()){var u=l.getCenterPoint(),d=H.distanceBetweenPoints(e,u);d<s&&(s=d,a=l)}}if(a){var h=a.tooltipPosition();r=h.x,o=h.y}return{x:r,y:o}}};function ze(t,e){return e&&(H.isArray(e)?Array.prototype.push.apply(t,e):t.push(e)),t}function Ee(t){return("string"==typeof t||t instanceof String)&&t.indexOf("\n")>-1?t.split("\n"):t}function Ve(t){var e=W.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,rtl:t.rtl,textDirection:t.textDirection,bodyFontColor:t.bodyFontColor,_bodyFontFamily:Ne(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:Ne(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:Ne(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:Ne(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:Ne(t.titleFontStyle,e.defaultFontStyle),titleFontSize:Ne(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:Ne(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:Ne(t.footerFontStyle,e.defaultFontStyle),footerFontSize:Ne(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function He(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function Be(t){return ze([],Ee(t))}var je=$.extend({initialize:function(){this._model=Ve(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options,n=e.callbacks,i=n.beforeTitle.apply(t,arguments),a=n.title.apply(t,arguments),r=n.afterTitle.apply(t,arguments),o=[];return o=ze(o,Ee(i)),o=ze(o,Ee(a)),o=ze(o,Ee(r))},getBeforeBody:function(){return Be(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var n=this,i=n._options.callbacks,a=[];return H.each(t,(function(t){var r={before:[],lines:[],after:[]};ze(r.before,Ee(i.beforeLabel.call(n,t,e))),ze(r.lines,i.label.call(n,t,e)),ze(r.after,Ee(i.afterLabel.call(n,t,e))),a.push(r)})),a},getAfterBody:function(){return Be(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this,e=t._options.callbacks,n=e.beforeFooter.apply(t,arguments),i=e.footer.apply(t,arguments),a=e.afterFooter.apply(t,arguments),r=[];return r=ze(r,Ee(n)),r=ze(r,Ee(i)),r=ze(r,Ee(a))},update:function(t){var e,n,i,a,r,o,s,l,u,d,h=this,c=h._options,f=h._model,g=h._model=Ve(c),m=h._active,p=h._data,v={xAlign:f.xAlign,yAlign:f.yAlign},b={x:f.x,y:f.y},y={width:f.width,height:f.height},x={x:f.caretX,y:f.caretY};if(m.length){g.opacity=1;var _=[],w=[];x=Ye[c.position].call(h,m,h._eventPosition);var k=[];for(e=0,n=m.length;e<n;++e)k.push((i=m[e],a=void 0,r=void 0,o=void 0,s=void 0,l=void 0,u=void 0,d=void 0,a=i._xScale,r=i._yScale||i._scale,o=i._index,s=i._datasetIndex,l=i._chart.getDatasetMeta(s).controller,u=l._getIndexScale(),d=l._getValueScale(),{xLabel:a?a.getLabelForIndex(o,s):"",yLabel:r?r.getLabelForIndex(o,s):"",label:u?""+u.getLabelForIndex(o,s):"",value:d?""+d.getLabelForIndex(o,s):"",index:o,datasetIndex:s,x:i._model.x,y:i._model.y}));c.filter&&(k=k.filter((function(t){return c.filter(t,p)}))),c.itemSort&&(k=k.sort((function(t,e){return c.itemSort(t,e,p)}))),H.each(k,(function(t){_.push(c.callbacks.labelColor.call(h,t,h._chart)),w.push(c.callbacks.labelTextColor.call(h,t,h._chart))})),g.title=h.getTitle(k,p),g.beforeBody=h.getBeforeBody(k,p),g.body=h.getBody(k,p),g.afterBody=h.getAfterBody(k,p),g.footer=h.getFooter(k,p),g.x=x.x,g.y=x.y,g.caretPadding=c.caretPadding,g.labelColors=_,g.labelTextColors=w,g.dataPoints=k,y=function(t,e){var n=t._chart.ctx,i=2*e.yPadding,a=0,r=e.body,o=r.reduce((function(t,e){return t+e.before.length+e.lines.length+e.after.length}),0);o+=e.beforeBody.length+e.afterBody.length;var s=e.title.length,l=e.footer.length,u=e.titleFontSize,d=e.bodyFontSize,h=e.footerFontSize;i+=s*u,i+=s?(s-1)*e.titleSpacing:0,i+=s?e.titleMarginBottom:0,i+=o*d,i+=o?(o-1)*e.bodySpacing:0,i+=l?e.footerMarginTop:0,i+=l*h,i+=l?(l-1)*e.footerSpacing:0;var c=0,f=function(t){a=Math.max(a,n.measureText(t).width+c)};return n.font=H.fontString(u,e._titleFontStyle,e._titleFontFamily),H.each(e.title,f),n.font=H.fontString(d,e._bodyFontStyle,e._bodyFontFamily),H.each(e.beforeBody.concat(e.afterBody),f),c=e.displayColors?d+2:0,H.each(r,(function(t){H.each(t.before,f),H.each(t.lines,f),H.each(t.after,f)})),c=0,n.font=H.fontString(h,e._footerFontStyle,e._footerFontFamily),H.each(e.footer,f),{width:a+=2*e.xPadding,height:i}}(this,g),b=function(t,e,n,i){var a=t.x,r=t.y,o=t.caretSize,s=t.caretPadding,l=t.cornerRadius,u=n.xAlign,d=n.yAlign,h=o+s,c=l+s;return"right"===u?a-=e.width:"center"===u&&((a-=e.width/2)+e.width>i.width&&(a=i.width-e.width),a<0&&(a=0)),"top"===d?r+=h:r-="bottom"===d?e.height+h:e.height/2,"center"===d?"left"===u?a+=h:"right"===u&&(a-=h):"left"===u?a-=c:"right"===u&&(a+=c),{x:a,y:r}}(g,y,v=function(t,e){var n,i,a,r,o,s=t._model,l=t._chart,u=t._chart.chartArea,d="center",h="center";s.y<e.height?h="top":s.y>l.height-e.height&&(h="bottom");var c=(u.left+u.right)/2,f=(u.top+u.bottom)/2;"center"===h?(n=function(t){return t<=c},i=function(t){return t>c}):(n=function(t){return t<=e.width/2},i=function(t){return t>=l.width-e.width/2}),a=function(t){return t+e.width+s.caretSize+s.caretPadding>l.width},r=function(t){return t-e.width-s.caretSize-s.caretPadding<0},o=function(t){return t<=f?"top":"bottom"},n(s.x)?(d="left",a(s.x)&&(d="center",h=o(s.y))):i(s.x)&&(d="right",r(s.x)&&(d="center",h=o(s.y)));var g=t._options;return{xAlign:g.xAlign?g.xAlign:d,yAlign:g.yAlign?g.yAlign:h}}(this,y),h._chart)}else g.opacity=0;return g.xAlign=v.xAlign,g.yAlign=v.yAlign,g.x=b.x,g.y=b.y,g.width=y.width,g.height=y.height,g.caretX=x.x,g.caretY=x.y,h._model=g,t&&c.custom&&c.custom.call(h,g),h},drawCaret:function(t,e){var n=this._chart.ctx,i=this._view,a=this.getCaretPosition(t,e,i);n.lineTo(a.x1,a.y1),n.lineTo(a.x2,a.y2),n.lineTo(a.x3,a.y3)},getCaretPosition:function(t,e,n){var i,a,r,o,s,l,u=n.caretSize,d=n.cornerRadius,h=n.xAlign,c=n.yAlign,f=t.x,g=t.y,m=e.width,p=e.height;if("center"===c)s=g+p/2,"left"===h?(a=(i=f)-u,r=i,o=s+u,l=s-u):(a=(i=f+m)+u,r=i,o=s-u,l=s+u);else if("left"===h?(i=(a=f+d+u)-u,r=a+u):"right"===h?(i=(a=f+m-d-u)-u,r=a+u):(i=(a=n.caretX)-u,r=a+u),"top"===c)s=(o=g)-u,l=o;else{s=(o=g+p)+u,l=o;var v=r;r=i,i=v}return{x1:i,x2:a,x3:r,y1:o,y2:s,y3:l}},drawTitle:function(t,e,n){var i,a,r,o=e.title,s=o.length;if(s){var l=We(e.rtl,e.x,e.width);for(t.x=He(e,e._titleAlign),n.textAlign=l.textAlign(e._titleAlign),n.textBaseline="middle",i=e.titleFontSize,a=e.titleSpacing,n.fillStyle=e.titleFontColor,n.font=H.fontString(i,e._titleFontStyle,e._titleFontFamily),r=0;r<s;++r)n.fillText(o[r],l.x(t.x),t.y+i/2),t.y+=i+a,r+1===s&&(t.y+=e.titleMarginBottom-a)}},drawBody:function(t,e,n){var i,a,r,o,s,l,u,d,h=e.bodyFontSize,c=e.bodySpacing,f=e._bodyAlign,g=e.body,m=e.displayColors,p=0,v=m?He(e,"left"):0,b=We(e.rtl,e.x,e.width),y=function(e){n.fillText(e,b.x(t.x+p),t.y+h/2),t.y+=h+c},x=b.textAlign(f);for(n.textAlign=f,n.textBaseline="middle",n.font=H.fontString(h,e._bodyFontStyle,e._bodyFontFamily),t.x=He(e,x),n.fillStyle=e.bodyFontColor,H.each(e.beforeBody,y),p=m&&"right"!==x?"center"===f?h/2+1:h+2:0,s=0,u=g.length;s<u;++s){for(i=g[s],a=e.labelTextColors[s],r=e.labelColors[s],n.fillStyle=a,H.each(i.before,y),l=0,d=(o=i.lines).length;l<d;++l){if(m){var _=b.x(v);n.fillStyle=e.legendColorBackground,n.fillRect(b.leftForLtr(_,h),t.y,h,h),n.lineWidth=1,n.strokeStyle=r.borderColor,n.strokeRect(b.leftForLtr(_,h),t.y,h,h),n.fillStyle=r.backgroundColor,n.fillRect(b.leftForLtr(b.xPlus(_,1),h-2),t.y+1,h-2,h-2),n.fillStyle=a}y(o[l])}H.each(i.after,y)}p=0,H.each(e.afterBody,y),t.y-=c},drawFooter:function(t,e,n){var i,a,r=e.footer,o=r.length;if(o){var s=We(e.rtl,e.x,e.width);for(t.x=He(e,e._footerAlign),t.y+=e.footerMarginTop,n.textAlign=s.textAlign(e._footerAlign),n.textBaseline="middle",i=e.footerFontSize,n.fillStyle=e.footerFontColor,n.font=H.fontString(i,e._footerFontStyle,e._footerFontFamily),a=0;a<o;++a)n.fillText(r[a],s.x(t.x),t.y+i/2),t.y+=i+e.footerSpacing}},drawBackground:function(t,e,n,i){n.fillStyle=e.backgroundColor,n.strokeStyle=e.borderColor,n.lineWidth=e.borderWidth;var a=e.xAlign,r=e.yAlign,o=t.x,s=t.y,l=i.width,u=i.height,d=e.cornerRadius;n.beginPath(),n.moveTo(o+d,s),"top"===r&&this.drawCaret(t,i),n.lineTo(o+l-d,s),n.quadraticCurveTo(o+l,s,o+l,s+d),"center"===r&&"right"===a&&this.drawCaret(t,i),n.lineTo(o+l,s+u-d),n.quadraticCurveTo(o+l,s+u,o+l-d,s+u),"bottom"===r&&this.drawCaret(t,i),n.lineTo(o+d,s+u),n.quadraticCurveTo(o,s+u,o,s+u-d),"center"===r&&"left"===a&&this.drawCaret(t,i),n.lineTo(o,s+d),n.quadraticCurveTo(o,s,o+d,s),n.closePath(),n.fill(),e.borderWidth>0&&n.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var n={width:e.width,height:e.height},i={x:e.x,y:e.y},a=Math.abs(e.opacity<.001)?0:e.opacity,r=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&r&&(t.save(),t.globalAlpha=a,this.drawBackground(i,e,t,n),i.y+=e.yPadding,H.rtl.overrideTextDirection(t,e.textDirection),this.drawTitle(i,e,t),this.drawBody(i,e,t),this.drawFooter(i,e,t),H.rtl.restoreTextDirection(t,e.textDirection),t.restore())}},handleEvent:function(t){var e,n=this,i=n._options;return n._lastActive=n._lastActive||[],"mouseout"===t.type?n._active=[]:(n._active=n._chart.getElementsAtEventForMode(t,i.mode,i),i.reverse&&n._active.reverse()),(e=!H.arrayEquals(n._active,n._lastActive))&&(n._lastActive=n._active,(i.enabled||i.custom)&&(n._eventPosition={x:t.x,y:t.y},n.update(!0),n.pivot())),e}}),Ue=Ye,Ge=je;Ge.positioners=Ue;var qe=H.valueOrDefault;function Ze(){return H.merge({},[].slice.call(arguments),{merger:function(t,e,n,i){if("xAxes"===t||"yAxes"===t){var a,r,o,s=n[t].length;for(e[t]||(e[t]=[]),a=0;a<s;++a)o=n[t][a],r=qe(o.type,"xAxes"===t?"category":"linear"),a>=e[t].length&&e[t].push({}),!e[t][a].type||o.type&&o.type!==e[t][a].type?H.merge(e[t][a],[Re.getScaleDefaults(r),o]):H.merge(e[t][a],o)}else H._merger(t,e,n,i)}})}function $e(){return H.merge({},[].slice.call(arguments),{merger:function(t,e,n,i){var a=e[t]||{},r=n[t];"scales"===t?e[t]=Ze(a,r):"scale"===t?e[t]=H.merge(a,[Re.getScaleDefaults(r.type),r]):H._merger(t,e,n,i)}})}function Xe(t){var e=t.options;H.each(t.scales,(function(e){me.removeBox(t,e)})),e=$e(W.global,W[t.config.type],e),t.options=t.config.options=e,t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.tooltip._options=e.tooltips,t.tooltip.initialize()}function Ke(t,e,n){var i,a=function(t){return t.id===i};do{i=e+n++}while(H.findIndex(t,a)>=0);return i}function Je(t){return"top"===t||"bottom"===t}function Qe(t,e){return function(n,i){return n[t]===i[t]?n[e]-i[e]:n[t]-i[t]}}W._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var tn=function(t,e){return this.construct(t,e),this};H.extend(tn.prototype,{construct:function(t,e){var n=this;e=function(t){var e=(t=t||{}).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=$e(W.global,W[t.type],t.options||{}),t}(e);var i=Ie.acquireContext(t,e),a=i&&i.canvas,r=a&&a.height,o=a&&a.width;n.id=H.uid(),n.ctx=i,n.canvas=a,n.config=e,n.width=o,n.height=r,n.aspectRatio=r?o/r:null,n.options=e.options,n._bufferedRender=!1,n._layers=[],n.chart=n,n.controller=n,tn.instances[n.id]=n,Object.defineProperty(n,"data",{get:function(){return n.config.data},set:function(t){n.config.data=t}}),i&&a?(n.initialize(),n.update()):console.error("Failed to create chart: can't acquire context from the given item")},initialize:function(){var t=this;return Le.notify(t,"beforeInit"),H.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.initToolTip(),Le.notify(t,"afterInit"),t},clear:function(){return H.canvas.clear(this),this},stop:function(){return J.cancelAnimation(this),this},resize:function(t){var e=this,n=e.options,i=e.canvas,a=n.maintainAspectRatio&&e.aspectRatio||null,r=Math.max(0,Math.floor(H.getMaximumWidth(i))),o=Math.max(0,Math.floor(a?r/a:H.getMaximumHeight(i)));if((e.width!==r||e.height!==o)&&(i.width=e.width=r,i.height=e.height=o,i.style.width=r+"px",i.style.height=o+"px",H.retinaScale(e,n.devicePixelRatio),!t)){var s={width:r,height:o};Le.notify(e,"resize",[s]),n.onResize&&n.onResize(e,s),e.stop(),e.update({duration:n.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},n=t.scale;H.each(e.xAxes,(function(t,n){t.id||(t.id=Ke(e.xAxes,"x-axis-",n))})),H.each(e.yAxes,(function(t,n){t.id||(t.id=Ke(e.yAxes,"y-axis-",n))})),n&&(n.id=n.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,n=t.scales||{},i=[],a=Object.keys(n).reduce((function(t,e){return t[e]=!1,t}),{});e.scales&&(i=i.concat((e.scales.xAxes||[]).map((function(t){return{options:t,dtype:"category",dposition:"bottom"}})),(e.scales.yAxes||[]).map((function(t){return{options:t,dtype:"linear",dposition:"left"}})))),e.scale&&i.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),H.each(i,(function(e){var i=e.options,r=i.id,o=qe(i.type,e.dtype);Je(i.position)!==Je(e.dposition)&&(i.position=e.dposition),a[r]=!0;var s=null;if(r in n&&n[r].type===o)(s=n[r]).options=i,s.ctx=t.ctx,s.chart=t;else{var l=Re.getScaleConstructor(o);if(!l)return;s=new l({id:r,type:o,options:i,ctx:t.ctx,chart:t}),n[s.id]=s}s.mergeTicksOptions(),e.isDefault&&(t.scale=s)})),H.each(a,(function(t,e){t||delete n[e]})),t.scales=n,Re.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t,e,n=this,i=[],a=n.data.datasets;for(t=0,e=a.length;t<e;t++){var r=a[t],o=n.getDatasetMeta(t),s=r.type||n.config.type;if(o.type&&o.type!==s&&(n.destroyDatasetMeta(t),o=n.getDatasetMeta(t)),o.type=s,o.order=r.order||0,o.index=t,o.controller)o.controller.updateIndex(t),o.controller.linkScales();else{var l=Jt[o.type];if(void 0===l)throw new Error('"'+o.type+'" is not a chart type.');o.controller=new l(n,t),i.push(o.controller)}}return i},resetElements:function(){var t=this;H.each(t.data.datasets,(function(e,n){t.getDatasetMeta(n).controller.reset()}),t)},reset:function(){this.resetElements(),this.tooltip.initialize()},update:function(t){var e,n,i=this;if(t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]}),Xe(i),Le._invalidate(i),!1!==Le.notify(i,"beforeUpdate")){i.tooltip._data=i.data;var a=i.buildOrUpdateControllers();for(e=0,n=i.data.datasets.length;e<n;e++)i.getDatasetMeta(e).controller.buildOrUpdateElements();i.updateLayout(),i.options.animation&&i.options.animation.duration&&H.each(a,(function(t){t.reset()})),i.updateDatasets(),i.tooltip.initialize(),i.lastActive=[],Le.notify(i,"afterUpdate"),i._layers.sort(Qe("z","_idx")),i._bufferedRender?i._bufferedRequest={duration:t.duration,easing:t.easing,lazy:t.lazy}:i.render(t)}},updateLayout:function(){var t=this;!1!==Le.notify(t,"beforeLayout")&&(me.update(this,this.width,this.height),t._layers=[],H.each(t.boxes,(function(e){e._configure&&e._configure(),t._layers.push.apply(t._layers,e._layers())}),t),t._layers.forEach((function(t,e){t._idx=e})),Le.notify(t,"afterScaleUpdate"),Le.notify(t,"afterLayout"))},updateDatasets:function(){if(!1!==Le.notify(this,"beforeDatasetsUpdate")){for(var t=0,e=this.data.datasets.length;t<e;++t)this.updateDataset(t);Le.notify(this,"afterDatasetsUpdate")}},updateDataset:function(t){var e=this.getDatasetMeta(t),n={meta:e,index:t};!1!==Le.notify(this,"beforeDatasetUpdate",[n])&&(e.controller._update(),Le.notify(this,"afterDatasetUpdate",[n]))},render:function(t){var e=this;t&&"object"==typeof t||(t={duration:t,lazy:arguments[1]});var n=e.options.animation,i=qe(t.duration,n&&n.duration),a=t.lazy;if(!1!==Le.notify(e,"beforeRender")){var r=function(t){Le.notify(e,"afterRender"),H.callback(n&&n.onComplete,[t],e)};if(n&&i){var o=new K({numSteps:i/16.66,easing:t.easing||n.easing,render:function(t,e){var n=H.easing.effects[e.easing],i=e.currentStep,a=i/e.numSteps;t.draw(n(a),a,i)},onAnimationProgress:n.onProgress,onAnimationComplete:r});J.addAnimation(e,o,i,a)}else e.draw(),r(new K({numSteps:0,chart:e}));return e}},draw:function(t){var e,n,i=this;if(i.clear(),H.isNullOrUndef(t)&&(t=1),i.transition(t),!(i.width<=0||i.height<=0)&&!1!==Le.notify(i,"beforeDraw",[t])){for(n=i._layers,e=0;e<n.length&&n[e].z<=0;++e)n[e].draw(i.chartArea);for(i.drawDatasets(t);e<n.length;++e)n[e].draw(i.chartArea);i._drawTooltip(t),Le.notify(i,"afterDraw",[t])}},transition:function(t){for(var e=0,n=(this.data.datasets||[]).length;e<n;++e)this.isDatasetVisible(e)&&this.getDatasetMeta(e).controller.transition(t);this.tooltip.transition(t)},_getSortedDatasetMetas:function(t){var e,n,i=[];for(e=0,n=(this.data.datasets||[]).length;e<n;++e)t&&!this.isDatasetVisible(e)||i.push(this.getDatasetMeta(e));return i.sort(Qe("order","index")),i},_getSortedVisibleDatasetMetas:function(){return this._getSortedDatasetMetas(!0)},drawDatasets:function(t){var e,n;if(!1!==Le.notify(this,"beforeDatasetsDraw",[t])){for(n=(e=this._getSortedVisibleDatasetMetas()).length-1;n>=0;--n)this.drawDataset(e[n],t);Le.notify(this,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var n={meta:t,index:t.index,easingValue:e};!1!==Le.notify(this,"beforeDatasetDraw",[n])&&(t.controller.draw(e),Le.notify(this,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this.tooltip,n={tooltip:e,easingValue:t};!1!==Le.notify(this,"beforeTooltipDraw",[n])&&(e.draw(),Le.notify(this,"afterTooltipDraw",[n]))},getElementAtEvent:function(t){return re.modes.single(this,t)},getElementsAtEvent:function(t){return re.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return re.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,n){var i=re.modes[e];return"function"==typeof i?i(this,t,n):[]},getDatasetAtEvent:function(t){return re.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this.data.datasets[t];e._meta||(e._meta={});var n=e._meta[this.id];return n||(n=e._meta[this.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e.order||0,index:t}),n},getVisibleDatasetCount:function(){for(var t=0,e=0,n=this.data.datasets.length;e<n;++e)this.isDatasetVisible(e)&&t++;return t},isDatasetVisible:function(t){var e=this.getDatasetMeta(t);return"boolean"==typeof e.hidden?!e.hidden:!this.data.datasets[t].hidden},generateLegend:function(){return this.options.legendCallback(this)},destroyDatasetMeta:function(t){var e=this.id,n=this.data.datasets[t],i=n._meta&&n._meta[e];i&&(i.controller.destroy(),delete n._meta[e])},destroy:function(){var t,e,n=this,i=n.canvas;for(n.stop(),t=0,e=n.data.datasets.length;t<e;++t)n.destroyDatasetMeta(t);i&&(n.unbindEvents(),H.canvas.clear(n),Ie.releaseContext(n.ctx),n.canvas=null,n.ctx=null),Le.notify(n,"destroy"),delete tn.instances[n.id]},toBase64Image:function(){return this.canvas.toDataURL.apply(this.canvas,arguments)},initToolTip:function(){var t=this;t.tooltip=new Ge({_chart:t,_chartInstance:t,_data:t.data,_options:t.options.tooltips},t)},bindEvents:function(){var t=this,e=t._listeners={},n=function(){t.eventHandler.apply(t,arguments)};H.each(t.options.events,(function(i){Ie.addEventListener(t,i,n),e[i]=n})),t.options.responsive&&(n=function(){t.resize()},Ie.addEventListener(t,"resize",n),e.resize=n)},unbindEvents:function(){var t=this,e=t._listeners;e&&(delete t._listeners,H.each(e,(function(e,n){Ie.removeEventListener(t,n,e)})))},updateHoverStyle:function(t,e,n){var i,a,r,o=n?"set":"remove";for(a=0,r=t.length;a<r;++a)(i=t[a])&&this.getDatasetMeta(i._datasetIndex).controller[o+"HoverStyle"](i);"dataset"===e&&this.getDatasetMeta(t[0]._datasetIndex).controller["_"+o+"DatasetHoverStyle"]()},eventHandler:function(t){var e=this,n=e.tooltip;if(!1!==Le.notify(e,"beforeEvent",[t])){e._bufferedRender=!0,e._bufferedRequest=null;var i=e.handleEvent(t);n&&(i=n._start?n.handleEvent(t):i|n.handleEvent(t)),Le.notify(e,"afterEvent",[t]);var a=e._bufferedRequest;return a?e.render(a):i&&!e.animating&&(e.stop(),e.render({duration:e.options.hover.animationDuration,lazy:!0})),e._bufferedRender=!1,e._bufferedRequest=null,e}},handleEvent:function(t){var e,n=this,i=n.options||{},a=i.hover;return n.lastActive=n.lastActive||[],"mouseout"===t.type?n.active=[]:n.active=n.getElementsAtEventForMode(t,a.mode,a),H.callback(i.onHover||i.hover.onHover,[t.native,n.active],n),"mouseup"!==t.type&&"click"!==t.type||i.onClick&&i.onClick.call(n,t.native,n.active),n.lastActive.length&&n.updateHoverStyle(n.lastActive,a.mode,!1),n.active.length&&a.mode&&n.updateHoverStyle(n.active,a.mode,!0),e=!H.arrayEquals(n.active,n.lastActive),n.lastActive=n.active,e}}),tn.instances={};var en=tn;tn.Controller=tn,tn.types={},H.configMerge=$e,H.scaleMerge=Ze;function nn(){throw new Error("This method is not implemented: either no adapter can be found or an incomplete integration was provided.")}function an(t){this.options=t||{}}H.extend(an.prototype,{formats:nn,parse:nn,format:nn,add:nn,diff:nn,startOf:nn,endOf:nn,_create:function(t){return t}}),an.override=function(t){H.extend(an.prototype,t)};var rn={_date:an},on={formatters:{values:function(t){return H.isArray(t)?t:""+t},linear:function(t,e,n){var i=n.length>3?n[2]-n[1]:n[1]-n[0];Math.abs(i)>1&&t!==Math.floor(t)&&(i=t-Math.floor(t));var a=H.log10(Math.abs(i)),r="";if(0!==t)if(Math.max(Math.abs(n[0]),Math.abs(n[n.length-1]))<1e-4){var o=H.log10(Math.abs(t)),s=Math.floor(o)-Math.floor(a);s=Math.max(Math.min(s,20),0),r=t.toExponential(s)}else{var l=-1*Math.floor(a);l=Math.max(Math.min(l,20),0),r=t.toFixed(l)}else r="0";return r},logarithmic:function(t,e,n){var i=t/Math.pow(10,Math.floor(H.log10(t)));return 0===t?"0":1===i||2===i||5===i||0===e||e===n.length-1?t.toExponential():""}}},sn=H.isArray,ln=H.isNullOrUndef,un=H.valueOrDefault,dn=H.valueAtIndexOrDefault;function hn(t,e,n){var i,a=t.getTicks().length,r=Math.min(e,a-1),o=t.getPixelForTick(r),s=t._startPixel,l=t._endPixel;if(!(n&&(i=1===a?Math.max(o-s,l-o):0===e?(t.getPixelForTick(1)-o)/2:(o-t.getPixelForTick(r-1))/2,(o+=r<e?i:-i)<s-1e-6||o>l+1e-6)))return o}function cn(t,e,n,i){var a,r,o,s,l,u,d,h,c,f,g,m,p,v=n.length,b=[],y=[],x=[];for(a=0;a<v;++a){if(s=n[a].label,l=n[a].major?e.major:e.minor,t.font=u=l.string,d=i[u]=i[u]||{data:{},gc:[]},h=l.lineHeight,c=f=0,ln(s)||sn(s)){if(sn(s))for(r=0,o=s.length;r<o;++r)g=s[r],ln(g)||sn(g)||(c=H.measureText(t,d.data,d.gc,c,g),f+=h)}else c=H.measureText(t,d.data,d.gc,c,s),f=h;b.push(c),y.push(f),x.push(h/2)}function _(t){return{width:b[t]||0,height:y[t]||0,offset:x[t]||0}}return function(t,e){H.each(t,(function(t){var n,i=t.gc,a=i.length/2;if(a>e){for(n=0;n<a;++n)delete t.data[i[n]];i.splice(0,a)}}))}(i,v),m=b.indexOf(Math.max.apply(null,b)),p=y.indexOf(Math.max.apply(null,y)),{first:_(0),last:_(v-1),widest:_(m),highest:_(p)}}function fn(t){return t.drawTicks?t.tickMarkLength:0}function gn(t){var e,n;return t.display?(e=H.options._parseFont(t),n=H.options.toPadding(t.padding),e.lineHeight+n.height):0}function mn(t,e){return H.extend(H.options._parseFont({fontFamily:un(e.fontFamily,t.fontFamily),fontSize:un(e.fontSize,t.fontSize),fontStyle:un(e.fontStyle,t.fontStyle),lineHeight:un(e.lineHeight,t.lineHeight)}),{color:H.options.resolve([e.fontColor,t.fontColor,W.global.defaultFontColor])})}function pn(t){var e=mn(t,t.minor);return{minor:e,major:t.major.enabled?mn(t,t.major):e}}function vn(t){var e,n,i,a=[];for(n=0,i=t.length;n<i;++n)void 0!==(e=t[n])._index&&a.push(e);return a}function bn(t,e,n,i){var a,r,o,s,l=un(n,0),u=Math.min(un(i,t.length),t.length),d=0;for(e=Math.ceil(e),i&&(e=(a=i-n)/Math.floor(a/e)),s=l;s<0;)d++,s=Math.round(l+d*e);for(r=Math.max(l,0);r<u;r++)o=t[r],r===s?(o._index=r,d++,s=Math.round(l+d*e)):delete o.label}W._set("scale",{display:!0,position:"left",offset:!1,gridLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,drawBorder:!0,drawOnChartArea:!0,drawTicks:!0,tickMarkLength:10,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)",zeroLineBorderDash:[],zeroLineBorderDashOffset:0,offsetGridLines:!1,borderDash:[],borderDashOffset:0},scaleLabel:{display:!1,labelString:"",padding:{top:4,bottom:4}},ticks:{beginAtZero:!1,minRotation:0,maxRotation:50,mirror:!1,padding:0,reverse:!1,display:!0,autoSkip:!0,autoSkipPadding:0,labelOffset:0,callback:on.formatters.values,minor:{},major:{}}});var yn=$.extend({zeroLineIndex:0,getPadding:function(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}},getTicks:function(){return this._ticks},_getLabels:function(){var t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]},mergeTicksOptions:function(){},beforeUpdate:function(){H.callback(this.options.beforeUpdate,[this])},update:function(t,e,n){var i,a,r,o,s,l=this,u=l.options.ticks,d=u.sampleSize;if(l.beforeUpdate(),l.maxWidth=t,l.maxHeight=e,l.margins=H.extend({left:0,right:0,top:0,bottom:0},n),l._ticks=null,l.ticks=null,l._labelSizes=null,l._maxLabelLines=0,l.longestLabelWidth=0,l.longestTextCache=l.longestTextCache||{},l._gridLineItems=null,l._labelItems=null,l.beforeSetDimensions(),l.setDimensions(),l.afterSetDimensions(),l.beforeDataLimits(),l.determineDataLimits(),l.afterDataLimits(),l.beforeBuildTicks(),o=l.buildTicks()||[],(!(o=l.afterBuildTicks(o)||o)||!o.length)&&l.ticks)for(o=[],i=0,a=l.ticks.length;i<a;++i)o.push({value:l.ticks[i],major:!1});return l._ticks=o,s=d<o.length,r=l._convertTicksToLabels(s?function(t,e){for(var n=[],i=t.length/e,a=0,r=t.length;a<r;a+=i)n.push(t[Math.floor(a)]);return n}(o,d):o),l._configure(),l.beforeCalculateTickRotation(),l.calculateTickRotation(),l.afterCalculateTickRotation(),l.beforeFit(),l.fit(),l.afterFit(),l._ticksToDraw=u.display&&(u.autoSkip||"auto"===u.source)?l._autoSkip(o):o,s&&(r=l._convertTicksToLabels(l._ticksToDraw)),l.ticks=r,l.afterUpdate(),l.minSize},_configure:function(){var t,e,n=this,i=n.options.ticks.reverse;n.isHorizontal()?(t=n.left,e=n.right):(t=n.top,e=n.bottom,i=!i),n._startPixel=t,n._endPixel=e,n._reversePixels=i,n._length=e-t},afterUpdate:function(){H.callback(this.options.afterUpdate,[this])},beforeSetDimensions:function(){H.callback(this.options.beforeSetDimensions,[this])},setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0},afterSetDimensions:function(){H.callback(this.options.afterSetDimensions,[this])},beforeDataLimits:function(){H.callback(this.options.beforeDataLimits,[this])},determineDataLimits:H.noop,afterDataLimits:function(){H.callback(this.options.afterDataLimits,[this])},beforeBuildTicks:function(){H.callback(this.options.beforeBuildTicks,[this])},buildTicks:H.noop,afterBuildTicks:function(t){var e=this;return sn(t)&&t.length?H.callback(e.options.afterBuildTicks,[e,t]):(e.ticks=H.callback(e.options.afterBuildTicks,[e,e.ticks])||e.ticks,t)},beforeTickToLabelConversion:function(){H.callback(this.options.beforeTickToLabelConversion,[this])},convertTicksToLabels:function(){var t=this.options.ticks;this.ticks=this.ticks.map(t.userCallback||t.callback,this)},afterTickToLabelConversion:function(){H.callback(this.options.afterTickToLabelConversion,[this])},beforeCalculateTickRotation:function(){H.callback(this.options.beforeCalculateTickRotation,[this])},calculateTickRotation:function(){var t,e,n,i,a,r,o,s=this,l=s.options,u=l.ticks,d=s.getTicks().length,h=u.minRotation||0,c=u.maxRotation,f=h;!s._isVisible()||!u.display||h>=c||d<=1||!s.isHorizontal()?s.labelRotation=h:(e=(t=s._getLabelSizes()).widest.width,n=t.highest.height-t.highest.offset,i=Math.min(s.maxWidth,s.chart.width-e),e+6>(a=l.offset?s.maxWidth/d:i/(d-1))&&(a=i/(d-(l.offset?.5:1)),r=s.maxHeight-fn(l.gridLines)-u.padding-gn(l.scaleLabel),o=Math.sqrt(e*e+n*n),f=H.toDegrees(Math.min(Math.asin(Math.min((t.highest.height+6)/a,1)),Math.asin(Math.min(r/o,1))-Math.asin(n/o))),f=Math.max(h,Math.min(c,f))),s.labelRotation=f)},afterCalculateTickRotation:function(){H.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){H.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},n=t.chart,i=t.options,a=i.ticks,r=i.scaleLabel,o=i.gridLines,s=t._isVisible(),l="bottom"===i.position,u=t.isHorizontal();if(u?e.width=t.maxWidth:s&&(e.width=fn(o)+gn(r)),u?s&&(e.height=fn(o)+gn(r)):e.height=t.maxHeight,a.display&&s){var d=pn(a),h=t._getLabelSizes(),c=h.first,f=h.last,g=h.widest,m=h.highest,p=.4*d.minor.lineHeight,v=a.padding;if(u){var b=0!==t.labelRotation,y=H.toRadians(t.labelRotation),x=Math.cos(y),_=Math.sin(y),w=_*g.width+x*(m.height-(b?m.offset:0))+(b?0:p);e.height=Math.min(t.maxHeight,e.height+w+v);var k,M,S=t.getPixelForTick(0)-t.left,D=t.right-t.getPixelForTick(t.getTicks().length-1);b?(k=l?x*c.width+_*c.offset:_*(c.height-c.offset),M=l?_*(f.height-f.offset):x*f.width+_*f.offset):(k=c.width/2,M=f.width/2),t.paddingLeft=Math.max((k-S)*t.width/(t.width-S),0)+3,t.paddingRight=Math.max((M-D)*t.width/(t.width-D),0)+3}else{var C=a.mirror?0:g.width+v+p;e.width=Math.min(t.maxWidth,e.width+C),t.paddingTop=c.height/2,t.paddingBottom=f.height/2}}t.handleMargins(),u?(t.width=t._length=n.width-t.margins.left-t.margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=n.height-t.margins.top-t.margins.bottom)},handleMargins:function(){var t=this;t.margins&&(t.margins.left=Math.max(t.paddingLeft,t.margins.left),t.margins.top=Math.max(t.paddingTop,t.margins.top),t.margins.right=Math.max(t.paddingRight,t.margins.right),t.margins.bottom=Math.max(t.paddingBottom,t.margins.bottom))},afterFit:function(){H.callback(this.options.afterFit,[this])},isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(ln(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},_convertTicksToLabels:function(t){var e,n,i,a=this;for(a.ticks=t.map((function(t){return t.value})),a.beforeTickToLabelConversion(),e=a.convertTicksToLabels(t)||a.ticks,a.afterTickToLabelConversion(),n=0,i=t.length;n<i;++n)t[n].label=e[n];return e},_getLabelSizes:function(){var t=this,e=t._labelSizes;return e||(t._labelSizes=e=cn(t.ctx,pn(t.options.ticks),t.getTicks(),t.longestTextCache),t.longestLabelWidth=e.widest.width),e},_parseValue:function(t){var e,n,i,a;return sn(t)?(e=+this.getRightValue(t[0]),n=+this.getRightValue(t[1]),i=Math.min(e,n),a=Math.max(e,n)):(e=void 0,n=t=+this.getRightValue(t),i=t,a=t),{min:i,max:a,start:e,end:n}},_getScaleLabel:function(t){var e=this._parseValue(t);return void 0!==e.start?"["+e.start+", "+e.end+"]":+this.getRightValue(t)},getLabelForIndex:H.noop,getPixelForValue:H.noop,getValueForPixel:H.noop,getPixelForTick:function(t){var e=this.options.offset,n=this._ticks.length,i=1/Math.max(n-(e?0:1),1);return t<0||t>n-1?null:this.getPixelForDecimal(t*i+(e?i/2:0))},getPixelForDecimal:function(t){return this._reversePixels&&(t=1-t),this._startPixel+t*this._length},getDecimalForPixel:function(t){var e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this.min,e=this.max;return this.beginAtZero?0:t<0&&e<0?e:t>0&&e>0?t:0},_autoSkip:function(t){var e,n,i,a,r=this.options.ticks,o=this._length,s=r.maxTicksLimit||o/this._tickSize()+1,l=r.major.enabled?function(t){var e,n,i=[];for(e=0,n=t.length;e<n;e++)t[e].major&&i.push(e);return i}(t):[],u=l.length,d=l[0],h=l[u-1];if(u>s)return function(t,e,n){var i,a,r=0,o=e[0];for(n=Math.ceil(n),i=0;i<t.length;i++)a=t[i],i===o?(a._index=i,o=e[++r*n]):delete a.label}(t,l,u/s),vn(t);if(i=function(t,e,n,i){var a,r,o,s,l=function(t){var e,n,i=t.length;if(i<2)return!1;for(n=t[0],e=1;e<i;++e)if(t[e]-t[e-1]!==n)return!1;return n}(t),u=(e.length-1)/i;if(!l)return Math.max(u,1);for(o=0,s=(a=H.math._factorize(l)).length-1;o<s;o++)if((r=a[o])>u)return r;return Math.max(u,1)}(l,t,0,s),u>0){for(e=0,n=u-1;e<n;e++)bn(t,i,l[e],l[e+1]);return a=u>1?(h-d)/(u-1):null,bn(t,i,H.isNullOrUndef(a)?0:d-a,d),bn(t,i,h,H.isNullOrUndef(a)?t.length:h+a),vn(t)}return bn(t,i),vn(t)},_tickSize:function(){var t=this.options.ticks,e=H.toRadians(this.labelRotation),n=Math.abs(Math.cos(e)),i=Math.abs(Math.sin(e)),a=this._getLabelSizes(),r=t.autoSkipPadding||0,o=a?a.widest.width+r:0,s=a?a.highest.height+r:0;return this.isHorizontal()?s*n>o*i?o/n:s/i:s*i<o*n?s/n:o/i},_isVisible:function(){var t,e,n,i=this.chart,a=this.options.display;if("auto"!==a)return!!a;for(t=0,e=i.data.datasets.length;t<e;++t)if(i.isDatasetVisible(t)&&((n=i.getDatasetMeta(t)).xAxisID===this.id||n.yAxisID===this.id))return!0;return!1},_computeGridLineItems:function(t){var e,n,i,a,r,o,s,l,u,d,h,c,f,g,m,p,v,b=this,y=b.chart,x=b.options,_=x.gridLines,w=x.position,k=_.offsetGridLines,M=b.isHorizontal(),S=b._ticksToDraw,D=S.length+(k?1:0),C=fn(_),P=[],T=_.drawBorder?dn(_.lineWidth,0,0):0,O=T/2,A=H._alignPixel,F=function(t){return A(y,t,T)};for("top"===w?(e=F(b.bottom),s=b.bottom-C,u=e-O,h=F(t.top)+O,f=t.bottom):"bottom"===w?(e=F(b.top),h=t.top,f=F(t.bottom)-O,s=e+O,u=b.top+C):"left"===w?(e=F(b.right),o=b.right-C,l=e-O,d=F(t.left)+O,c=t.right):(e=F(b.left),d=t.left,c=F(t.right)-O,o=e+O,l=b.left+C),n=0;n<D;++n)i=S[n]||{},ln(i.label)&&n<S.length||(n===b.zeroLineIndex&&x.offset===k?(g=_.zeroLineWidth,m=_.zeroLineColor,p=_.zeroLineBorderDash||[],v=_.zeroLineBorderDashOffset||0):(g=dn(_.lineWidth,n,1),m=dn(_.color,n,"rgba(0,0,0,0.1)"),p=_.borderDash||[],v=_.borderDashOffset||0),void 0!==(a=hn(b,i._index||n,k))&&(r=A(y,a,g),M?o=l=d=c=r:s=u=h=f=r,P.push({tx1:o,ty1:s,tx2:l,ty2:u,x1:d,y1:h,x2:c,y2:f,width:g,color:m,borderDash:p,borderDashOffset:v})));return P.ticksLength=D,P.borderValue=e,P},_computeLabelItems:function(){var t,e,n,i,a,r,o,s,l,u,d,h,c=this,f=c.options,g=f.ticks,m=f.position,p=g.mirror,v=c.isHorizontal(),b=c._ticksToDraw,y=pn(g),x=g.padding,_=fn(f.gridLines),w=-H.toRadians(c.labelRotation),k=[];for("top"===m?(r=c.bottom-_-x,o=w?"left":"center"):"bottom"===m?(r=c.top+_+x,o=w?"right":"center"):"left"===m?(a=c.right-(p?0:_)-x,o=p?"left":"right"):(a=c.left+(p?0:_)+x,o=p?"right":"left"),t=0,e=b.length;t<e;++t)i=(n=b[t]).label,ln(i)||(s=c.getPixelForTick(n._index||t)+g.labelOffset,u=(l=n.major?y.major:y.minor).lineHeight,d=sn(i)?i.length:1,v?(a=s,h="top"===m?((w?1:.5)-d)*u:(w?0:.5)*u):(r=s,h=(1-d)*u/2),k.push({x:a,y:r,rotation:w,label:i,font:l,textOffset:h,textAlign:o}));return k},_drawGrid:function(t){var e=this,n=e.options.gridLines;if(n.display){var i,a,r,o,s,l=e.ctx,u=e.chart,d=H._alignPixel,h=n.drawBorder?dn(n.lineWidth,0,0):0,c=e._gridLineItems||(e._gridLineItems=e._computeGridLineItems(t));for(r=0,o=c.length;r<o;++r)i=(s=c[r]).width,a=s.color,i&&a&&(l.save(),l.lineWidth=i,l.strokeStyle=a,l.setLineDash&&(l.setLineDash(s.borderDash),l.lineDashOffset=s.borderDashOffset),l.beginPath(),n.drawTicks&&(l.moveTo(s.tx1,s.ty1),l.lineTo(s.tx2,s.ty2)),n.drawOnChartArea&&(l.moveTo(s.x1,s.y1),l.lineTo(s.x2,s.y2)),l.stroke(),l.restore());if(h){var f,g,m,p,v=h,b=dn(n.lineWidth,c.ticksLength-1,1),y=c.borderValue;e.isHorizontal()?(f=d(u,e.left,v)-v/2,g=d(u,e.right,b)+b/2,m=p=y):(m=d(u,e.top,v)-v/2,p=d(u,e.bottom,b)+b/2,f=g=y),l.lineWidth=h,l.strokeStyle=dn(n.color,0),l.beginPath(),l.moveTo(f,m),l.lineTo(g,p),l.stroke()}}},_drawLabels:function(){var t=this;if(t.options.ticks.display){var e,n,i,a,r,o,s,l,u=t.ctx,d=t._labelItems||(t._labelItems=t._computeLabelItems());for(e=0,i=d.length;e<i;++e){if(o=(r=d[e]).font,u.save(),u.translate(r.x,r.y),u.rotate(r.rotation),u.font=o.string,u.fillStyle=o.color,u.textBaseline="middle",u.textAlign=r.textAlign,s=r.label,l=r.textOffset,sn(s))for(n=0,a=s.length;n<a;++n)u.fillText(""+s[n],0,l),l+=o.lineHeight;else u.fillText(s,0,l);u.restore()}}},_drawTitle:function(){var t=this,e=t.ctx,n=t.options,i=n.scaleLabel;if(i.display){var a,r,o=un(i.fontColor,W.global.defaultFontColor),s=H.options._parseFont(i),l=H.options.toPadding(i.padding),u=s.lineHeight/2,d=n.position,h=0;if(t.isHorizontal())a=t.left+t.width/2,r="bottom"===d?t.bottom-u-l.bottom:t.top+u+l.top;else{var c="left"===d;a=c?t.left+u+l.top:t.right-u-l.top,r=t.top+t.height/2,h=c?-.5*Math.PI:.5*Math.PI}e.save(),e.translate(a,r),e.rotate(h),e.textAlign="center",e.textBaseline="middle",e.fillStyle=o,e.font=s.string,e.fillText(i.labelString,0,0),e.restore()}},draw:function(t){this._isVisible()&&(this._drawGrid(t),this._drawTitle(),this._drawLabels())},_layers:function(){var t=this,e=t.options,n=e.ticks&&e.ticks.z||0,i=e.gridLines&&e.gridLines.z||0;return t._isVisible()&&n!==i&&t.draw===t._draw?[{z:i,draw:function(){t._drawGrid.apply(t,arguments),t._drawTitle.apply(t,arguments)}},{z:n,draw:function(){t._drawLabels.apply(t,arguments)}}]:[{z:n,draw:function(){t.draw.apply(t,arguments)}}]},_getMatchingVisibleMetas:function(t){var e=this,n=e.isHorizontal();return e.chart._getSortedVisibleDatasetMetas().filter((function(i){return(!t||i.type===t)&&(n?i.xAxisID===e.id:i.yAxisID===e.id)}))}});yn.prototype._draw=yn.prototype.draw;var xn=yn,_n=H.isNullOrUndef,wn=xn.extend({determineDataLimits:function(){var t,e=this,n=e._getLabels(),i=e.options.ticks,a=i.min,r=i.max,o=0,s=n.length-1;void 0!==a&&(t=n.indexOf(a))>=0&&(o=t),void 0!==r&&(t=n.indexOf(r))>=0&&(s=t),e.minIndex=o,e.maxIndex=s,e.min=n[o],e.max=n[s]},buildTicks:function(){var t=this._getLabels(),e=this.minIndex,n=this.maxIndex;this.ticks=0===e&&n===t.length-1?t:t.slice(e,n+1)},getLabelForIndex:function(t,e){var n=this.chart;return n.getDatasetMeta(e).controller._getValueScaleId()===this.id?this.getRightValue(n.data.datasets[e].data[t]):this._getLabels()[t]},_configure:function(){var t=this,e=t.options.offset,n=t.ticks;xn.prototype._configure.call(t),t.isHorizontal()||(t._reversePixels=!t._reversePixels),n&&(t._startValue=t.minIndex-(e?.5:0),t._valueRange=Math.max(n.length-(e?0:1),1))},getPixelForValue:function(t,e,n){var i,a,r,o=this;return _n(e)||_n(n)||(t=o.chart.data.datasets[n].data[e]),_n(t)||(i=o.isHorizontal()?t.x:t.y),(void 0!==i||void 0!==t&&isNaN(e))&&(a=o._getLabels(),t=H.valueOrDefault(i,t),e=-1!==(r=a.indexOf(t))?r:e,isNaN(e)&&(e=t)),o.getPixelForDecimal((e-o._startValue)/o._valueRange)},getPixelForTick:function(t){var e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t],t+this.minIndex)},getValueForPixel:function(t){var e=Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange);return Math.min(Math.max(e,0),this.ticks.length-1)},getBasePixel:function(){return this.bottom}}),kn={position:"bottom"};wn._defaults=kn;var Mn=H.noop,Sn=H.isNullOrUndef;var Dn=xn.extend({getRightValue:function(t){return"string"==typeof t?+t:xn.prototype.getRightValue.call(this,t)},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;if(e.beginAtZero){var n=H.sign(t.min),i=H.sign(t.max);n<0&&i<0?t.max=0:n>0&&i>0&&(t.min=0)}var a=void 0!==e.min||void 0!==e.suggestedMin,r=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),a!==r&&t.min>=t.max&&(a?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this.options.ticks,n=e.stepSize,i=e.maxTicksLimit;return n?t=Math.ceil(this.max/n)-Math.floor(this.min/n)+1:(t=this._computeTickLimit(),i=i||11),i&&(t=Math.min(i,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:Mn,buildTicks:function(){var t=this,e=t.options.ticks,n=t.getTickLimit(),i={maxTicks:n=Math.max(2,n),min:e.min,max:e.max,precision:e.precision,stepSize:H.valueOrDefault(e.fixedStepSize,e.stepSize)},a=t.ticks=function(t,e){var n,i,a,r,o=[],s=t.stepSize,l=s||1,u=t.maxTicks-1,d=t.min,h=t.max,c=t.precision,f=e.min,g=e.max,m=H.niceNum((g-f)/u/l)*l;if(m<1e-14&&Sn(d)&&Sn(h))return[f,g];(r=Math.ceil(g/m)-Math.floor(f/m))>u&&(m=H.niceNum(r*m/u/l)*l),s||Sn(c)?n=Math.pow(10,H._decimalPlaces(m)):(n=Math.pow(10,c),m=Math.ceil(m*n)/n),i=Math.floor(f/m)*m,a=Math.ceil(g/m)*m,s&&(!Sn(d)&&H.almostWhole(d/m,m/1e3)&&(i=d),!Sn(h)&&H.almostWhole(h/m,m/1e3)&&(a=h)),r=(a-i)/m,r=H.almostEquals(r,Math.round(r),m/1e3)?Math.round(r):Math.ceil(r),i=Math.round(i*n)/n,a=Math.round(a*n)/n,o.push(Sn(d)?i:d);for(var p=1;p<r;++p)o.push(Math.round((i+p*m)*n)/n);return o.push(Sn(h)?a:h),o}(i,t);t.handleDirectionalChanges(),t.max=H.max(a),t.min=H.min(a),e.reverse?(a.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max)},convertTicksToLabels:function(){var t=this;t.ticksAsNumbers=t.ticks.slice(),t.zeroLineIndex=t.ticks.indexOf(0),xn.prototype.convertTicksToLabels.call(t)},_configure:function(){var t,e=this,n=e.getTicks(),i=e.min,a=e.max;xn.prototype._configure.call(e),e.options.offset&&n.length&&(i-=t=(a-i)/Math.max(n.length-1,1)/2,a+=t),e._startValue=i,e._endValue=a,e._valueRange=a-i}}),Cn={position:"left",ticks:{callback:on.formatters.linear}};function Pn(t,e,n,i){var a,r,o=t.options,s=function(t,e,n){var i=[n.type,void 0===e&&void 0===n.stack?n.index:"",n.stack].join(".");return void 0===t[i]&&(t[i]={pos:[],neg:[]}),t[i]}(e,o.stacked,n),l=s.pos,u=s.neg,d=i.length;for(a=0;a<d;++a)r=t._parseValue(i[a]),isNaN(r.min)||isNaN(r.max)||n.data[a].hidden||(l[a]=l[a]||0,u[a]=u[a]||0,o.relativePoints?l[a]=100:r.min<0||r.max<0?u[a]+=r.min:l[a]+=r.max)}function Tn(t,e,n){var i,a,r=n.length;for(i=0;i<r;++i)a=t._parseValue(n[i]),isNaN(a.min)||isNaN(a.max)||e.data[i].hidden||(t.min=Math.min(t.min,a.min),t.max=Math.max(t.max,a.max))}var On=Dn.extend({determineDataLimits:function(){var t,e,n,i,a=this,r=a.options,o=a.chart.data.datasets,s=a._getMatchingVisibleMetas(),l=r.stacked,u={},d=s.length;if(a.min=Number.POSITIVE_INFINITY,a.max=Number.NEGATIVE_INFINITY,void 0===l)for(t=0;!l&&t<d;++t)l=void 0!==(e=s[t]).stack;for(t=0;t<d;++t)n=o[(e=s[t]).index].data,l?Pn(a,u,e,n):Tn(a,e,n);H.each(u,(function(t){i=t.pos.concat(t.neg),a.min=Math.min(a.min,H.min(i)),a.max=Math.max(a.max,H.max(i))})),a.min=H.isFinite(a.min)&&!isNaN(a.min)?a.min:0,a.max=H.isFinite(a.max)&&!isNaN(a.max)?a.max:1,a.handleTickRangeOptions()},_computeTickLimit:function(){var t;return this.isHorizontal()?Math.ceil(this.width/40):(t=H.options._parseFont(this.options.ticks),Math.ceil(this.height/t.lineHeight))},handleDirectionalChanges:function(){this.isHorizontal()||this.ticks.reverse()},getLabelForIndex:function(t,e){return this._getScaleLabel(this.chart.data.datasets[e].data[t])},getPixelForValue:function(t){return this.getPixelForDecimal((+this.getRightValue(t)-this._startValue)/this._valueRange)},getValueForPixel:function(t){return this._startValue+this.getDecimalForPixel(t)*this._valueRange},getPixelForTick:function(t){var e=this.ticksAsNumbers;return t<0||t>e.length-1?null:this.getPixelForValue(e[t])}}),An=Cn;On._defaults=An;var Fn=H.valueOrDefault,In=H.math.log10;var Ln={position:"left",ticks:{callback:on.formatters.logarithmic}};function Rn(t,e){return H.isFinite(t)&&t>=0?t:e}var Nn=xn.extend({determineDataLimits:function(){var t,e,n,i,a,r,o=this,s=o.options,l=o.chart,u=l.data.datasets,d=o.isHorizontal();function h(t){return d?t.xAxisID===o.id:t.yAxisID===o.id}o.min=Number.POSITIVE_INFINITY,o.max=Number.NEGATIVE_INFINITY,o.minNotZero=Number.POSITIVE_INFINITY;var c=s.stacked;if(void 0===c)for(t=0;t<u.length;t++)if(e=l.getDatasetMeta(t),l.isDatasetVisible(t)&&h(e)&&void 0!==e.stack){c=!0;break}if(s.stacked||c){var f={};for(t=0;t<u.length;t++){var g=[(e=l.getDatasetMeta(t)).type,void 0===s.stacked&&void 0===e.stack?t:"",e.stack].join(".");if(l.isDatasetVisible(t)&&h(e))for(void 0===f[g]&&(f[g]=[]),a=0,r=(i=u[t].data).length;a<r;a++){var m=f[g];n=o._parseValue(i[a]),isNaN(n.min)||isNaN(n.max)||e.data[a].hidden||n.min<0||n.max<0||(m[a]=m[a]||0,m[a]+=n.max)}}H.each(f,(function(t){if(t.length>0){var e=H.min(t),n=H.max(t);o.min=Math.min(o.min,e),o.max=Math.max(o.max,n)}}))}else for(t=0;t<u.length;t++)if(e=l.getDatasetMeta(t),l.isDatasetVisible(t)&&h(e))for(a=0,r=(i=u[t].data).length;a<r;a++)n=o._parseValue(i[a]),isNaN(n.min)||isNaN(n.max)||e.data[a].hidden||n.min<0||n.max<0||(o.min=Math.min(n.min,o.min),o.max=Math.max(n.max,o.max),0!==n.min&&(o.minNotZero=Math.min(n.min,o.minNotZero)));o.min=H.isFinite(o.min)?o.min:null,o.max=H.isFinite(o.max)?o.max:null,o.minNotZero=H.isFinite(o.minNotZero)?o.minNotZero:null,this.handleTickRangeOptions()},handleTickRangeOptions:function(){var t=this,e=t.options.ticks;t.min=Rn(e.min,t.min),t.max=Rn(e.max,t.max),t.min===t.max&&(0!==t.min&&null!==t.min?(t.min=Math.pow(10,Math.floor(In(t.min))-1),t.max=Math.pow(10,Math.floor(In(t.max))+1)):(t.min=1,t.max=10)),null===t.min&&(t.min=Math.pow(10,Math.floor(In(t.max))-1)),null===t.max&&(t.max=0!==t.min?Math.pow(10,Math.floor(In(t.min))+1):10),null===t.minNotZero&&(t.min>0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(In(t.max))):t.minNotZero=1)},buildTicks:function(){var t=this,e=t.options.ticks,n=!t.isHorizontal(),i={min:Rn(e.min),max:Rn(e.max)},a=t.ticks=function(t,e){var n,i,a=[],r=Fn(t.min,Math.pow(10,Math.floor(In(e.min)))),o=Math.floor(In(e.max)),s=Math.ceil(e.max/Math.pow(10,o));0===r?(n=Math.floor(In(e.minNotZero)),i=Math.floor(e.minNotZero/Math.pow(10,n)),a.push(r),r=i*Math.pow(10,n)):(n=Math.floor(In(r)),i=Math.floor(r/Math.pow(10,n)));var l=n<0?Math.pow(10,Math.abs(n)):1;do{a.push(r),10===++i&&(i=1,l=++n>=0?1:l),r=Math.round(i*Math.pow(10,n)*l)/l}while(n<o||n===o&&i<s);var u=Fn(t.max,r);return a.push(u),a}(i,t);t.max=H.max(a),t.min=H.min(a),e.reverse?(n=!n,t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max),n&&a.reverse()},convertTicksToLabels:function(){this.tickValues=this.ticks.slice(),xn.prototype.convertTicksToLabels.call(this)},getLabelForIndex:function(t,e){return this._getScaleLabel(this.chart.data.datasets[e].data[t])},getPixelForTick:function(t){var e=this.tickValues;return t<0||t>e.length-1?null:this.getPixelForValue(e[t])},_getFirstTickValue:function(t){var e=Math.floor(In(t));return Math.floor(t/Math.pow(10,e))*Math.pow(10,e)},_configure:function(){var t=this,e=t.min,n=0;xn.prototype._configure.call(t),0===e&&(e=t._getFirstTickValue(t.minNotZero),n=Fn(t.options.ticks.fontSize,W.global.defaultFontSize)/t._length),t._startValue=In(e),t._valueOffset=n,t._valueRange=(In(t.max)-In(e))/(1-n)},getPixelForValue:function(t){var e=this,n=0;return(t=+e.getRightValue(t))>e.min&&t>0&&(n=(In(t)-e._startValue)/e._valueRange+e._valueOffset),e.getPixelForDecimal(n)},getValueForPixel:function(t){var e=this,n=e.getDecimalForPixel(t);return 0===n&&0===e.min?0:Math.pow(10,e._startValue+(n-e._valueOffset)*e._valueRange)}}),Wn=Ln;Nn._defaults=Wn;var Yn=H.valueOrDefault,zn=H.valueAtIndexOrDefault,En=H.options.resolve,Vn={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:on.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(t){return t}}};function Hn(t){var e=t.ticks;return e.display&&t.display?Yn(e.fontSize,W.global.defaultFontSize)+2*e.backdropPaddingY:0}function Bn(t,e,n,i,a){return t===i||t===a?{start:e-n/2,end:e+n/2}:t<i||t>a?{start:e-n,end:e}:{start:e,end:e+n}}function jn(t){return 0===t||180===t?"center":t<180?"left":"right"}function Un(t,e,n,i){var a,r,o=n.y+i/2;if(H.isArray(e))for(a=0,r=e.length;a<r;++a)t.fillText(e[a],n.x,o),o+=i;else t.fillText(e,n.x,o)}function Gn(t,e,n){90===t||270===t?n.y-=e.h/2:(t>270||t<90)&&(n.y-=e.h)}function qn(t){return H.isNumber(t)?t:0}var Zn=Dn.extend({setDimensions:function(){var t=this;t.width=t.maxWidth,t.height=t.maxHeight,t.paddingTop=Hn(t.options)/2,t.xCenter=Math.floor(t.width/2),t.yCenter=Math.floor((t.height-t.paddingTop)/2),t.drawingArea=Math.min(t.height-t.paddingTop,t.width)/2},determineDataLimits:function(){var t=this,e=t.chart,n=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;H.each(e.data.datasets,(function(a,r){if(e.isDatasetVisible(r)){var o=e.getDatasetMeta(r);H.each(a.data,(function(e,a){var r=+t.getRightValue(e);isNaN(r)||o.data[a].hidden||(n=Math.min(r,n),i=Math.max(r,i))}))}})),t.min=n===Number.POSITIVE_INFINITY?0:n,t.max=i===Number.NEGATIVE_INFINITY?0:i,t.handleTickRangeOptions()},_computeTickLimit:function(){return Math.ceil(this.drawingArea/Hn(this.options))},convertTicksToLabels:function(){var t=this;Dn.prototype.convertTicksToLabels.call(t),t.pointLabels=t.chart.data.labels.map((function(){var e=H.callback(t.options.pointLabels.callback,arguments,t);return e||0===e?e:""}))},getLabelForIndex:function(t,e){return+this.getRightValue(this.chart.data.datasets[e].data[t])},fit:function(){var t=this.options;t.display&&t.pointLabels.display?function(t){var e,n,i,a=H.options._parseFont(t.options.pointLabels),r={l:0,r:t.width,t:0,b:t.height-t.paddingTop},o={};t.ctx.font=a.string,t._pointLabelSizes=[];var s,l,u,d=t.chart.data.labels.length;for(e=0;e<d;e++){i=t.getPointPosition(e,t.drawingArea+5),s=t.ctx,l=a.lineHeight,u=t.pointLabels[e],n=H.isArray(u)?{w:H.longestText(s,s.font,u),h:u.length*l}:{w:s.measureText(u).width,h:l},t._pointLabelSizes[e]=n;var h=t.getIndexAngle(e),c=H.toDegrees(h)%360,f=Bn(c,i.x,n.w,0,180),g=Bn(c,i.y,n.h,90,270);f.start<r.l&&(r.l=f.start,o.l=h),f.end>r.r&&(r.r=f.end,o.r=h),g.start<r.t&&(r.t=g.start,o.t=h),g.end>r.b&&(r.b=g.end,o.b=h)}t.setReductions(t.drawingArea,r,o)}(this):this.setCenterPoint(0,0,0,0)},setReductions:function(t,e,n){var i=this,a=e.l/Math.sin(n.l),r=Math.max(e.r-i.width,0)/Math.sin(n.r),o=-e.t/Math.cos(n.t),s=-Math.max(e.b-(i.height-i.paddingTop),0)/Math.cos(n.b);a=qn(a),r=qn(r),o=qn(o),s=qn(s),i.drawingArea=Math.min(Math.floor(t-(a+r)/2),Math.floor(t-(o+s)/2)),i.setCenterPoint(a,r,o,s)},setCenterPoint:function(t,e,n,i){var a=this,r=a.width-e-a.drawingArea,o=t+a.drawingArea,s=n+a.drawingArea,l=a.height-a.paddingTop-i-a.drawingArea;a.xCenter=Math.floor((o+r)/2+a.left),a.yCenter=Math.floor((s+l)/2+a.top+a.paddingTop)},getIndexAngle:function(t){var e=this.chart,n=(t*(360/e.data.labels.length)+((e.options||{}).startAngle||0))%360;return(n<0?n+360:n)*Math.PI*2/360},getDistanceFromCenterForValue:function(t){var e=this;if(H.isNullOrUndef(t))return NaN;var n=e.drawingArea/(e.max-e.min);return e.options.ticks.reverse?(e.max-t)*n:(t-e.min)*n},getPointPosition:function(t,e){var n=this.getIndexAngle(t)-Math.PI/2;return{x:Math.cos(n)*e+this.xCenter,y:Math.sin(n)*e+this.yCenter}},getPointPositionForValue:function(t,e){return this.getPointPosition(t,this.getDistanceFromCenterForValue(e))},getBasePosition:function(t){var e=this.min,n=this.max;return this.getPointPositionForValue(t||0,this.beginAtZero?0:e<0&&n<0?n:e>0&&n>0?e:0)},_drawGrid:function(){var t,e,n,i=this,a=i.ctx,r=i.options,o=r.gridLines,s=r.angleLines,l=Yn(s.lineWidth,o.lineWidth),u=Yn(s.color,o.color);if(r.pointLabels.display&&function(t){var e=t.ctx,n=t.options,i=n.pointLabels,a=Hn(n),r=t.getDistanceFromCenterForValue(n.ticks.reverse?t.min:t.max),o=H.options._parseFont(i);e.save(),e.font=o.string,e.textBaseline="middle";for(var s=t.chart.data.labels.length-1;s>=0;s--){var l=0===s?a/2:0,u=t.getPointPosition(s,r+l+5),d=zn(i.fontColor,s,W.global.defaultFontColor);e.fillStyle=d;var h=t.getIndexAngle(s),c=H.toDegrees(h);e.textAlign=jn(c),Gn(c,t._pointLabelSizes[s],u),Un(e,t.pointLabels[s],u,o.lineHeight)}e.restore()}(i),o.display&&H.each(i.ticks,(function(t,n){0!==n&&(e=i.getDistanceFromCenterForValue(i.ticksAsNumbers[n]),function(t,e,n,i){var a,r=t.ctx,o=e.circular,s=t.chart.data.labels.length,l=zn(e.color,i-1),u=zn(e.lineWidth,i-1);if((o||s)&&l&&u){if(r.save(),r.strokeStyle=l,r.lineWidth=u,r.setLineDash&&(r.setLineDash(e.borderDash||[]),r.lineDashOffset=e.borderDashOffset||0),r.beginPath(),o)r.arc(t.xCenter,t.yCenter,n,0,2*Math.PI);else{a=t.getPointPosition(0,n),r.moveTo(a.x,a.y);for(var d=1;d<s;d++)a=t.getPointPosition(d,n),r.lineTo(a.x,a.y)}r.closePath(),r.stroke(),r.restore()}}(i,o,e,n))})),s.display&&l&&u){for(a.save(),a.lineWidth=l,a.strokeStyle=u,a.setLineDash&&(a.setLineDash(En([s.borderDash,o.borderDash,[]])),a.lineDashOffset=En([s.borderDashOffset,o.borderDashOffset,0])),t=i.chart.data.labels.length-1;t>=0;t--)e=i.getDistanceFromCenterForValue(r.ticks.reverse?i.min:i.max),n=i.getPointPosition(t,e),a.beginPath(),a.moveTo(i.xCenter,i.yCenter),a.lineTo(n.x,n.y),a.stroke();a.restore()}},_drawLabels:function(){var t=this,e=t.ctx,n=t.options.ticks;if(n.display){var i,a,r=t.getIndexAngle(0),o=H.options._parseFont(n),s=Yn(n.fontColor,W.global.defaultFontColor);e.save(),e.font=o.string,e.translate(t.xCenter,t.yCenter),e.rotate(r),e.textAlign="center",e.textBaseline="middle",H.each(t.ticks,(function(r,l){(0!==l||n.reverse)&&(i=t.getDistanceFromCenterForValue(t.ticksAsNumbers[l]),n.showLabelBackdrop&&(a=e.measureText(r).width,e.fillStyle=n.backdropColor,e.fillRect(-a/2-n.backdropPaddingX,-i-o.size/2-n.backdropPaddingY,a+2*n.backdropPaddingX,o.size+2*n.backdropPaddingY)),e.fillStyle=s,e.fillText(r,0,-i))})),e.restore()}},_drawTitle:H.noop}),$n=Vn;Zn._defaults=$n;var Xn=H._deprecated,Kn=H.options.resolve,Jn=H.valueOrDefault,Qn=Number.MIN_SAFE_INTEGER||-9007199254740991,ti=Number.MAX_SAFE_INTEGER||9007199254740991,ei={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ni=Object.keys(ei);function ii(t,e){return t-e}function ai(t){return H.valueOrDefault(t.time.min,t.ticks.min)}function ri(t){return H.valueOrDefault(t.time.max,t.ticks.max)}function oi(t,e,n,i){var a=function(t,e,n){for(var i,a,r,o=0,s=t.length-1;o>=0&&o<=s;){if(a=t[(i=o+s>>1)-1]||null,r=t[i],!a)return{lo:null,hi:r};if(r[e]<n)o=i+1;else{if(!(a[e]>n))return{lo:a,hi:r};s=i-1}}return{lo:r,hi:null}}(t,e,n),r=a.lo?a.hi?a.lo:t[t.length-2]:t[0],o=a.lo?a.hi?a.hi:t[t.length-1]:t[1],s=o[e]-r[e],l=s?(n-r[e])/s:0,u=(o[i]-r[i])*l;return r[i]+u}function si(t,e){var n=t._adapter,i=t.options.time,a=i.parser,r=a||i.format,o=e;return"function"==typeof a&&(o=a(o)),H.isFinite(o)||(o="string"==typeof r?n.parse(o,r):n.parse(o)),null!==o?+o:(a||"function"!=typeof r||(o=r(e),H.isFinite(o)||(o=n.parse(o))),o)}function li(t,e){if(H.isNullOrUndef(e))return null;var n=t.options.time,i=si(t,t.getRightValue(e));return null===i?i:(n.round&&(i=+t._adapter.startOf(i,n.round)),i)}function ui(t,e,n,i){var a,r,o,s=ni.length;for(a=ni.indexOf(t);a<s-1;++a)if(o=(r=ei[ni[a]]).steps?r.steps:ti,r.common&&Math.ceil((n-e)/(o*r.size))<=i)return ni[a];return ni[s-1]}function di(t,e,n){var i,a,r=[],o={},s=e.length;for(i=0;i<s;++i)o[a=e[i]]=i,r.push({value:a,major:!1});return 0!==s&&n?function(t,e,n,i){var a,r,o=t._adapter,s=+o.startOf(e[0].value,i),l=e[e.length-1].value;for(a=s;a<=l;a=+o.add(a,1,i))(r=n[a])>=0&&(e[r].major=!0);return e}(t,r,o,n):r}var hi=xn.extend({initialize:function(){this.mergeTicksOptions(),xn.prototype.initialize.call(this)},update:function(){var t=this,e=t.options,n=e.time||(e.time={}),i=t._adapter=new rn._date(e.adapters.date);return Xn("time scale",n.format,"time.format","time.parser"),Xn("time scale",n.min,"time.min","ticks.min"),Xn("time scale",n.max,"time.max","ticks.max"),H.mergeIf(n.displayFormats,i.formats()),xn.prototype.update.apply(t,arguments)},getRightValue:function(t){return t&&void 0!==t.t&&(t=t.t),xn.prototype.getRightValue.call(this,t)},determineDataLimits:function(){var t,e,n,i,a,r,o,s=this,l=s.chart,u=s._adapter,d=s.options,h=d.time.unit||"day",c=ti,f=Qn,g=[],m=[],p=[],v=s._getLabels();for(t=0,n=v.length;t<n;++t)p.push(li(s,v[t]));for(t=0,n=(l.data.datasets||[]).length;t<n;++t)if(l.isDatasetVisible(t))if(a=l.data.datasets[t].data,H.isObject(a[0]))for(m[t]=[],e=0,i=a.length;e<i;++e)r=li(s,a[e]),g.push(r),m[t][e]=r;else m[t]=p.slice(0),o||(g=g.concat(p),o=!0);else m[t]=[];p.length&&(c=Math.min(c,p[0]),f=Math.max(f,p[p.length-1])),g.length&&(g=n>1?function(t){var e,n,i,a={},r=[];for(e=0,n=t.length;e<n;++e)a[i=t[e]]||(a[i]=!0,r.push(i));return r}(g).sort(ii):g.sort(ii),c=Math.min(c,g[0]),f=Math.max(f,g[g.length-1])),c=li(s,ai(d))||c,f=li(s,ri(d))||f,c=c===ti?+u.startOf(Date.now(),h):c,f=f===Qn?+u.endOf(Date.now(),h)+1:f,s.min=Math.min(c,f),s.max=Math.max(c+1,f),s._table=[],s._timestamps={data:g,datasets:m,labels:p}},buildTicks:function(){var t,e,n,i=this,a=i.min,r=i.max,o=i.options,s=o.ticks,l=o.time,u=i._timestamps,d=[],h=i.getLabelCapacity(a),c=s.source,f=o.distribution;for(u="data"===c||"auto"===c&&"series"===f?u.data:"labels"===c?u.labels:function(t,e,n,i){var a,r=t._adapter,o=t.options,s=o.time,l=s.unit||ui(s.minUnit,e,n,i),u=Kn([s.stepSize,s.unitStepSize,1]),d="week"===l&&s.isoWeekday,h=e,c=[];if(d&&(h=+r.startOf(h,"isoWeek",d)),h=+r.startOf(h,d?"day":l),r.diff(n,e,l)>1e5*u)throw e+" and "+n+" are too far apart with stepSize of "+u+" "+l;for(a=h;a<n;a=+r.add(a,u,l))c.push(a);return a!==n&&"ticks"!==o.bounds||c.push(a),c}(i,a,r,h),"ticks"===o.bounds&&u.length&&(a=u[0],r=u[u.length-1]),a=li(i,ai(o))||a,r=li(i,ri(o))||r,t=0,e=u.length;t<e;++t)(n=u[t])>=a&&n<=r&&d.push(n);return i.min=a,i.max=r,i._unit=l.unit||(s.autoSkip?ui(l.minUnit,i.min,i.max,h):function(t,e,n,i,a){var r,o;for(r=ni.length-1;r>=ni.indexOf(n);r--)if(o=ni[r],ei[o].common&&t._adapter.diff(a,i,o)>=e-1)return o;return ni[n?ni.indexOf(n):0]}(i,d.length,l.minUnit,i.min,i.max)),i._majorUnit=s.major.enabled&&"year"!==i._unit?function(t){for(var e=ni.indexOf(t)+1,n=ni.length;e<n;++e)if(ei[ni[e]].common)return ni[e]}(i._unit):void 0,i._table=function(t,e,n,i){if("linear"===i||!t.length)return[{time:e,pos:0},{time:n,pos:1}];var a,r,o,s,l,u=[],d=[e];for(a=0,r=t.length;a<r;++a)(s=t[a])>e&&s<n&&d.push(s);for(d.push(n),a=0,r=d.length;a<r;++a)l=d[a+1],o=d[a-1],s=d[a],void 0!==o&&void 0!==l&&Math.round((l+o)/2)===s||u.push({time:s,pos:a/(r-1)});return u}(i._timestamps.data,a,r,f),i._offsets=function(t,e,n,i,a){var r,o,s=0,l=0;return a.offset&&e.length&&(r=oi(t,"time",e[0],"pos"),s=1===e.length?1-r:(oi(t,"time",e[1],"pos")-r)/2,o=oi(t,"time",e[e.length-1],"pos"),l=1===e.length?o:(o-oi(t,"time",e[e.length-2],"pos"))/2),{start:s,end:l,factor:1/(s+1+l)}}(i._table,d,0,0,o),s.reverse&&d.reverse(),di(i,d,i._majorUnit)},getLabelForIndex:function(t,e){var n=this,i=n._adapter,a=n.chart.data,r=n.options.time,o=a.labels&&t<a.labels.length?a.labels[t]:"",s=a.datasets[e].data[t];return H.isObject(s)&&(o=n.getRightValue(s)),r.tooltipFormat?i.format(si(n,o),r.tooltipFormat):"string"==typeof o?o:i.format(si(n,o),r.displayFormats.datetime)},tickFormatFunction:function(t,e,n,i){var a=this._adapter,r=this.options,o=r.time.displayFormats,s=o[this._unit],l=this._majorUnit,u=o[l],d=n[e],h=r.ticks,c=l&&u&&d&&d.major,f=a.format(t,i||(c?u:s)),g=c?h.major:h.minor,m=Kn([g.callback,g.userCallback,h.callback,h.userCallback]);return m?m(f,e,n):f},convertTicksToLabels:function(t){var e,n,i=[];for(e=0,n=t.length;e<n;++e)i.push(this.tickFormatFunction(t[e].value,e,t));return i},getPixelForOffset:function(t){var e=this._offsets,n=oi(this._table,"time",t,"pos");return this.getPixelForDecimal((e.start+n)*e.factor)},getPixelForValue:function(t,e,n){var i=null;if(void 0!==e&&void 0!==n&&(i=this._timestamps.datasets[n][e]),null===i&&(i=li(this,t)),null!==i)return this.getPixelForOffset(i)},getPixelForTick:function(t){var e=this.getTicks();return t>=0&&t<e.length?this.getPixelForOffset(e[t].value):null},getValueForPixel:function(t){var e=this._offsets,n=this.getDecimalForPixel(t)/e.factor-e.end,i=oi(this._table,"pos",n,"time");return this._adapter._create(i)},_getLabelSize:function(t){var e=this.options.ticks,n=this.ctx.measureText(t).width,i=H.toRadians(this.isHorizontal()?e.maxRotation:e.minRotation),a=Math.cos(i),r=Math.sin(i),o=Jn(e.fontSize,W.global.defaultFontSize);return{w:n*a+o*r,h:n*r+o*a}},getLabelWidth:function(t){return this._getLabelSize(t).w},getLabelCapacity:function(t){var e=this,n=e.options.time,i=n.displayFormats,a=i[n.unit]||i.millisecond,r=e.tickFormatFunction(t,0,di(e,[t],e._majorUnit),a),o=e._getLabelSize(r),s=Math.floor(e.isHorizontal()?e.width/o.w:e.height/o.h);return e.options.offset&&s--,s>0?s:1}}),ci={position:"bottom",distribution:"linear",bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,displayFormat:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{autoSkip:!1,source:"auto",major:{enabled:!1}}};hi._defaults=ci;var fi={category:wn,linear:On,logarithmic:Nn,radialLinear:Zn,time:hi},gi=e((function(e,n){e.exports=function(){var n,i;function a(){return n.apply(null,arguments)}function r(t){return t instanceof Array||"[object Array]"===Object.prototype.toString.call(t)}function o(t){return null!=t&&"[object Object]"===Object.prototype.toString.call(t)}function s(t){return void 0===t}function l(t){return"number"==typeof t||"[object Number]"===Object.prototype.toString.call(t)}function u(t){return t instanceof Date||"[object Date]"===Object.prototype.toString.call(t)}function d(t,e){var n,i=[];for(n=0;n<t.length;++n)i.push(e(t[n],n));return i}function h(t,e){return Object.prototype.hasOwnProperty.call(t,e)}function c(t,e){for(var n in e)h(e,n)&&(t[n]=e[n]);return h(e,"toString")&&(t.toString=e.toString),h(e,"valueOf")&&(t.valueOf=e.valueOf),t}function f(t,e,n,i){return Ie(t,e,n,i,!0).utc()}function g(t){return null==t._pf&&(t._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1,parsedDateParts:[],meridiem:null,rfc2822:!1,weekdayMismatch:!1}),t._pf}function m(t){if(null==t._isValid){var e=g(t),n=i.call(e.parsedDateParts,(function(t){return null!=t})),a=!isNaN(t._d.getTime())&&e.overflow<0&&!e.empty&&!e.invalidMonth&&!e.invalidWeekday&&!e.weekdayMismatch&&!e.nullInput&&!e.invalidFormat&&!e.userInvalidated&&(!e.meridiem||e.meridiem&&n);if(t._strict&&(a=a&&0===e.charsLeftOver&&0===e.unusedTokens.length&&void 0===e.bigHour),null!=Object.isFrozen&&Object.isFrozen(t))return a;t._isValid=a}return t._isValid}function p(t){var e=f(NaN);return null!=t?c(g(e),t):g(e).userInvalidated=!0,e}i=Array.prototype.some?Array.prototype.some:function(t){for(var e=Object(this),n=e.length>>>0,i=0;i<n;i++)if(i in e&&t.call(this,e[i],i,e))return!0;return!1};var v=a.momentProperties=[];function b(t,e){var n,i,a;if(s(e._isAMomentObject)||(t._isAMomentObject=e._isAMomentObject),s(e._i)||(t._i=e._i),s(e._f)||(t._f=e._f),s(e._l)||(t._l=e._l),s(e._strict)||(t._strict=e._strict),s(e._tzm)||(t._tzm=e._tzm),s(e._isUTC)||(t._isUTC=e._isUTC),s(e._offset)||(t._offset=e._offset),s(e._pf)||(t._pf=g(e)),s(e._locale)||(t._locale=e._locale),v.length>0)for(n=0;n<v.length;n++)s(a=e[i=v[n]])||(t[i]=a);return t}var y=!1;function x(t){b(this,t),this._d=new Date(null!=t._d?t._d.getTime():NaN),this.isValid()||(this._d=new Date(NaN)),!1===y&&(y=!0,a.updateOffset(this),y=!1)}function _(t){return t instanceof x||null!=t&&null!=t._isAMomentObject}function w(t){return t<0?Math.ceil(t)||0:Math.floor(t)}function k(t){var e=+t,n=0;return 0!==e&&isFinite(e)&&(n=w(e)),n}function M(t,e,n){var i,a=Math.min(t.length,e.length),r=Math.abs(t.length-e.length),o=0;for(i=0;i<a;i++)(n&&t[i]!==e[i]||!n&&k(t[i])!==k(e[i]))&&o++;return o+r}function S(t){!1===a.suppressDeprecationWarnings&&"undefined"!=typeof console&&console.warn&&console.warn("Deprecation warning: "+t)}function D(t,e){var n=!0;return c((function(){if(null!=a.deprecationHandler&&a.deprecationHandler(null,t),n){for(var i,r=[],o=0;o<arguments.length;o++){if(i="","object"==typeof arguments[o]){for(var s in i+="\n["+o+"] ",arguments[0])i+=s+": "+arguments[0][s]+", ";i=i.slice(0,-2)}else i=arguments[o];r.push(i)}S(t+"\nArguments: "+Array.prototype.slice.call(r).join("")+"\n"+(new Error).stack),n=!1}return e.apply(this,arguments)}),e)}var C,P={};function T(t,e){null!=a.deprecationHandler&&a.deprecationHandler(t,e),P[t]||(S(e),P[t]=!0)}function O(t){return t instanceof Function||"[object Function]"===Object.prototype.toString.call(t)}function A(t,e){var n,i=c({},t);for(n in e)h(e,n)&&(o(t[n])&&o(e[n])?(i[n]={},c(i[n],t[n]),c(i[n],e[n])):null!=e[n]?i[n]=e[n]:delete i[n]);for(n in t)h(t,n)&&!h(e,n)&&o(t[n])&&(i[n]=c({},i[n]));return i}function F(t){null!=t&&this.set(t)}a.suppressDeprecationWarnings=!1,a.deprecationHandler=null,C=Object.keys?Object.keys:function(t){var e,n=[];for(e in t)h(t,e)&&n.push(e);return n};var I={};function L(t,e){var n=t.toLowerCase();I[n]=I[n+"s"]=I[e]=t}function R(t){return"string"==typeof t?I[t]||I[t.toLowerCase()]:void 0}function N(t){var e,n,i={};for(n in t)h(t,n)&&(e=R(n))&&(i[e]=t[n]);return i}var W={};function Y(t,e){W[t]=e}function z(t,e,n){var i=""+Math.abs(t),a=e-i.length;return(t>=0?n?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+i}var E=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,V=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,H={},B={};function j(t,e,n,i){var a=i;"string"==typeof i&&(a=function(){return this[i]()}),t&&(B[t]=a),e&&(B[e[0]]=function(){return z(a.apply(this,arguments),e[1],e[2])}),n&&(B[n]=function(){return this.localeData().ordinal(a.apply(this,arguments),t)})}function U(t,e){return t.isValid()?(e=G(e,t.localeData()),H[e]=H[e]||function(t){var e,n,i,a=t.match(E);for(e=0,n=a.length;e<n;e++)B[a[e]]?a[e]=B[a[e]]:a[e]=(i=a[e]).match(/\[[\s\S]/)?i.replace(/^\[|\]$/g,""):i.replace(/\\/g,"");return function(e){var i,r="";for(i=0;i<n;i++)r+=O(a[i])?a[i].call(e,t):a[i];return r}}(e),H[e](t)):t.localeData().invalidDate()}function G(t,e){var n=5;function i(t){return e.longDateFormat(t)||t}for(V.lastIndex=0;n>=0&&V.test(t);)t=t.replace(V,i),V.lastIndex=0,n-=1;return t}var q=/\d/,Z=/\d\d/,$=/\d{3}/,X=/\d{4}/,K=/[+-]?\d{6}/,J=/\d\d?/,Q=/\d\d\d\d?/,tt=/\d\d\d\d\d\d?/,et=/\d{1,3}/,nt=/\d{1,4}/,it=/[+-]?\d{1,6}/,at=/\d+/,rt=/[+-]?\d+/,ot=/Z|[+-]\d\d:?\d\d/gi,st=/Z|[+-]\d\d(?::?\d\d)?/gi,lt=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,ut={};function dt(t,e,n){ut[t]=O(e)?e:function(t,i){return t&&n?n:e}}function ht(t,e){return h(ut,t)?ut[t](e._strict,e._locale):new RegExp(ct(t.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,(function(t,e,n,i,a){return e||n||i||a}))))}function ct(t){return t.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var ft={};function gt(t,e){var n,i=e;for("string"==typeof t&&(t=[t]),l(e)&&(i=function(t,n){n[e]=k(t)}),n=0;n<t.length;n++)ft[t[n]]=i}function mt(t,e){gt(t,(function(t,n,i,a){i._w=i._w||{},e(t,i._w,i,a)}))}function pt(t,e,n){null!=e&&h(ft,t)&&ft[t](e,n._a,n,t)}var vt=0,bt=1,yt=2,xt=3,_t=4,wt=5,kt=6,Mt=7,St=8;function Dt(t){return Ct(t)?366:365}function Ct(t){return t%4==0&&t%100!=0||t%400==0}j("Y",0,0,(function(){var t=this.year();return t<=9999?""+t:"+"+t})),j(0,["YY",2],0,(function(){return this.year()%100})),j(0,["YYYY",4],0,"year"),j(0,["YYYYY",5],0,"year"),j(0,["YYYYYY",6,!0],0,"year"),L("year","y"),Y("year",1),dt("Y",rt),dt("YY",J,Z),dt("YYYY",nt,X),dt("YYYYY",it,K),dt("YYYYYY",it,K),gt(["YYYYY","YYYYYY"],vt),gt("YYYY",(function(t,e){e[vt]=2===t.length?a.parseTwoDigitYear(t):k(t)})),gt("YY",(function(t,e){e[vt]=a.parseTwoDigitYear(t)})),gt("Y",(function(t,e){e[vt]=parseInt(t,10)})),a.parseTwoDigitYear=function(t){return k(t)+(k(t)>68?1900:2e3)};var Pt,Tt=Ot("FullYear",!0);function Ot(t,e){return function(n){return null!=n?(Ft(this,t,n),a.updateOffset(this,e),this):At(this,t)}}function At(t,e){return t.isValid()?t._d["get"+(t._isUTC?"UTC":"")+e]():NaN}function Ft(t,e,n){t.isValid()&&!isNaN(n)&&("FullYear"===e&&Ct(t.year())&&1===t.month()&&29===t.date()?t._d["set"+(t._isUTC?"UTC":"")+e](n,t.month(),It(n,t.month())):t._d["set"+(t._isUTC?"UTC":"")+e](n))}function It(t,e){if(isNaN(t)||isNaN(e))return NaN;var n=function(t,e){return(t%e+e)%e}(e,12);return t+=(e-n)/12,1===n?Ct(t)?29:28:31-n%7%2}Pt=Array.prototype.indexOf?Array.prototype.indexOf:function(t){var e;for(e=0;e<this.length;++e)if(this[e]===t)return e;return-1},j("M",["MM",2],"Mo",(function(){return this.month()+1})),j("MMM",0,0,(function(t){return this.localeData().monthsShort(this,t)})),j("MMMM",0,0,(function(t){return this.localeData().months(this,t)})),L("month","M"),Y("month",8),dt("M",J),dt("MM",J,Z),dt("MMM",(function(t,e){return e.monthsShortRegex(t)})),dt("MMMM",(function(t,e){return e.monthsRegex(t)})),gt(["M","MM"],(function(t,e){e[bt]=k(t)-1})),gt(["MMM","MMMM"],(function(t,e,n,i){var a=n._locale.monthsParse(t,i,n._strict);null!=a?e[bt]=a:g(n).invalidMonth=t}));var Lt=/D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,Rt="January_February_March_April_May_June_July_August_September_October_November_December".split("_"),Nt="Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_");function Wt(t,e,n){var i,a,r,o=t.toLocaleLowerCase();if(!this._monthsParse)for(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[],i=0;i<12;++i)r=f([2e3,i]),this._shortMonthsParse[i]=this.monthsShort(r,"").toLocaleLowerCase(),this._longMonthsParse[i]=this.months(r,"").toLocaleLowerCase();return n?"MMM"===e?-1!==(a=Pt.call(this._shortMonthsParse,o))?a:null:-1!==(a=Pt.call(this._longMonthsParse,o))?a:null:"MMM"===e?-1!==(a=Pt.call(this._shortMonthsParse,o))?a:-1!==(a=Pt.call(this._longMonthsParse,o))?a:null:-1!==(a=Pt.call(this._longMonthsParse,o))?a:-1!==(a=Pt.call(this._shortMonthsParse,o))?a:null}function Yt(t,e){var n;if(!t.isValid())return t;if("string"==typeof e)if(/^\d+$/.test(e))e=k(e);else if(!l(e=t.localeData().monthsParse(e)))return t;return n=Math.min(t.date(),It(t.year(),e)),t._d["set"+(t._isUTC?"UTC":"")+"Month"](e,n),t}function zt(t){return null!=t?(Yt(this,t),a.updateOffset(this,!0),this):At(this,"Month")}var Et=lt,Vt=lt;function Ht(){function t(t,e){return e.length-t.length}var e,n,i=[],a=[],r=[];for(e=0;e<12;e++)n=f([2e3,e]),i.push(this.monthsShort(n,"")),a.push(this.months(n,"")),r.push(this.months(n,"")),r.push(this.monthsShort(n,""));for(i.sort(t),a.sort(t),r.sort(t),e=0;e<12;e++)i[e]=ct(i[e]),a[e]=ct(a[e]);for(e=0;e<24;e++)r[e]=ct(r[e]);this._monthsRegex=new RegExp("^("+r.join("|")+")","i"),this._monthsShortRegex=this._monthsRegex,this._monthsStrictRegex=new RegExp("^("+a.join("|")+")","i"),this._monthsShortStrictRegex=new RegExp("^("+i.join("|")+")","i")}function Bt(t,e,n,i,a,r,o){var s;return t<100&&t>=0?(s=new Date(t+400,e,n,i,a,r,o),isFinite(s.getFullYear())&&s.setFullYear(t)):s=new Date(t,e,n,i,a,r,o),s}function jt(t){var e;if(t<100&&t>=0){var n=Array.prototype.slice.call(arguments);n[0]=t+400,e=new Date(Date.UTC.apply(null,n)),isFinite(e.getUTCFullYear())&&e.setUTCFullYear(t)}else e=new Date(Date.UTC.apply(null,arguments));return e}function Ut(t,e,n){var i=7+e-n;return-(7+jt(t,0,i).getUTCDay()-e)%7+i-1}function Gt(t,e,n,i,a){var r,o,s=1+7*(e-1)+(7+n-i)%7+Ut(t,i,a);return s<=0?o=Dt(r=t-1)+s:s>Dt(t)?(r=t+1,o=s-Dt(t)):(r=t,o=s),{year:r,dayOfYear:o}}function qt(t,e,n){var i,a,r=Ut(t.year(),e,n),o=Math.floor((t.dayOfYear()-r-1)/7)+1;return o<1?i=o+Zt(a=t.year()-1,e,n):o>Zt(t.year(),e,n)?(i=o-Zt(t.year(),e,n),a=t.year()+1):(a=t.year(),i=o),{week:i,year:a}}function Zt(t,e,n){var i=Ut(t,e,n),a=Ut(t+1,e,n);return(Dt(t)-i+a)/7}function $t(t,e){return t.slice(e,7).concat(t.slice(0,e))}j("w",["ww",2],"wo","week"),j("W",["WW",2],"Wo","isoWeek"),L("week","w"),L("isoWeek","W"),Y("week",5),Y("isoWeek",5),dt("w",J),dt("ww",J,Z),dt("W",J),dt("WW",J,Z),mt(["w","ww","W","WW"],(function(t,e,n,i){e[i.substr(0,1)]=k(t)})),j("d",0,"do","day"),j("dd",0,0,(function(t){return this.localeData().weekdaysMin(this,t)})),j("ddd",0,0,(function(t){return this.localeData().weekdaysShort(this,t)})),j("dddd",0,0,(function(t){return this.localeData().weekdays(this,t)})),j("e",0,0,"weekday"),j("E",0,0,"isoWeekday"),L("day","d"),L("weekday","e"),L("isoWeekday","E"),Y("day",11),Y("weekday",11),Y("isoWeekday",11),dt("d",J),dt("e",J),dt("E",J),dt("dd",(function(t,e){return e.weekdaysMinRegex(t)})),dt("ddd",(function(t,e){return e.weekdaysShortRegex(t)})),dt("dddd",(function(t,e){return e.weekdaysRegex(t)})),mt(["dd","ddd","dddd"],(function(t,e,n,i){var a=n._locale.weekdaysParse(t,i,n._strict);null!=a?e.d=a:g(n).invalidWeekday=t})),mt(["d","e","E"],(function(t,e,n,i){e[i]=k(t)}));var Xt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Kt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Jt="Su_Mo_Tu_We_Th_Fr_Sa".split("_");function Qt(t,e,n){var i,a,r,o=t.toLocaleLowerCase();if(!this._weekdaysParse)for(this._weekdaysParse=[],this._shortWeekdaysParse=[],this._minWeekdaysParse=[],i=0;i<7;++i)r=f([2e3,1]).day(i),this._minWeekdaysParse[i]=this.weekdaysMin(r,"").toLocaleLowerCase(),this._shortWeekdaysParse[i]=this.weekdaysShort(r,"").toLocaleLowerCase(),this._weekdaysParse[i]=this.weekdays(r,"").toLocaleLowerCase();return n?"dddd"===e?-1!==(a=Pt.call(this._weekdaysParse,o))?a:null:"ddd"===e?-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:null:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:"dddd"===e?-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:"ddd"===e?-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:null:-1!==(a=Pt.call(this._minWeekdaysParse,o))?a:-1!==(a=Pt.call(this._weekdaysParse,o))?a:-1!==(a=Pt.call(this._shortWeekdaysParse,o))?a:null}var te=lt,ee=lt,ne=lt;function ie(){function t(t,e){return e.length-t.length}var e,n,i,a,r,o=[],s=[],l=[],u=[];for(e=0;e<7;e++)n=f([2e3,1]).day(e),i=this.weekdaysMin(n,""),a=this.weekdaysShort(n,""),r=this.weekdays(n,""),o.push(i),s.push(a),l.push(r),u.push(i),u.push(a),u.push(r);for(o.sort(t),s.sort(t),l.sort(t),u.sort(t),e=0;e<7;e++)s[e]=ct(s[e]),l[e]=ct(l[e]),u[e]=ct(u[e]);this._weekdaysRegex=new RegExp("^("+u.join("|")+")","i"),this._weekdaysShortRegex=this._weekdaysRegex,this._weekdaysMinRegex=this._weekdaysRegex,this._weekdaysStrictRegex=new RegExp("^("+l.join("|")+")","i"),this._weekdaysShortStrictRegex=new RegExp("^("+s.join("|")+")","i"),this._weekdaysMinStrictRegex=new RegExp("^("+o.join("|")+")","i")}function ae(){return this.hours()%12||12}function re(t,e){j(t,0,0,(function(){return this.localeData().meridiem(this.hours(),this.minutes(),e)}))}function oe(t,e){return e._meridiemParse}j("H",["HH",2],0,"hour"),j("h",["hh",2],0,ae),j("k",["kk",2],0,(function(){return this.hours()||24})),j("hmm",0,0,(function(){return""+ae.apply(this)+z(this.minutes(),2)})),j("hmmss",0,0,(function(){return""+ae.apply(this)+z(this.minutes(),2)+z(this.seconds(),2)})),j("Hmm",0,0,(function(){return""+this.hours()+z(this.minutes(),2)})),j("Hmmss",0,0,(function(){return""+this.hours()+z(this.minutes(),2)+z(this.seconds(),2)})),re("a",!0),re("A",!1),L("hour","h"),Y("hour",13),dt("a",oe),dt("A",oe),dt("H",J),dt("h",J),dt("k",J),dt("HH",J,Z),dt("hh",J,Z),dt("kk",J,Z),dt("hmm",Q),dt("hmmss",tt),dt("Hmm",Q),dt("Hmmss",tt),gt(["H","HH"],xt),gt(["k","kk"],(function(t,e,n){var i=k(t);e[xt]=24===i?0:i})),gt(["a","A"],(function(t,e,n){n._isPm=n._locale.isPM(t),n._meridiem=t})),gt(["h","hh"],(function(t,e,n){e[xt]=k(t),g(n).bigHour=!0})),gt("hmm",(function(t,e,n){var i=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i)),g(n).bigHour=!0})),gt("hmmss",(function(t,e,n){var i=t.length-4,a=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i,2)),e[wt]=k(t.substr(a)),g(n).bigHour=!0})),gt("Hmm",(function(t,e,n){var i=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i))})),gt("Hmmss",(function(t,e,n){var i=t.length-4,a=t.length-2;e[xt]=k(t.substr(0,i)),e[_t]=k(t.substr(i,2)),e[wt]=k(t.substr(a))}));var se,le=Ot("Hours",!0),ue={calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},longDateFormat:{LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},invalidDate:"Invalid date",ordinal:"%d",dayOfMonthOrdinalParse:/\d{1,2}/,relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},months:Rt,monthsShort:Nt,week:{dow:0,doy:6},weekdays:Xt,weekdaysMin:Jt,weekdaysShort:Kt,meridiemParse:/[ap]\.?m?\.?/i},de={},he={};function ce(t){return t?t.toLowerCase().replace("_","-"):t}function fe(n){var i=null;if(!de[n]&&e&&e.exports)try{i=se._abbr,t(),ge(i)}catch(t){}return de[n]}function ge(t,e){var n;return t&&((n=s(e)?pe(t):me(t,e))?se=n:"undefined"!=typeof console&&console.warn&&console.warn("Locale "+t+" not found. Did you forget to load it?")),se._abbr}function me(t,e){if(null!==e){var n,i=ue;if(e.abbr=t,null!=de[t])T("defineLocaleOverride","use moment.updateLocale(localeName, config) to change an existing locale. moment.defineLocale(localeName, config) should only be used for creating a new locale See http://momentjs.com/guides/#/warnings/define-locale/ for more info."),i=de[t]._config;else if(null!=e.parentLocale)if(null!=de[e.parentLocale])i=de[e.parentLocale]._config;else{if(null==(n=fe(e.parentLocale)))return he[e.parentLocale]||(he[e.parentLocale]=[]),he[e.parentLocale].push({name:t,config:e}),null;i=n._config}return de[t]=new F(A(i,e)),he[t]&&he[t].forEach((function(t){me(t.name,t.config)})),ge(t),de[t]}return delete de[t],null}function pe(t){var e;if(t&&t._locale&&t._locale._abbr&&(t=t._locale._abbr),!t)return se;if(!r(t)){if(e=fe(t))return e;t=[t]}return function(t){for(var e,n,i,a,r=0;r<t.length;){for(e=(a=ce(t[r]).split("-")).length,n=(n=ce(t[r+1]))?n.split("-"):null;e>0;){if(i=fe(a.slice(0,e).join("-")))return i;if(n&&n.length>=e&&M(a,n,!0)>=e-1)break;e--}r++}return se}(t)}function ve(t){var e,n=t._a;return n&&-2===g(t).overflow&&(e=n[bt]<0||n[bt]>11?bt:n[yt]<1||n[yt]>It(n[vt],n[bt])?yt:n[xt]<0||n[xt]>24||24===n[xt]&&(0!==n[_t]||0!==n[wt]||0!==n[kt])?xt:n[_t]<0||n[_t]>59?_t:n[wt]<0||n[wt]>59?wt:n[kt]<0||n[kt]>999?kt:-1,g(t)._overflowDayOfYear&&(e<vt||e>yt)&&(e=yt),g(t)._overflowWeeks&&-1===e&&(e=Mt),g(t)._overflowWeekday&&-1===e&&(e=St),g(t).overflow=e),t}function be(t,e,n){return null!=t?t:null!=e?e:n}function ye(t){var e,n,i,r,o,s=[];if(!t._d){for(i=function(t){var e=new Date(a.now());return t._useUTC?[e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate()]:[e.getFullYear(),e.getMonth(),e.getDate()]}(t),t._w&&null==t._a[yt]&&null==t._a[bt]&&function(t){var e,n,i,a,r,o,s,l;if(null!=(e=t._w).GG||null!=e.W||null!=e.E)r=1,o=4,n=be(e.GG,t._a[vt],qt(Le(),1,4).year),i=be(e.W,1),((a=be(e.E,1))<1||a>7)&&(l=!0);else{r=t._locale._week.dow,o=t._locale._week.doy;var u=qt(Le(),r,o);n=be(e.gg,t._a[vt],u.year),i=be(e.w,u.week),null!=e.d?((a=e.d)<0||a>6)&&(l=!0):null!=e.e?(a=e.e+r,(e.e<0||e.e>6)&&(l=!0)):a=r}i<1||i>Zt(n,r,o)?g(t)._overflowWeeks=!0:null!=l?g(t)._overflowWeekday=!0:(s=Gt(n,i,a,r,o),t._a[vt]=s.year,t._dayOfYear=s.dayOfYear)}(t),null!=t._dayOfYear&&(o=be(t._a[vt],i[vt]),(t._dayOfYear>Dt(o)||0===t._dayOfYear)&&(g(t)._overflowDayOfYear=!0),n=jt(o,0,t._dayOfYear),t._a[bt]=n.getUTCMonth(),t._a[yt]=n.getUTCDate()),e=0;e<3&&null==t._a[e];++e)t._a[e]=s[e]=i[e];for(;e<7;e++)t._a[e]=s[e]=null==t._a[e]?2===e?1:0:t._a[e];24===t._a[xt]&&0===t._a[_t]&&0===t._a[wt]&&0===t._a[kt]&&(t._nextDay=!0,t._a[xt]=0),t._d=(t._useUTC?jt:Bt).apply(null,s),r=t._useUTC?t._d.getUTCDay():t._d.getDay(),null!=t._tzm&&t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),t._nextDay&&(t._a[xt]=24),t._w&&void 0!==t._w.d&&t._w.d!==r&&(g(t).weekdayMismatch=!0)}}var xe=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,_e=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,we=/Z|[+-]\d\d(?::?\d\d)?/,ke=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,!1],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,!1],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,!1],["YYYYDDD",/\d{7}/]],Me=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],Se=/^\/?Date\((\-?\d+)/i;function De(t){var e,n,i,a,r,o,s=t._i,l=xe.exec(s)||_e.exec(s);if(l){for(g(t).iso=!0,e=0,n=ke.length;e<n;e++)if(ke[e][1].exec(l[1])){a=ke[e][0],i=!1!==ke[e][2];break}if(null==a)return void(t._isValid=!1);if(l[3]){for(e=0,n=Me.length;e<n;e++)if(Me[e][1].exec(l[3])){r=(l[2]||" ")+Me[e][0];break}if(null==r)return void(t._isValid=!1)}if(!i&&null!=r)return void(t._isValid=!1);if(l[4]){if(!we.exec(l[4]))return void(t._isValid=!1);o="Z"}t._f=a+(r||"")+(o||""),Ae(t)}else t._isValid=!1}var Ce=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;function Pe(t){var e=parseInt(t,10);return e<=49?2e3+e:e<=999?1900+e:e}var Te={UT:0,GMT:0,EDT:-240,EST:-300,CDT:-300,CST:-360,MDT:-360,MST:-420,PDT:-420,PST:-480};function Oe(t){var e,n,i,a,r,o,s,l=Ce.exec(t._i.replace(/\([^)]*\)|[\n\t]/g," ").replace(/(\s\s+)/g," ").replace(/^\s\s*/,"").replace(/\s\s*$/,""));if(l){var u=(e=l[4],n=l[3],i=l[2],a=l[5],r=l[6],o=l[7],s=[Pe(e),Nt.indexOf(n),parseInt(i,10),parseInt(a,10),parseInt(r,10)],o&&s.push(parseInt(o,10)),s);if(!function(t,e,n){return!t||Kt.indexOf(t)===new Date(e[0],e[1],e[2]).getDay()||(g(n).weekdayMismatch=!0,n._isValid=!1,!1)}(l[1],u,t))return;t._a=u,t._tzm=function(t,e,n){if(t)return Te[t];if(e)return 0;var i=parseInt(n,10),a=i%100;return(i-a)/100*60+a}(l[8],l[9],l[10]),t._d=jt.apply(null,t._a),t._d.setUTCMinutes(t._d.getUTCMinutes()-t._tzm),g(t).rfc2822=!0}else t._isValid=!1}function Ae(t){if(t._f!==a.ISO_8601)if(t._f!==a.RFC_2822){t._a=[],g(t).empty=!0;var e,n,i,r,o,s=""+t._i,l=s.length,u=0;for(i=G(t._f,t._locale).match(E)||[],e=0;e<i.length;e++)r=i[e],(n=(s.match(ht(r,t))||[])[0])&&((o=s.substr(0,s.indexOf(n))).length>0&&g(t).unusedInput.push(o),s=s.slice(s.indexOf(n)+n.length),u+=n.length),B[r]?(n?g(t).empty=!1:g(t).unusedTokens.push(r),pt(r,n,t)):t._strict&&!n&&g(t).unusedTokens.push(r);g(t).charsLeftOver=l-u,s.length>0&&g(t).unusedInput.push(s),t._a[xt]<=12&&!0===g(t).bigHour&&t._a[xt]>0&&(g(t).bigHour=void 0),g(t).parsedDateParts=t._a.slice(0),g(t).meridiem=t._meridiem,t._a[xt]=function(t,e,n){var i;return null==n?e:null!=t.meridiemHour?t.meridiemHour(e,n):null!=t.isPM?((i=t.isPM(n))&&e<12&&(e+=12),i||12!==e||(e=0),e):e}(t._locale,t._a[xt],t._meridiem),ye(t),ve(t)}else Oe(t);else De(t)}function Fe(t){var e=t._i,n=t._f;return t._locale=t._locale||pe(t._l),null===e||void 0===n&&""===e?p({nullInput:!0}):("string"==typeof e&&(t._i=e=t._locale.preparse(e)),_(e)?new x(ve(e)):(u(e)?t._d=e:r(n)?function(t){var e,n,i,a,r;if(0===t._f.length)return g(t).invalidFormat=!0,void(t._d=new Date(NaN));for(a=0;a<t._f.length;a++)r=0,e=b({},t),null!=t._useUTC&&(e._useUTC=t._useUTC),e._f=t._f[a],Ae(e),m(e)&&(r+=g(e).charsLeftOver,r+=10*g(e).unusedTokens.length,g(e).score=r,(null==i||r<i)&&(i=r,n=e));c(t,n||e)}(t):n?Ae(t):function(t){var e=t._i;s(e)?t._d=new Date(a.now()):u(e)?t._d=new Date(e.valueOf()):"string"==typeof e?function(t){var e=Se.exec(t._i);null===e?(De(t),!1===t._isValid&&(delete t._isValid,Oe(t),!1===t._isValid&&(delete t._isValid,a.createFromInputFallback(t)))):t._d=new Date(+e[1])}(t):r(e)?(t._a=d(e.slice(0),(function(t){return parseInt(t,10)})),ye(t)):o(e)?function(t){if(!t._d){var e=N(t._i);t._a=d([e.year,e.month,e.day||e.date,e.hour,e.minute,e.second,e.millisecond],(function(t){return t&&parseInt(t,10)})),ye(t)}}(t):l(e)?t._d=new Date(e):a.createFromInputFallback(t)}(t),m(t)||(t._d=null),t))}function Ie(t,e,n,i,a){var s,l={};return!0!==n&&!1!==n||(i=n,n=void 0),(o(t)&&function(t){if(Object.getOwnPropertyNames)return 0===Object.getOwnPropertyNames(t).length;var e;for(e in t)if(t.hasOwnProperty(e))return!1;return!0}(t)||r(t)&&0===t.length)&&(t=void 0),l._isAMomentObject=!0,l._useUTC=l._isUTC=a,l._l=n,l._i=t,l._f=e,l._strict=i,(s=new x(ve(Fe(l))))._nextDay&&(s.add(1,"d"),s._nextDay=void 0),s}function Le(t,e,n,i){return Ie(t,e,n,i,!1)}a.createFromInputFallback=D("value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.",(function(t){t._d=new Date(t._i+(t._useUTC?" UTC":""))})),a.ISO_8601=function(){},a.RFC_2822=function(){};var Re=D("moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/",(function(){var t=Le.apply(null,arguments);return this.isValid()&&t.isValid()?t<this?this:t:p()})),Ne=D("moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/",(function(){var t=Le.apply(null,arguments);return this.isValid()&&t.isValid()?t>this?this:t:p()}));function We(t,e){var n,i;if(1===e.length&&r(e[0])&&(e=e[0]),!e.length)return Le();for(n=e[0],i=1;i<e.length;++i)e[i].isValid()&&!e[i][t](n)||(n=e[i]);return n}var Ye=["year","quarter","month","week","day","hour","minute","second","millisecond"];function ze(t){var e=N(t),n=e.year||0,i=e.quarter||0,a=e.month||0,r=e.week||e.isoWeek||0,o=e.day||0,s=e.hour||0,l=e.minute||0,u=e.second||0,d=e.millisecond||0;this._isValid=function(t){for(var e in t)if(-1===Pt.call(Ye,e)||null!=t[e]&&isNaN(t[e]))return!1;for(var n=!1,i=0;i<Ye.length;++i)if(t[Ye[i]]){if(n)return!1;parseFloat(t[Ye[i]])!==k(t[Ye[i]])&&(n=!0)}return!0}(e),this._milliseconds=+d+1e3*u+6e4*l+1e3*s*60*60,this._days=+o+7*r,this._months=+a+3*i+12*n,this._data={},this._locale=pe(),this._bubble()}function Ee(t){return t instanceof ze}function Ve(t){return t<0?-1*Math.round(-1*t):Math.round(t)}function He(t,e){j(t,0,0,(function(){var t=this.utcOffset(),n="+";return t<0&&(t=-t,n="-"),n+z(~~(t/60),2)+e+z(~~t%60,2)}))}He("Z",":"),He("ZZ",""),dt("Z",st),dt("ZZ",st),gt(["Z","ZZ"],(function(t,e,n){n._useUTC=!0,n._tzm=je(st,t)}));var Be=/([\+\-]|\d\d)/gi;function je(t,e){var n=(e||"").match(t);if(null===n)return null;var i=((n[n.length-1]||[])+"").match(Be)||["-",0,0],a=60*i[1]+k(i[2]);return 0===a?0:"+"===i[0]?a:-a}function Ue(t,e){var n,i;return e._isUTC?(n=e.clone(),i=(_(t)||u(t)?t.valueOf():Le(t).valueOf())-n.valueOf(),n._d.setTime(n._d.valueOf()+i),a.updateOffset(n,!1),n):Le(t).local()}function Ge(t){return 15*-Math.round(t._d.getTimezoneOffset()/15)}function qe(){return!!this.isValid()&&this._isUTC&&0===this._offset}a.updateOffset=function(){};var Ze=/^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/,$e=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function Xe(t,e){var n,i,a,r,o,s,u=t,d=null;return Ee(t)?u={ms:t._milliseconds,d:t._days,M:t._months}:l(t)?(u={},e?u[e]=t:u.milliseconds=t):(d=Ze.exec(t))?(n="-"===d[1]?-1:1,u={y:0,d:k(d[yt])*n,h:k(d[xt])*n,m:k(d[_t])*n,s:k(d[wt])*n,ms:k(Ve(1e3*d[kt]))*n}):(d=$e.exec(t))?(n="-"===d[1]?-1:1,u={y:Ke(d[2],n),M:Ke(d[3],n),w:Ke(d[4],n),d:Ke(d[5],n),h:Ke(d[6],n),m:Ke(d[7],n),s:Ke(d[8],n)}):null==u?u={}:"object"==typeof u&&("from"in u||"to"in u)&&(r=Le(u.from),o=Le(u.to),a=r.isValid()&&o.isValid()?(o=Ue(o,r),r.isBefore(o)?s=Je(r,o):((s=Je(o,r)).milliseconds=-s.milliseconds,s.months=-s.months),s):{milliseconds:0,months:0},(u={}).ms=a.milliseconds,u.M=a.months),i=new ze(u),Ee(t)&&h(t,"_locale")&&(i._locale=t._locale),i}function Ke(t,e){var n=t&&parseFloat(t.replace(",","."));return(isNaN(n)?0:n)*e}function Je(t,e){var n={};return n.months=e.month()-t.month()+12*(e.year()-t.year()),t.clone().add(n.months,"M").isAfter(e)&&--n.months,n.milliseconds=+e-+t.clone().add(n.months,"M"),n}function Qe(t,e){return function(n,i){var a;return null===i||isNaN(+i)||(T(e,"moment()."+e+"(period, number) is deprecated. Please use moment()."+e+"(number, period). See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info."),a=n,n=i,i=a),tn(this,Xe(n="string"==typeof n?+n:n,i),t),this}}function tn(t,e,n,i){var r=e._milliseconds,o=Ve(e._days),s=Ve(e._months);t.isValid()&&(i=null==i||i,s&&Yt(t,At(t,"Month")+s*n),o&&Ft(t,"Date",At(t,"Date")+o*n),r&&t._d.setTime(t._d.valueOf()+r*n),i&&a.updateOffset(t,o||s))}Xe.fn=ze.prototype,Xe.invalid=function(){return Xe(NaN)};var en=Qe(1,"add"),nn=Qe(-1,"subtract");function an(t,e){var n=12*(e.year()-t.year())+(e.month()-t.month()),i=t.clone().add(n,"months");return-(n+(e-i<0?(e-i)/(i-t.clone().add(n-1,"months")):(e-i)/(t.clone().add(n+1,"months")-i)))||0}function rn(t){var e;return void 0===t?this._locale._abbr:(null!=(e=pe(t))&&(this._locale=e),this)}a.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",a.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var on=D("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",(function(t){return void 0===t?this.localeData():this.locale(t)}));function sn(){return this._locale}var ln=1e3,un=60*ln,dn=60*un,hn=3506328*dn;function cn(t,e){return(t%e+e)%e}function fn(t,e,n){return t<100&&t>=0?new Date(t+400,e,n)-hn:new Date(t,e,n).valueOf()}function gn(t,e,n){return t<100&&t>=0?Date.UTC(t+400,e,n)-hn:Date.UTC(t,e,n)}function mn(t,e){j(0,[t,t.length],0,e)}function pn(t,e,n,i,a){var r;return null==t?qt(this,i,a).year:(e>(r=Zt(t,i,a))&&(e=r),vn.call(this,t,e,n,i,a))}function vn(t,e,n,i,a){var r=Gt(t,e,n,i,a),o=jt(r.year,0,r.dayOfYear);return this.year(o.getUTCFullYear()),this.month(o.getUTCMonth()),this.date(o.getUTCDate()),this}j(0,["gg",2],0,(function(){return this.weekYear()%100})),j(0,["GG",2],0,(function(){return this.isoWeekYear()%100})),mn("gggg","weekYear"),mn("ggggg","weekYear"),mn("GGGG","isoWeekYear"),mn("GGGGG","isoWeekYear"),L("weekYear","gg"),L("isoWeekYear","GG"),Y("weekYear",1),Y("isoWeekYear",1),dt("G",rt),dt("g",rt),dt("GG",J,Z),dt("gg",J,Z),dt("GGGG",nt,X),dt("gggg",nt,X),dt("GGGGG",it,K),dt("ggggg",it,K),mt(["gggg","ggggg","GGGG","GGGGG"],(function(t,e,n,i){e[i.substr(0,2)]=k(t)})),mt(["gg","GG"],(function(t,e,n,i){e[i]=a.parseTwoDigitYear(t)})),j("Q",0,"Qo","quarter"),L("quarter","Q"),Y("quarter",7),dt("Q",q),gt("Q",(function(t,e){e[bt]=3*(k(t)-1)})),j("D",["DD",2],"Do","date"),L("date","D"),Y("date",9),dt("D",J),dt("DD",J,Z),dt("Do",(function(t,e){return t?e._dayOfMonthOrdinalParse||e._ordinalParse:e._dayOfMonthOrdinalParseLenient})),gt(["D","DD"],yt),gt("Do",(function(t,e){e[yt]=k(t.match(J)[0])}));var bn=Ot("Date",!0);j("DDD",["DDDD",3],"DDDo","dayOfYear"),L("dayOfYear","DDD"),Y("dayOfYear",4),dt("DDD",et),dt("DDDD",$),gt(["DDD","DDDD"],(function(t,e,n){n._dayOfYear=k(t)})),j("m",["mm",2],0,"minute"),L("minute","m"),Y("minute",14),dt("m",J),dt("mm",J,Z),gt(["m","mm"],_t);var yn=Ot("Minutes",!1);j("s",["ss",2],0,"second"),L("second","s"),Y("second",15),dt("s",J),dt("ss",J,Z),gt(["s","ss"],wt);var xn,_n=Ot("Seconds",!1);for(j("S",0,0,(function(){return~~(this.millisecond()/100)})),j(0,["SS",2],0,(function(){return~~(this.millisecond()/10)})),j(0,["SSS",3],0,"millisecond"),j(0,["SSSS",4],0,(function(){return 10*this.millisecond()})),j(0,["SSSSS",5],0,(function(){return 100*this.millisecond()})),j(0,["SSSSSS",6],0,(function(){return 1e3*this.millisecond()})),j(0,["SSSSSSS",7],0,(function(){return 1e4*this.millisecond()})),j(0,["SSSSSSSS",8],0,(function(){return 1e5*this.millisecond()})),j(0,["SSSSSSSSS",9],0,(function(){return 1e6*this.millisecond()})),L("millisecond","ms"),Y("millisecond",16),dt("S",et,q),dt("SS",et,Z),dt("SSS",et,$),xn="SSSS";xn.length<=9;xn+="S")dt(xn,at);function wn(t,e){e[kt]=k(1e3*("0."+t))}for(xn="S";xn.length<=9;xn+="S")gt(xn,wn);var kn=Ot("Milliseconds",!1);j("z",0,0,"zoneAbbr"),j("zz",0,0,"zoneName");var Mn=x.prototype;function Sn(t){return t}Mn.add=en,Mn.calendar=function(t,e){var n=t||Le(),i=Ue(n,this).startOf("day"),r=a.calendarFormat(this,i)||"sameElse",o=e&&(O(e[r])?e[r].call(this,n):e[r]);return this.format(o||this.localeData().calendar(r,this,Le(n)))},Mn.clone=function(){return new x(this)},Mn.diff=function(t,e,n){var i,a,r;if(!this.isValid())return NaN;if(!(i=Ue(t,this)).isValid())return NaN;switch(a=6e4*(i.utcOffset()-this.utcOffset()),e=R(e)){case"year":r=an(this,i)/12;break;case"month":r=an(this,i);break;case"quarter":r=an(this,i)/3;break;case"second":r=(this-i)/1e3;break;case"minute":r=(this-i)/6e4;break;case"hour":r=(this-i)/36e5;break;case"day":r=(this-i-a)/864e5;break;case"week":r=(this-i-a)/6048e5;break;default:r=this-i}return n?r:w(r)},Mn.endOf=function(t){var e;if(void 0===(t=R(t))||"millisecond"===t||!this.isValid())return this;var n=this._isUTC?gn:fn;switch(t){case"year":e=n(this.year()+1,0,1)-1;break;case"quarter":e=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":e=n(this.year(),this.month()+1,1)-1;break;case"week":e=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":e=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":e=n(this.year(),this.month(),this.date()+1)-1;break;case"hour":e=this._d.valueOf(),e+=dn-cn(e+(this._isUTC?0:this.utcOffset()*un),dn)-1;break;case"minute":e=this._d.valueOf(),e+=un-cn(e,un)-1;break;case"second":e=this._d.valueOf(),e+=ln-cn(e,ln)-1}return this._d.setTime(e),a.updateOffset(this,!0),this},Mn.format=function(t){t||(t=this.isUtc()?a.defaultFormatUtc:a.defaultFormat);var e=U(this,t);return this.localeData().postformat(e)},Mn.from=function(t,e){return this.isValid()&&(_(t)&&t.isValid()||Le(t).isValid())?Xe({to:this,from:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()},Mn.fromNow=function(t){return this.from(Le(),t)},Mn.to=function(t,e){return this.isValid()&&(_(t)&&t.isValid()||Le(t).isValid())?Xe({from:this,to:t}).locale(this.locale()).humanize(!e):this.localeData().invalidDate()},Mn.toNow=function(t){return this.to(Le(),t)},Mn.get=function(t){return O(this[t=R(t)])?this[t]():this},Mn.invalidAt=function(){return g(this).overflow},Mn.isAfter=function(t,e){var n=_(t)?t:Le(t);return!(!this.isValid()||!n.isValid())&&("millisecond"===(e=R(e)||"millisecond")?this.valueOf()>n.valueOf():n.valueOf()<this.clone().startOf(e).valueOf())},Mn.isBefore=function(t,e){var n=_(t)?t:Le(t);return!(!this.isValid()||!n.isValid())&&("millisecond"===(e=R(e)||"millisecond")?this.valueOf()<n.valueOf():this.clone().endOf(e).valueOf()<n.valueOf())},Mn.isBetween=function(t,e,n,i){var a=_(t)?t:Le(t),r=_(e)?e:Le(e);return!!(this.isValid()&&a.isValid()&&r.isValid())&&("("===(i=i||"()")[0]?this.isAfter(a,n):!this.isBefore(a,n))&&(")"===i[1]?this.isBefore(r,n):!this.isAfter(r,n))},Mn.isSame=function(t,e){var n,i=_(t)?t:Le(t);return!(!this.isValid()||!i.isValid())&&("millisecond"===(e=R(e)||"millisecond")?this.valueOf()===i.valueOf():(n=i.valueOf(),this.clone().startOf(e).valueOf()<=n&&n<=this.clone().endOf(e).valueOf()))},Mn.isSameOrAfter=function(t,e){return this.isSame(t,e)||this.isAfter(t,e)},Mn.isSameOrBefore=function(t,e){return this.isSame(t,e)||this.isBefore(t,e)},Mn.isValid=function(){return m(this)},Mn.lang=on,Mn.locale=rn,Mn.localeData=sn,Mn.max=Ne,Mn.min=Re,Mn.parsingFlags=function(){return c({},g(this))},Mn.set=function(t,e){if("object"==typeof t)for(var n=function(t){var e=[];for(var n in t)e.push({unit:n,priority:W[n]});return e.sort((function(t,e){return t.priority-e.priority})),e}(t=N(t)),i=0;i<n.length;i++)this[n[i].unit](t[n[i].unit]);else if(O(this[t=R(t)]))return this[t](e);return this},Mn.startOf=function(t){var e;if(void 0===(t=R(t))||"millisecond"===t||!this.isValid())return this;var n=this._isUTC?gn:fn;switch(t){case"year":e=n(this.year(),0,1);break;case"quarter":e=n(this.year(),this.month()-this.month()%3,1);break;case"month":e=n(this.year(),this.month(),1);break;case"week":e=n(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":e=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":e=n(this.year(),this.month(),this.date());break;case"hour":e=this._d.valueOf(),e-=cn(e+(this._isUTC?0:this.utcOffset()*un),dn);break;case"minute":e=this._d.valueOf(),e-=cn(e,un);break;case"second":e=this._d.valueOf(),e-=cn(e,ln)}return this._d.setTime(e),a.updateOffset(this,!0),this},Mn.subtract=nn,Mn.toArray=function(){var t=this;return[t.year(),t.month(),t.date(),t.hour(),t.minute(),t.second(),t.millisecond()]},Mn.toObject=function(){var t=this;return{years:t.year(),months:t.month(),date:t.date(),hours:t.hours(),minutes:t.minutes(),seconds:t.seconds(),milliseconds:t.milliseconds()}},Mn.toDate=function(){return new Date(this.valueOf())},Mn.toISOString=function(t){if(!this.isValid())return null;var e=!0!==t,n=e?this.clone().utc():this;return n.year()<0||n.year()>9999?U(n,e?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ"):O(Date.prototype.toISOString)?e?this.toDate().toISOString():new Date(this.valueOf()+60*this.utcOffset()*1e3).toISOString().replace("Z",U(n,"Z")):U(n,e?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")},Mn.inspect=function(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var t="moment",e="";this.isLocal()||(t=0===this.utcOffset()?"moment.utc":"moment.parseZone",e="Z");var n="["+t+'("]',i=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY",a=e+'[")]';return this.format(n+i+"-MM-DD[T]HH:mm:ss.SSS"+a)},Mn.toJSON=function(){return this.isValid()?this.toISOString():null},Mn.toString=function(){return this.clone().locale("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},Mn.unix=function(){return Math.floor(this.valueOf()/1e3)},Mn.valueOf=function(){return this._d.valueOf()-6e4*(this._offset||0)},Mn.creationData=function(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}},Mn.year=Tt,Mn.isLeapYear=function(){return Ct(this.year())},Mn.weekYear=function(t){return pn.call(this,t,this.week(),this.weekday(),this.localeData()._week.dow,this.localeData()._week.doy)},Mn.isoWeekYear=function(t){return pn.call(this,t,this.isoWeek(),this.isoWeekday(),1,4)},Mn.quarter=Mn.quarters=function(t){return null==t?Math.ceil((this.month()+1)/3):this.month(3*(t-1)+this.month()%3)},Mn.month=zt,Mn.daysInMonth=function(){return It(this.year(),this.month())},Mn.week=Mn.weeks=function(t){var e=this.localeData().week(this);return null==t?e:this.add(7*(t-e),"d")},Mn.isoWeek=Mn.isoWeeks=function(t){var e=qt(this,1,4).week;return null==t?e:this.add(7*(t-e),"d")},Mn.weeksInYear=function(){var t=this.localeData()._week;return Zt(this.year(),t.dow,t.doy)},Mn.isoWeeksInYear=function(){return Zt(this.year(),1,4)},Mn.date=bn,Mn.day=Mn.days=function(t){if(!this.isValid())return null!=t?this:NaN;var e=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=t?(t=function(t,e){return"string"!=typeof t?t:isNaN(t)?"number"==typeof(t=e.weekdaysParse(t))?t:null:parseInt(t,10)}(t,this.localeData()),this.add(t-e,"d")):e},Mn.weekday=function(t){if(!this.isValid())return null!=t?this:NaN;var e=(this.day()+7-this.localeData()._week.dow)%7;return null==t?e:this.add(t-e,"d")},Mn.isoWeekday=function(t){if(!this.isValid())return null!=t?this:NaN;if(null!=t){var e=function(t,e){return"string"==typeof t?e.weekdaysParse(t)%7||7:isNaN(t)?null:t}(t,this.localeData());return this.day(this.day()%7?e:e-7)}return this.day()||7},Mn.dayOfYear=function(t){var e=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return null==t?e:this.add(t-e,"d")},Mn.hour=Mn.hours=le,Mn.minute=Mn.minutes=yn,Mn.second=Mn.seconds=_n,Mn.millisecond=Mn.milliseconds=kn,Mn.utcOffset=function(t,e,n){var i,r=this._offset||0;if(!this.isValid())return null!=t?this:NaN;if(null!=t){if("string"==typeof t){if(null===(t=je(st,t)))return this}else Math.abs(t)<16&&!n&&(t*=60);return!this._isUTC&&e&&(i=Ge(this)),this._offset=t,this._isUTC=!0,null!=i&&this.add(i,"m"),r!==t&&(!e||this._changeInProgress?tn(this,Xe(t-r,"m"),1,!1):this._changeInProgress||(this._changeInProgress=!0,a.updateOffset(this,!0),this._changeInProgress=null)),this}return this._isUTC?r:Ge(this)},Mn.utc=function(t){return this.utcOffset(0,t)},Mn.local=function(t){return this._isUTC&&(this.utcOffset(0,t),this._isUTC=!1,t&&this.subtract(Ge(this),"m")),this},Mn.parseZone=function(){if(null!=this._tzm)this.utcOffset(this._tzm,!1,!0);else if("string"==typeof this._i){var t=je(ot,this._i);null!=t?this.utcOffset(t):this.utcOffset(0,!0)}return this},Mn.hasAlignedHourOffset=function(t){return!!this.isValid()&&(t=t?Le(t).utcOffset():0,(this.utcOffset()-t)%60==0)},Mn.isDST=function(){return this.utcOffset()>this.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()},Mn.isLocal=function(){return!!this.isValid()&&!this._isUTC},Mn.isUtcOffset=function(){return!!this.isValid()&&this._isUTC},Mn.isUtc=qe,Mn.isUTC=qe,Mn.zoneAbbr=function(){return this._isUTC?"UTC":""},Mn.zoneName=function(){return this._isUTC?"Coordinated Universal Time":""},Mn.dates=D("dates accessor is deprecated. Use date instead.",bn),Mn.months=D("months accessor is deprecated. Use month instead",zt),Mn.years=D("years accessor is deprecated. Use year instead",Tt),Mn.zone=D("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",(function(t,e){return null!=t?("string"!=typeof t&&(t=-t),this.utcOffset(t,e),this):-this.utcOffset()})),Mn.isDSTShifted=D("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",(function(){if(!s(this._isDSTShifted))return this._isDSTShifted;var t={};if(b(t,this),(t=Fe(t))._a){var e=t._isUTC?f(t._a):Le(t._a);this._isDSTShifted=this.isValid()&&M(t._a,e.toArray())>0}else this._isDSTShifted=!1;return this._isDSTShifted}));var Dn=F.prototype;function Cn(t,e,n,i){var a=pe(),r=f().set(i,e);return a[n](r,t)}function Pn(t,e,n){if(l(t)&&(e=t,t=void 0),t=t||"",null!=e)return Cn(t,e,n,"month");var i,a=[];for(i=0;i<12;i++)a[i]=Cn(t,i,n,"month");return a}function Tn(t,e,n,i){"boolean"==typeof t?(l(e)&&(n=e,e=void 0),e=e||""):(n=e=t,t=!1,l(e)&&(n=e,e=void 0),e=e||"");var a,r=pe(),o=t?r._week.dow:0;if(null!=n)return Cn(e,(n+o)%7,i,"day");var s=[];for(a=0;a<7;a++)s[a]=Cn(e,(a+o)%7,i,"day");return s}Dn.calendar=function(t,e,n){var i=this._calendar[t]||this._calendar.sameElse;return O(i)?i.call(e,n):i},Dn.longDateFormat=function(t){var e=this._longDateFormat[t],n=this._longDateFormat[t.toUpperCase()];return e||!n?e:(this._longDateFormat[t]=n.replace(/MMMM|MM|DD|dddd/g,(function(t){return t.slice(1)})),this._longDateFormat[t])},Dn.invalidDate=function(){return this._invalidDate},Dn.ordinal=function(t){return this._ordinal.replace("%d",t)},Dn.preparse=Sn,Dn.postformat=Sn,Dn.relativeTime=function(t,e,n,i){var a=this._relativeTime[n];return O(a)?a(t,e,n,i):a.replace(/%d/i,t)},Dn.pastFuture=function(t,e){var n=this._relativeTime[t>0?"future":"past"];return O(n)?n(e):n.replace(/%s/i,e)},Dn.set=function(t){var e,n;for(n in t)O(e=t[n])?this[n]=e:this["_"+n]=e;this._config=t,this._dayOfMonthOrdinalParseLenient=new RegExp((this._dayOfMonthOrdinalParse.source||this._ordinalParse.source)+"|"+/\d{1,2}/.source)},Dn.months=function(t,e){return t?r(this._months)?this._months[t.month()]:this._months[(this._months.isFormat||Lt).test(e)?"format":"standalone"][t.month()]:r(this._months)?this._months:this._months.standalone},Dn.monthsShort=function(t,e){return t?r(this._monthsShort)?this._monthsShort[t.month()]:this._monthsShort[Lt.test(e)?"format":"standalone"][t.month()]:r(this._monthsShort)?this._monthsShort:this._monthsShort.standalone},Dn.monthsParse=function(t,e,n){var i,a,r;if(this._monthsParseExact)return Wt.call(this,t,e,n);for(this._monthsParse||(this._monthsParse=[],this._longMonthsParse=[],this._shortMonthsParse=[]),i=0;i<12;i++){if(a=f([2e3,i]),n&&!this._longMonthsParse[i]&&(this._longMonthsParse[i]=new RegExp("^"+this.months(a,"").replace(".","")+"$","i"),this._shortMonthsParse[i]=new RegExp("^"+this.monthsShort(a,"").replace(".","")+"$","i")),n||this._monthsParse[i]||(r="^"+this.months(a,"")+"|^"+this.monthsShort(a,""),this._monthsParse[i]=new RegExp(r.replace(".",""),"i")),n&&"MMMM"===e&&this._longMonthsParse[i].test(t))return i;if(n&&"MMM"===e&&this._shortMonthsParse[i].test(t))return i;if(!n&&this._monthsParse[i].test(t))return i}},Dn.monthsRegex=function(t){return this._monthsParseExact?(h(this,"_monthsRegex")||Ht.call(this),t?this._monthsStrictRegex:this._monthsRegex):(h(this,"_monthsRegex")||(this._monthsRegex=Vt),this._monthsStrictRegex&&t?this._monthsStrictRegex:this._monthsRegex)},Dn.monthsShortRegex=function(t){return this._monthsParseExact?(h(this,"_monthsRegex")||Ht.call(this),t?this._monthsShortStrictRegex:this._monthsShortRegex):(h(this,"_monthsShortRegex")||(this._monthsShortRegex=Et),this._monthsShortStrictRegex&&t?this._monthsShortStrictRegex:this._monthsShortRegex)},Dn.week=function(t){return qt(t,this._week.dow,this._week.doy).week},Dn.firstDayOfYear=function(){return this._week.doy},Dn.firstDayOfWeek=function(){return this._week.dow},Dn.weekdays=function(t,e){var n=r(this._weekdays)?this._weekdays:this._weekdays[t&&!0!==t&&this._weekdays.isFormat.test(e)?"format":"standalone"];return!0===t?$t(n,this._week.dow):t?n[t.day()]:n},Dn.weekdaysMin=function(t){return!0===t?$t(this._weekdaysMin,this._week.dow):t?this._weekdaysMin[t.day()]:this._weekdaysMin},Dn.weekdaysShort=function(t){return!0===t?$t(this._weekdaysShort,this._week.dow):t?this._weekdaysShort[t.day()]:this._weekdaysShort},Dn.weekdaysParse=function(t,e,n){var i,a,r;if(this._weekdaysParseExact)return Qt.call(this,t,e,n);for(this._weekdaysParse||(this._weekdaysParse=[],this._minWeekdaysParse=[],this._shortWeekdaysParse=[],this._fullWeekdaysParse=[]),i=0;i<7;i++){if(a=f([2e3,1]).day(i),n&&!this._fullWeekdaysParse[i]&&(this._fullWeekdaysParse[i]=new RegExp("^"+this.weekdays(a,"").replace(".","\\.?")+"$","i"),this._shortWeekdaysParse[i]=new RegExp("^"+this.weekdaysShort(a,"").replace(".","\\.?")+"$","i"),this._minWeekdaysParse[i]=new RegExp("^"+this.weekdaysMin(a,"").replace(".","\\.?")+"$","i")),this._weekdaysParse[i]||(r="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,""),this._weekdaysParse[i]=new RegExp(r.replace(".",""),"i")),n&&"dddd"===e&&this._fullWeekdaysParse[i].test(t))return i;if(n&&"ddd"===e&&this._shortWeekdaysParse[i].test(t))return i;if(n&&"dd"===e&&this._minWeekdaysParse[i].test(t))return i;if(!n&&this._weekdaysParse[i].test(t))return i}},Dn.weekdaysRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysStrictRegex:this._weekdaysRegex):(h(this,"_weekdaysRegex")||(this._weekdaysRegex=te),this._weekdaysStrictRegex&&t?this._weekdaysStrictRegex:this._weekdaysRegex)},Dn.weekdaysShortRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex):(h(this,"_weekdaysShortRegex")||(this._weekdaysShortRegex=ee),this._weekdaysShortStrictRegex&&t?this._weekdaysShortStrictRegex:this._weekdaysShortRegex)},Dn.weekdaysMinRegex=function(t){return this._weekdaysParseExact?(h(this,"_weekdaysRegex")||ie.call(this),t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex):(h(this,"_weekdaysMinRegex")||(this._weekdaysMinRegex=ne),this._weekdaysMinStrictRegex&&t?this._weekdaysMinStrictRegex:this._weekdaysMinRegex)},Dn.isPM=function(t){return"p"===(t+"").toLowerCase().charAt(0)},Dn.meridiem=function(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"},ge("en",{dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(t){var e=t%10;return t+(1===k(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th")}}),a.lang=D("moment.lang is deprecated. Use moment.locale instead.",ge),a.langData=D("moment.langData is deprecated. Use moment.localeData instead.",pe);var On=Math.abs;function An(t,e,n,i){var a=Xe(e,n);return t._milliseconds+=i*a._milliseconds,t._days+=i*a._days,t._months+=i*a._months,t._bubble()}function Fn(t){return t<0?Math.floor(t):Math.ceil(t)}function In(t){return 4800*t/146097}function Ln(t){return 146097*t/4800}function Rn(t){return function(){return this.as(t)}}var Nn=Rn("ms"),Wn=Rn("s"),Yn=Rn("m"),zn=Rn("h"),En=Rn("d"),Vn=Rn("w"),Hn=Rn("M"),Bn=Rn("Q"),jn=Rn("y");function Un(t){return function(){return this.isValid()?this._data[t]:NaN}}var Gn=Un("milliseconds"),qn=Un("seconds"),Zn=Un("minutes"),$n=Un("hours"),Xn=Un("days"),Kn=Un("months"),Jn=Un("years"),Qn=Math.round,ti={ss:44,s:45,m:45,h:22,d:26,M:11};function ei(t,e,n,i,a){return a.relativeTime(e||1,!!n,t,i)}var ni=Math.abs;function ii(t){return(t>0)-(t<0)||+t}function ai(){if(!this.isValid())return this.localeData().invalidDate();var t,e,n=ni(this._milliseconds)/1e3,i=ni(this._days),a=ni(this._months);t=w(n/60),e=w(t/60),n%=60,t%=60;var r=w(a/12),o=a%=12,s=i,l=e,u=t,d=n?n.toFixed(3).replace(/\.?0+$/,""):"",h=this.asSeconds();if(!h)return"P0D";var c=h<0?"-":"",f=ii(this._months)!==ii(h)?"-":"",g=ii(this._days)!==ii(h)?"-":"",m=ii(this._milliseconds)!==ii(h)?"-":"";return c+"P"+(r?f+r+"Y":"")+(o?f+o+"M":"")+(s?g+s+"D":"")+(l||u||d?"T":"")+(l?m+l+"H":"")+(u?m+u+"M":"")+(d?m+d+"S":"")}var ri=ze.prototype;return ri.isValid=function(){return this._isValid},ri.abs=function(){var t=this._data;return this._milliseconds=On(this._milliseconds),this._days=On(this._days),this._months=On(this._months),t.milliseconds=On(t.milliseconds),t.seconds=On(t.seconds),t.minutes=On(t.minutes),t.hours=On(t.hours),t.months=On(t.months),t.years=On(t.years),this},ri.add=function(t,e){return An(this,t,e,1)},ri.subtract=function(t,e){return An(this,t,e,-1)},ri.as=function(t){if(!this.isValid())return NaN;var e,n,i=this._milliseconds;if("month"===(t=R(t))||"quarter"===t||"year"===t)switch(e=this._days+i/864e5,n=this._months+In(e),t){case"month":return n;case"quarter":return n/3;case"year":return n/12}else switch(e=this._days+Math.round(Ln(this._months)),t){case"week":return e/7+i/6048e5;case"day":return e+i/864e5;case"hour":return 24*e+i/36e5;case"minute":return 1440*e+i/6e4;case"second":return 86400*e+i/1e3;case"millisecond":return Math.floor(864e5*e)+i;default:throw new Error("Unknown unit "+t)}},ri.asMilliseconds=Nn,ri.asSeconds=Wn,ri.asMinutes=Yn,ri.asHours=zn,ri.asDays=En,ri.asWeeks=Vn,ri.asMonths=Hn,ri.asQuarters=Bn,ri.asYears=jn,ri.valueOf=function(){return this.isValid()?this._milliseconds+864e5*this._days+this._months%12*2592e6+31536e6*k(this._months/12):NaN},ri._bubble=function(){var t,e,n,i,a,r=this._milliseconds,o=this._days,s=this._months,l=this._data;return r>=0&&o>=0&&s>=0||r<=0&&o<=0&&s<=0||(r+=864e5*Fn(Ln(s)+o),o=0,s=0),l.milliseconds=r%1e3,t=w(r/1e3),l.seconds=t%60,e=w(t/60),l.minutes=e%60,n=w(e/60),l.hours=n%24,o+=w(n/24),a=w(In(o)),s+=a,o-=Fn(Ln(a)),i=w(s/12),s%=12,l.days=o,l.months=s,l.years=i,this},ri.clone=function(){return Xe(this)},ri.get=function(t){return t=R(t),this.isValid()?this[t+"s"]():NaN},ri.milliseconds=Gn,ri.seconds=qn,ri.minutes=Zn,ri.hours=$n,ri.days=Xn,ri.weeks=function(){return w(this.days()/7)},ri.months=Kn,ri.years=Jn,ri.humanize=function(t){if(!this.isValid())return this.localeData().invalidDate();var e=this.localeData(),n=function(t,e,n){var i=Xe(t).abs(),a=Qn(i.as("s")),r=Qn(i.as("m")),o=Qn(i.as("h")),s=Qn(i.as("d")),l=Qn(i.as("M")),u=Qn(i.as("y")),d=a<=ti.ss&&["s",a]||a<ti.s&&["ss",a]||r<=1&&["m"]||r<ti.m&&["mm",r]||o<=1&&["h"]||o<ti.h&&["hh",o]||s<=1&&["d"]||s<ti.d&&["dd",s]||l<=1&&["M"]||l<ti.M&&["MM",l]||u<=1&&["y"]||["yy",u];return d[2]=e,d[3]=+t>0,d[4]=n,ei.apply(null,d)}(this,!t,e);return t&&(n=e.pastFuture(+this,n)),e.postformat(n)},ri.toISOString=ai,ri.toString=ai,ri.toJSON=ai,ri.locale=rn,ri.localeData=sn,ri.toIsoString=D("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ai),ri.lang=on,j("X",0,0,"unix"),j("x",0,0,"valueOf"),dt("x",rt),dt("X",/[+-]?\d+(\.\d{1,3})?/),gt("X",(function(t,e,n){n._d=new Date(1e3*parseFloat(t,10))})),gt("x",(function(t,e,n){n._d=new Date(k(t))})),a.version="2.24.0",n=Le,a.fn=Mn,a.min=function(){return We("isBefore",[].slice.call(arguments,0))},a.max=function(){return We("isAfter",[].slice.call(arguments,0))},a.now=function(){return Date.now?Date.now():+new Date},a.utc=f,a.unix=function(t){return Le(1e3*t)},a.months=function(t,e){return Pn(t,e,"months")},a.isDate=u,a.locale=ge,a.invalid=p,a.duration=Xe,a.isMoment=_,a.weekdays=function(t,e,n){return Tn(t,e,n,"weekdays")},a.parseZone=function(){return Le.apply(null,arguments).parseZone()},a.localeData=pe,a.isDuration=Ee,a.monthsShort=function(t,e){return Pn(t,e,"monthsShort")},a.weekdaysMin=function(t,e,n){return Tn(t,e,n,"weekdaysMin")},a.defineLocale=me,a.updateLocale=function(t,e){if(null!=e){var n,i,a=ue;null!=(i=fe(t))&&(a=i._config),e=A(a,e),(n=new F(e)).parentLocale=de[t],de[t]=n,ge(t)}else null!=de[t]&&(null!=de[t].parentLocale?de[t]=de[t].parentLocale:null!=de[t]&&delete de[t]);return de[t]},a.locales=function(){return C(de)},a.weekdaysShort=function(t,e,n){return Tn(t,e,n,"weekdaysShort")},a.normalizeUnits=R,a.relativeTimeRounding=function(t){return void 0===t?Qn:"function"==typeof t&&(Qn=t,!0)},a.relativeTimeThreshold=function(t,e){return void 0!==ti[t]&&(void 0===e?ti[t]:(ti[t]=e,"s"===t&&(ti.ss=e-1),!0))},a.calendarFormat=function(t,e){var n=t.diff(e,"days",!0);return n<-6?"sameElse":n<-1?"lastWeek":n<0?"lastDay":n<1?"sameDay":n<2?"nextDay":n<7?"nextWeek":"sameElse"},a.prototype=Mn,a.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},a}()})),mi={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};rn._date.override("function"==typeof gi?{_id:"moment",formats:function(){return mi},parse:function(t,e){return"string"==typeof t&&"string"==typeof e?t=gi(t,e):t instanceof gi||(t=gi(t)),t.isValid()?t.valueOf():null},format:function(t,e){return gi(t).format(e)},add:function(t,e,n){return gi(t).add(e,n).valueOf()},diff:function(t,e,n){return gi(t).diff(gi(e),n)},startOf:function(t,e,n){return t=gi(t),"isoWeek"===e?t.isoWeekday(n).valueOf():t.startOf(e).valueOf()},endOf:function(t,e){return gi(t).endOf(e).valueOf()},_create:function(t){return gi(t)}}:{}),W._set("global",{plugins:{filler:{propagate:!0}}});var pi={dataset:function(t){var e=t.fill,n=t.chart,i=n.getDatasetMeta(e),a=i&&n.isDatasetVisible(e)&&i.dataset._children||[],r=a.length||0;return r?function(t,e){return e<r&&a[e]._view||null}:null},boundary:function(t){var e=t.boundary,n=e?e.x:null,i=e?e.y:null;return H.isArray(e)?function(t,n){return e[n]}:function(t){return{x:null===n?t.x:n,y:null===i?t.y:i}}}};function vi(t,e,n){var i,a=t._model||{},r=a.fill;if(void 0===r&&(r=!!a.backgroundColor),!1===r||null===r)return!1;if(!0===r)return"origin";if(i=parseFloat(r,10),isFinite(i)&&Math.floor(i)===i)return"-"!==r[0]&&"+"!==r[0]||(i=e+i),!(i===e||i<0||i>=n)&&i;switch(r){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return r;default:return!1}}function bi(t){return(t.el._scale||{}).getPointPositionForValue?function(t){var e,n,i,a,r,o=t.el._scale,s=o.options,l=o.chart.data.labels.length,u=t.fill,d=[];if(!l)return null;for(e=s.ticks.reverse?o.max:o.min,n=s.ticks.reverse?o.min:o.max,i=o.getPointPositionForValue(0,e),a=0;a<l;++a)r="start"===u||"end"===u?o.getPointPositionForValue(a,"start"===u?e:n):o.getBasePosition(a),s.gridLines.circular&&(r.cx=i.x,r.cy=i.y,r.angle=o.getIndexAngle(a)-Math.PI/2),d.push(r);return d}(t):function(t){var e,n=t.el._model||{},i=t.el._scale||{},a=t.fill,r=null;if(isFinite(a))return null;if("start"===a?r=void 0===n.scaleBottom?i.bottom:n.scaleBottom:"end"===a?r=void 0===n.scaleTop?i.top:n.scaleTop:void 0!==n.scaleZero?r=n.scaleZero:i.getBasePixel&&(r=i.getBasePixel()),null!=r){if(void 0!==r.x&&void 0!==r.y)return r;if(H.isFinite(r))return{x:(e=i.isHorizontal())?r:null,y:e?null:r}}return null}(t)}function yi(t,e,n){var i,a=t[e].fill,r=[e];if(!n)return a;for(;!1!==a&&-1===r.indexOf(a);){if(!isFinite(a))return a;if(!(i=t[a]))return!1;if(i.visible)return a;r.push(a),a=i.fill}return!1}function xi(t){var e=t.fill,n="dataset";return!1===e?null:(isFinite(e)||(n="boundary"),pi[n](t))}function _i(t){return t&&!t.skip}function wi(t,e,n,i,a){var r,o,s,l;if(i&&a){for(t.moveTo(e[0].x,e[0].y),r=1;r<i;++r)H.canvas.lineTo(t,e[r-1],e[r]);if(void 0===n[0].angle)for(t.lineTo(n[a-1].x,n[a-1].y),r=a-1;r>0;--r)H.canvas.lineTo(t,n[r],n[r-1],!0);else for(o=n[0].cx,s=n[0].cy,l=Math.sqrt(Math.pow(n[0].x-o,2)+Math.pow(n[0].y-s,2)),r=a-1;r>0;--r)t.arc(o,s,l,n[r].angle,n[r-1].angle,!0)}}function ki(t,e,n,i,a,r){var o,s,l,u,d,h,c,f,g=e.length,m=i.spanGaps,p=[],v=[],b=0,y=0;for(t.beginPath(),o=0,s=g;o<s;++o)d=n(u=e[l=o%g]._view,l,i),h=_i(u),c=_i(d),r&&void 0===f&&h&&(s=g+(f=o+1)),h&&c?(b=p.push(u),y=v.push(d)):b&&y&&(m?(h&&p.push(u),c&&v.push(d)):(wi(t,p,v,b,y),b=y=0,p=[],v=[]));wi(t,p,v,b,y),t.closePath(),t.fillStyle=a,t.fill()}var Mi={id:"filler",afterDatasetsUpdate:function(t,e){var n,i,a,r,o=(t.data.datasets||[]).length,s=e.propagate,l=[];for(i=0;i<o;++i)r=null,(a=(n=t.getDatasetMeta(i)).dataset)&&a._model&&a instanceof wt.Line&&(r={visible:t.isDatasetVisible(i),fill:vi(a,i,o),chart:t,el:a}),n.$filler=r,l.push(r);for(i=0;i<o;++i)(r=l[i])&&(r.fill=yi(l,i,s),r.boundary=bi(r),r.mapper=xi(r))},beforeDatasetsDraw:function(t){var e,n,i,a,r,o,s,l=t._getSortedVisibleDatasetMetas(),u=t.ctx;for(n=l.length-1;n>=0;--n)(e=l[n].$filler)&&e.visible&&(a=(i=e.el)._view,r=i._children||[],o=e.mapper,s=a.backgroundColor||W.global.defaultColor,o&&s&&r.length&&(H.canvas.clipArea(u,t.chartArea),ki(u,r,o,a,s,i._loop),H.canvas.unclipArea(u)))}},Si=H.rtl.getRtlAdapter,Di=H.noop,Ci=H.valueOrDefault;function Pi(t,e){return t.usePointStyle&&t.boxWidth>e?e:t.boxWidth}W._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var n=e.datasetIndex,i=this.chart,a=i.getDatasetMeta(n);a.hidden=null===a.hidden?!i.data.datasets[n].hidden:null,i.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data.datasets,n=t.options.legend||{},i=n.labels&&n.labels.usePointStyle;return t._getSortedDatasetMetas().map((function(n){var a=n.controller.getStyle(i?0:void 0);return{text:e[n.index].label,fillStyle:a.backgroundColor,hidden:!t.isDatasetVisible(n.index),lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:a.borderWidth,strokeStyle:a.borderColor,pointStyle:a.pointStyle,rotation:a.rotation,datasetIndex:n.index}}),this)}}},legendCallback:function(t){var e,n,i,a=document.createElement("ul"),r=t.data.datasets;for(a.setAttribute("class",t.id+"-legend"),e=0,n=r.length;e<n;e++)(i=a.appendChild(document.createElement("li"))).appendChild(document.createElement("span")).style.backgroundColor=r[e].backgroundColor,r[e].label&&i.appendChild(document.createTextNode(r[e].label));return a.outerHTML}});var Ti=$.extend({initialize:function(t){H.extend(this,t),this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1},beforeUpdate:Di,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Di,beforeSetDimensions:Di,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Di,beforeBuildLabels:Di,buildLabels:function(){var t=this,e=t.options.labels||{},n=H.callback(e.generateLabels,[t.chart],t)||[];e.filter&&(n=n.filter((function(n){return e.filter(n,t.chart.data)}))),t.options.reverse&&n.reverse(),t.legendItems=n},afterBuildLabels:Di,beforeFit:Di,fit:function(){var t=this,e=t.options,n=e.labels,i=e.display,a=t.ctx,r=H.options._parseFont(n),o=r.size,s=t.legendHitBoxes=[],l=t.minSize,u=t.isHorizontal();if(u?(l.width=t.maxWidth,l.height=i?10:0):(l.width=i?10:0,l.height=t.maxHeight),i){if(a.font=r.string,u){var d=t.lineWidths=[0],h=0;a.textAlign="left",a.textBaseline="middle",H.each(t.legendItems,(function(t,e){var i=Pi(n,o)+o/2+a.measureText(t.text).width;(0===e||d[d.length-1]+i+2*n.padding>l.width)&&(h+=o+n.padding,d[d.length-(e>0?0:1)]=0),s[e]={left:0,top:0,width:i,height:o},d[d.length-1]+=i+n.padding})),l.height+=h}else{var c=n.padding,f=t.columnWidths=[],g=t.columnHeights=[],m=n.padding,p=0,v=0;H.each(t.legendItems,(function(t,e){var i=Pi(n,o)+o/2+a.measureText(t.text).width;e>0&&v+o+2*c>l.height&&(m+=p+n.padding,f.push(p),g.push(v),p=0,v=0),p=Math.max(p,i),v+=o+c,s[e]={left:0,top:0,width:i,height:o}})),m+=p,f.push(p),g.push(v),l.width+=m}t.width=l.width,t.height=l.height}else t.width=l.width=t.height=l.height=0},afterFit:Di,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,n=e.labels,i=W.global,a=i.defaultColor,r=i.elements.line,o=t.height,s=t.columnHeights,l=t.width,u=t.lineWidths;if(e.display){var d,h=Si(e.rtl,t.left,t.minSize.width),c=t.ctx,f=Ci(n.fontColor,i.defaultFontColor),g=H.options._parseFont(n),m=g.size;c.textAlign=h.textAlign("left"),c.textBaseline="middle",c.lineWidth=.5,c.strokeStyle=f,c.fillStyle=f,c.font=g.string;var p=Pi(n,m),v=t.legendHitBoxes,b=function(t,i){switch(e.align){case"start":return n.padding;case"end":return t-i;default:return(t-i+n.padding)/2}},y=t.isHorizontal();d=y?{x:t.left+b(l,u[0]),y:t.top+n.padding,line:0}:{x:t.left+n.padding,y:t.top+b(o,s[0]),line:0},H.rtl.overrideTextDirection(t.ctx,e.textDirection);var x=m+n.padding;H.each(t.legendItems,(function(e,i){var f=c.measureText(e.text).width,g=p+m/2+f,_=d.x,w=d.y;h.setWidth(t.minSize.width),y?i>0&&_+g+n.padding>t.left+t.minSize.width&&(w=d.y+=x,d.line++,_=d.x=t.left+b(l,u[d.line])):i>0&&w+x>t.top+t.minSize.height&&(_=d.x=_+t.columnWidths[d.line]+n.padding,d.line++,w=d.y=t.top+b(o,s[d.line]));var k=h.x(_);!function(t,e,i){if(!(isNaN(p)||p<=0)){c.save();var o=Ci(i.lineWidth,r.borderWidth);if(c.fillStyle=Ci(i.fillStyle,a),c.lineCap=Ci(i.lineCap,r.borderCapStyle),c.lineDashOffset=Ci(i.lineDashOffset,r.borderDashOffset),c.lineJoin=Ci(i.lineJoin,r.borderJoinStyle),c.lineWidth=o,c.strokeStyle=Ci(i.strokeStyle,a),c.setLineDash&&c.setLineDash(Ci(i.lineDash,r.borderDash)),n&&n.usePointStyle){var s=p*Math.SQRT2/2,l=h.xPlus(t,p/2),u=e+m/2;H.canvas.drawPoint(c,i.pointStyle,s,l,u,i.rotation)}else c.fillRect(h.leftForLtr(t,p),e,p,m),0!==o&&c.strokeRect(h.leftForLtr(t,p),e,p,m);c.restore()}}(k,w,e),v[i].left=h.leftForLtr(k,v[i].width),v[i].top=w,function(t,e,n,i){var a=m/2,r=h.xPlus(t,p+a),o=e+a;c.fillText(n.text,r,o),n.hidden&&(c.beginPath(),c.lineWidth=2,c.moveTo(r,o),c.lineTo(h.xPlus(r,i),o),c.stroke())}(k,w,e,f),y?d.x+=g+n.padding:d.y+=x})),H.rtl.restoreTextDirection(t.ctx,e.textDirection)}},_getLegendItemAt:function(t,e){var n,i,a,r=this;if(t>=r.left&&t<=r.right&&e>=r.top&&e<=r.bottom)for(a=r.legendHitBoxes,n=0;n<a.length;++n)if(t>=(i=a[n]).left&&t<=i.left+i.width&&e>=i.top&&e<=i.top+i.height)return r.legendItems[n];return null},handleEvent:function(t){var e,n=this,i=n.options,a="mouseup"===t.type?"click":t.type;if("mousemove"===a){if(!i.onHover&&!i.onLeave)return}else{if("click"!==a)return;if(!i.onClick)return}e=n._getLegendItemAt(t.x,t.y),"click"===a?e&&i.onClick&&i.onClick.call(n,t.native,e):(i.onLeave&&e!==n._hoveredItem&&(n._hoveredItem&&i.onLeave.call(n,t.native,n._hoveredItem),n._hoveredItem=e),i.onHover&&e&&i.onHover.call(n,t.native,e))}});function Oi(t,e){var n=new Ti({ctx:t.ctx,options:e,chart:t});me.configure(t,n,e),me.addBox(t,n),t.legend=n}var Ai={id:"legend",_element:Ti,beforeInit:function(t){var e=t.options.legend;e&&Oi(t,e)},beforeUpdate:function(t){var e=t.options.legend,n=t.legend;e?(H.mergeIf(e,W.global.legend),n?(me.configure(t,n,e),n.options=e):Oi(t,e)):n&&(me.removeBox(t,n),delete t.legend)},afterEvent:function(t,e){var n=t.legend;n&&n.handleEvent(e)}},Fi=H.noop;W._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Ii=$.extend({initialize:function(t){H.extend(this,t),this.legendHitBoxes=[]},beforeUpdate:Fi,update:function(t,e,n){var i=this;return i.beforeUpdate(),i.maxWidth=t,i.maxHeight=e,i.margins=n,i.beforeSetDimensions(),i.setDimensions(),i.afterSetDimensions(),i.beforeBuildLabels(),i.buildLabels(),i.afterBuildLabels(),i.beforeFit(),i.fit(),i.afterFit(),i.afterUpdate(),i.minSize},afterUpdate:Fi,beforeSetDimensions:Fi,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Fi,beforeBuildLabels:Fi,buildLabels:Fi,afterBuildLabels:Fi,beforeFit:Fi,fit:function(){var t,e=this,n=e.options,i=e.minSize={},a=e.isHorizontal();n.display?(t=(H.isArray(n.text)?n.text.length:1)*H.options._parseFont(n).lineHeight+2*n.padding,e.width=i.width=a?e.maxWidth:t,e.height=i.height=a?t:e.maxHeight):e.width=i.width=e.height=i.height=0},afterFit:Fi,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,n=t.options;if(n.display){var i,a,r,o=H.options._parseFont(n),s=o.lineHeight,l=s/2+n.padding,u=0,d=t.top,h=t.left,c=t.bottom,f=t.right;e.fillStyle=H.valueOrDefault(n.fontColor,W.global.defaultFontColor),e.font=o.string,t.isHorizontal()?(a=h+(f-h)/2,r=d+l,i=f-h):(a="left"===n.position?h+l:f-l,r=d+(c-d)/2,i=c-d,u=Math.PI*("left"===n.position?-.5:.5)),e.save(),e.translate(a,r),e.rotate(u),e.textAlign="center",e.textBaseline="middle";var g=n.text;if(H.isArray(g))for(var m=0,p=0;p<g.length;++p)e.fillText(g[p],0,m,i),m+=s;else e.fillText(g,0,0,i);e.restore()}}});function Li(t,e){var n=new Ii({ctx:t.ctx,options:e,chart:t});me.configure(t,n,e),me.addBox(t,n),t.titleBlock=n}var Ri={},Ni=Mi,Wi=Ai,Yi={id:"title",_element:Ii,beforeInit:function(t){var e=t.options.title;e&&Li(t,e)},beforeUpdate:function(t){var e=t.options.title,n=t.titleBlock;e?(H.mergeIf(e,W.global.title),n?(me.configure(t,n,e),n.options=e):Li(t,e)):n&&(me.removeBox(t,n),delete t.titleBlock)}};for(var zi in Ri.filler=Ni,Ri.legend=Wi,Ri.title=Yi,en.helpers=H,function(){function t(t,e,n){var i;return"string"==typeof t?(i=parseInt(t,10),-1!==t.indexOf("%")&&(i=i/100*e.parentNode[n])):i=t,i}function e(t){return null!=t&&"none"!==t}function n(n,i,a){var r=document.defaultView,o=H._getParentNode(n),s=r.getComputedStyle(n)[i],l=r.getComputedStyle(o)[i],u=e(s),d=e(l),h=Number.POSITIVE_INFINITY;return u||d?Math.min(u?t(s,n,a):h,d?t(l,o,a):h):"none"}H.where=function(t,e){if(H.isArray(t)&&Array.prototype.filter)return t.filter(e);var n=[];return H.each(t,(function(t){e(t)&&n.push(t)})),n},H.findIndex=Array.prototype.findIndex?function(t,e,n){return t.findIndex(e,n)}:function(t,e,n){n=void 0===n?t:n;for(var i=0,a=t.length;i<a;++i)if(e.call(n,t[i],i,t))return i;return-1},H.findNextWhere=function(t,e,n){H.isNullOrUndef(n)&&(n=-1);for(var i=n+1;i<t.length;i++){var a=t[i];if(e(a))return a}},H.findPreviousWhere=function(t,e,n){H.isNullOrUndef(n)&&(n=t.length);for(var i=n-1;i>=0;i--){var a=t[i];if(e(a))return a}},H.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},H.almostEquals=function(t,e,n){return Math.abs(t-e)<n},H.almostWhole=function(t,e){var n=Math.round(t);return n-e<=t&&n+e>=t},H.max=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.max(t,e)}),Number.NEGATIVE_INFINITY)},H.min=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.min(t,e)}),Number.POSITIVE_INFINITY)},H.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0===(t=+t)||isNaN(t)?t:t>0?1:-1},H.toRadians=function(t){return t*(Math.PI/180)},H.toDegrees=function(t){return t*(180/Math.PI)},H._decimalPlaces=function(t){if(H.isFinite(t)){for(var e=1,n=0;Math.round(t*e)/e!==t;)e*=10,n++;return n}},H.getAngleFromPoint=function(t,e){var n=e.x-t.x,i=e.y-t.y,a=Math.sqrt(n*n+i*i),r=Math.atan2(i,n);return r<-.5*Math.PI&&(r+=2*Math.PI),{angle:r,distance:a}},H.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},H.aliasPixel=function(t){return t%2==0?0:.5},H._alignPixel=function(t,e,n){var i=t.currentDevicePixelRatio,a=n/2;return Math.round((e-a)*i)/i+a},H.splineCurve=function(t,e,n,i){var a=t.skip?e:t,r=e,o=n.skip?e:n,s=Math.sqrt(Math.pow(r.x-a.x,2)+Math.pow(r.y-a.y,2)),l=Math.sqrt(Math.pow(o.x-r.x,2)+Math.pow(o.y-r.y,2)),u=s/(s+l),d=l/(s+l),h=i*(u=isNaN(u)?0:u),c=i*(d=isNaN(d)?0:d);return{previous:{x:r.x-h*(o.x-a.x),y:r.y-h*(o.y-a.y)},next:{x:r.x+c*(o.x-a.x),y:r.y+c*(o.y-a.y)}}},H.EPSILON=Number.EPSILON||1e-14,H.splineCurveMonotone=function(t){var e,n,i,a,r,o,s,l,u,d=(t||[]).map((function(t){return{model:t._model,deltaK:0,mK:0}})),h=d.length;for(e=0;e<h;++e)if(!(i=d[e]).model.skip){if(n=e>0?d[e-1]:null,(a=e<h-1?d[e+1]:null)&&!a.model.skip){var c=a.model.x-i.model.x;i.deltaK=0!==c?(a.model.y-i.model.y)/c:0}!n||n.model.skip?i.mK=i.deltaK:!a||a.model.skip?i.mK=n.deltaK:this.sign(n.deltaK)!==this.sign(i.deltaK)?i.mK=0:i.mK=(n.deltaK+i.deltaK)/2}for(e=0;e<h-1;++e)i=d[e],a=d[e+1],i.model.skip||a.model.skip||(H.almostEquals(i.deltaK,0,this.EPSILON)?i.mK=a.mK=0:(r=i.mK/i.deltaK,o=a.mK/i.deltaK,(l=Math.pow(r,2)+Math.pow(o,2))<=9||(s=3/Math.sqrt(l),i.mK=r*s*i.deltaK,a.mK=o*s*i.deltaK)));for(e=0;e<h;++e)(i=d[e]).model.skip||(n=e>0?d[e-1]:null,a=e<h-1?d[e+1]:null,n&&!n.model.skip&&(u=(i.model.x-n.model.x)/3,i.model.controlPointPreviousX=i.model.x-u,i.model.controlPointPreviousY=i.model.y-u*i.mK),a&&!a.model.skip&&(u=(a.model.x-i.model.x)/3,i.model.controlPointNextX=i.model.x+u,i.model.controlPointNextY=i.model.y+u*i.mK))},H.nextItem=function(t,e,n){return n?e>=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},H.previousItem=function(t,e,n){return n?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},H.niceNum=function(t,e){var n=Math.floor(H.log10(t)),i=t/Math.pow(10,n);return(e?i<1.5?1:i<3?2:i<7?5:10:i<=1?1:i<=2?2:i<=5?5:10)*Math.pow(10,n)},H.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},H.getRelativePosition=function(t,e){var n,i,a=t.originalEvent||t,r=t.target||t.srcElement,o=r.getBoundingClientRect(),s=a.touches;s&&s.length>0?(n=s[0].clientX,i=s[0].clientY):(n=a.clientX,i=a.clientY);var l=parseFloat(H.getStyle(r,"padding-left")),u=parseFloat(H.getStyle(r,"padding-top")),d=parseFloat(H.getStyle(r,"padding-right")),h=parseFloat(H.getStyle(r,"padding-bottom")),c=o.right-o.left-l-d,f=o.bottom-o.top-u-h;return{x:n=Math.round((n-o.left-l)/c*r.width/e.currentDevicePixelRatio),y:i=Math.round((i-o.top-u)/f*r.height/e.currentDevicePixelRatio)}},H.getConstraintWidth=function(t){return n(t,"max-width","clientWidth")},H.getConstraintHeight=function(t){return n(t,"max-height","clientHeight")},H._calculatePadding=function(t,e,n){return(e=H.getStyle(t,e)).indexOf("%")>-1?n*parseInt(e,10)/100:parseInt(e,10)},H._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},H.getMaximumWidth=function(t){var e=H._getParentNode(t);if(!e)return t.clientWidth;var n=e.clientWidth,i=n-H._calculatePadding(e,"padding-left",n)-H._calculatePadding(e,"padding-right",n),a=H.getConstraintWidth(t);return isNaN(a)?i:Math.min(i,a)},H.getMaximumHeight=function(t){var e=H._getParentNode(t);if(!e)return t.clientHeight;var n=e.clientHeight,i=n-H._calculatePadding(e,"padding-top",n)-H._calculatePadding(e,"padding-bottom",n),a=H.getConstraintHeight(t);return isNaN(a)?i:Math.min(i,a)},H.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},H.retinaScale=function(t,e){var n=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==n){var i=t.canvas,a=t.height,r=t.width;i.height=a*n,i.width=r*n,t.ctx.scale(n,n),i.style.height||i.style.width||(i.style.height=a+"px",i.style.width=r+"px")}},H.fontString=function(t,e,n){return e+" "+t+"px "+n},H.longestText=function(t,e,n,i){var a=(i=i||{}).data=i.data||{},r=i.garbageCollect=i.garbageCollect||[];i.font!==e&&(a=i.data={},r=i.garbageCollect=[],i.font=e),t.font=e;var o,s,l,u,d,h=0,c=n.length;for(o=0;o<c;o++)if(null!=(u=n[o])&&!0!==H.isArray(u))h=H.measureText(t,a,r,h,u);else if(H.isArray(u))for(s=0,l=u.length;s<l;s++)null==(d=u[s])||H.isArray(d)||(h=H.measureText(t,a,r,h,d));var f=r.length/2;if(f>n.length){for(o=0;o<f;o++)delete a[r[o]];r.splice(0,f)}return h},H.measureText=function(t,e,n,i,a){var r=e[a];return r||(r=e[a]=t.measureText(a).width,n.push(a)),r>i&&(i=r),i},H.numberOfLabelLines=function(t){var e=1;return H.each(t,(function(t){H.isArray(t)&&t.length>e&&(e=t.length)})),e},H.color=k?function(t){return t instanceof CanvasGradient&&(t=W.global.defaultColor),k(t)}:function(t){return console.error("Color.js not found!"),t},H.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:H.color(t).saturate(.5).darken(.1).rgbString()}}(),en._adapters=rn,en.Animation=K,en.animationService=J,en.controllers=Jt,en.DatasetController=it,en.defaults=W,en.Element=$,en.elements=wt,en.Interaction=re,en.layouts=me,en.platform=Ie,en.plugins=Le,en.Scale=xn,en.scaleService=Re,en.Ticks=on,en.Tooltip=Ge,en.helpers.each(fi,(function(t,e){en.scaleService.registerScaleType(e,t,t._defaults)})),Ri)Ri.hasOwnProperty(zi)&&en.plugins.register(Ri[zi]);en.platform.initialize();var Ei=en;return"undefined"!=typeof window&&(window.Chart=en),en.Chart=en,en.Legend=Ri.legend._element,en.Title=Ri.title._element,en.pluginService=en.plugins,en.PluginBase=en.Element.extend({}),en.canvasHelpers=en.helpers.canvas,en.layoutService=en.layouts,en.LinearScaleBase=Dn,en.helpers.each(["Bar","Bubble","Doughnut","Line","PolarArea","Radar","Scatter"],(function(t){en[t]=function(e,n){return new en(e,en.helpers.merge(n||{},{type:t.charAt(0).toLowerCase()+t.slice(1)}))}})),Ei})); diff --git a/lib/web/chartjs/Chart.css b/lib/web/chartjs/Chart.css deleted file mode 100644 index 5e749593eeb65..0000000000000 --- a/lib/web/chartjs/Chart.css +++ /dev/null @@ -1,47 +0,0 @@ -/* - * DOM element rendering detection - * https://davidwalsh.name/detect-node-insertion - */ -@keyframes chartjs-render-animation { - from { opacity: 0.99; } - to { opacity: 1; } -} - -.chartjs-render-monitor { - animation: chartjs-render-animation 0.001s; -} - -/* - * DOM element resizing detection - * https://github.com/marcj/css-element-queries - */ -.chartjs-size-monitor, -.chartjs-size-monitor-expand, -.chartjs-size-monitor-shrink { - position: absolute; - direction: ltr; - left: 0; - top: 0; - right: 0; - bottom: 0; - overflow: hidden; - pointer-events: none; - visibility: hidden; - z-index: -1; -} - -.chartjs-size-monitor-expand > div { - position: absolute; - width: 1000000px; - height: 1000000px; - left: 0; - top: 0; -} - -.chartjs-size-monitor-shrink > div { - position: absolute; - width: 200%; - height: 200%; - left: 0; - top: 0; -} diff --git a/lib/web/chartjs/Chart.js b/lib/web/chartjs/Chart.js deleted file mode 100644 index e8d937cf82c54..0000000000000 --- a/lib/web/chartjs/Chart.js +++ /dev/null @@ -1,16151 +0,0 @@ -/*! - * Chart.js v2.9.3 - * https://www.chartjs.org - * (c) 2019 Chart.js Contributors - * Released under the MIT License - */ -(function (global, factory) { -typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(function() { try { return require('moment'); } catch(e) { } }()) : -typeof define === 'function' && define.amd ? define(['require'], function(require) { return factory(function() { try { return require('moment'); } catch(e) { } }()); }) : -(global = global || self, global.Chart = factory(global.moment)); -}(this, (function (moment) { 'use strict'; - -moment = moment && moment.hasOwnProperty('default') ? moment['default'] : moment; - -function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; -} - -function getCjsExportFromNamespace (n) { - return n && n['default'] || n; -} - -var colorName = { - "aliceblue": [240, 248, 255], - "antiquewhite": [250, 235, 215], - "aqua": [0, 255, 255], - "aquamarine": [127, 255, 212], - "azure": [240, 255, 255], - "beige": [245, 245, 220], - "bisque": [255, 228, 196], - "black": [0, 0, 0], - "blanchedalmond": [255, 235, 205], - "blue": [0, 0, 255], - "blueviolet": [138, 43, 226], - "brown": [165, 42, 42], - "burlywood": [222, 184, 135], - "cadetblue": [95, 158, 160], - "chartreuse": [127, 255, 0], - "chocolate": [210, 105, 30], - "coral": [255, 127, 80], - "cornflowerblue": [100, 149, 237], - "cornsilk": [255, 248, 220], - "crimson": [220, 20, 60], - "cyan": [0, 255, 255], - "darkblue": [0, 0, 139], - "darkcyan": [0, 139, 139], - "darkgoldenrod": [184, 134, 11], - "darkgray": [169, 169, 169], - "darkgreen": [0, 100, 0], - "darkgrey": [169, 169, 169], - "darkkhaki": [189, 183, 107], - "darkmagenta": [139, 0, 139], - "darkolivegreen": [85, 107, 47], - "darkorange": [255, 140, 0], - "darkorchid": [153, 50, 204], - "darkred": [139, 0, 0], - "darksalmon": [233, 150, 122], - "darkseagreen": [143, 188, 143], - "darkslateblue": [72, 61, 139], - "darkslategray": [47, 79, 79], - "darkslategrey": [47, 79, 79], - "darkturquoise": [0, 206, 209], - "darkviolet": [148, 0, 211], - "deeppink": [255, 20, 147], - "deepskyblue": [0, 191, 255], - "dimgray": [105, 105, 105], - "dimgrey": [105, 105, 105], - "dodgerblue": [30, 144, 255], - "firebrick": [178, 34, 34], - "floralwhite": [255, 250, 240], - "forestgreen": [34, 139, 34], - "fuchsia": [255, 0, 255], - "gainsboro": [220, 220, 220], - "ghostwhite": [248, 248, 255], - "gold": [255, 215, 0], - "goldenrod": [218, 165, 32], - "gray": [128, 128, 128], - "green": [0, 128, 0], - "greenyellow": [173, 255, 47], - "grey": [128, 128, 128], - "honeydew": [240, 255, 240], - "hotpink": [255, 105, 180], - "indianred": [205, 92, 92], - "indigo": [75, 0, 130], - "ivory": [255, 255, 240], - "khaki": [240, 230, 140], - "lavender": [230, 230, 250], - "lavenderblush": [255, 240, 245], - "lawngreen": [124, 252, 0], - "lemonchiffon": [255, 250, 205], - "lightblue": [173, 216, 230], - "lightcoral": [240, 128, 128], - "lightcyan": [224, 255, 255], - "lightgoldenrodyellow": [250, 250, 210], - "lightgray": [211, 211, 211], - "lightgreen": [144, 238, 144], - "lightgrey": [211, 211, 211], - "lightpink": [255, 182, 193], - "lightsalmon": [255, 160, 122], - "lightseagreen": [32, 178, 170], - "lightskyblue": [135, 206, 250], - "lightslategray": [119, 136, 153], - "lightslategrey": [119, 136, 153], - "lightsteelblue": [176, 196, 222], - "lightyellow": [255, 255, 224], - "lime": [0, 255, 0], - "limegreen": [50, 205, 50], - "linen": [250, 240, 230], - "magenta": [255, 0, 255], - "maroon": [128, 0, 0], - "mediumaquamarine": [102, 205, 170], - "mediumblue": [0, 0, 205], - "mediumorchid": [186, 85, 211], - "mediumpurple": [147, 112, 219], - "mediumseagreen": [60, 179, 113], - "mediumslateblue": [123, 104, 238], - "mediumspringgreen": [0, 250, 154], - "mediumturquoise": [72, 209, 204], - "mediumvioletred": [199, 21, 133], - "midnightblue": [25, 25, 112], - "mintcream": [245, 255, 250], - "mistyrose": [255, 228, 225], - "moccasin": [255, 228, 181], - "navajowhite": [255, 222, 173], - "navy": [0, 0, 128], - "oldlace": [253, 245, 230], - "olive": [128, 128, 0], - "olivedrab": [107, 142, 35], - "orange": [255, 165, 0], - "orangered": [255, 69, 0], - "orchid": [218, 112, 214], - "palegoldenrod": [238, 232, 170], - "palegreen": [152, 251, 152], - "paleturquoise": [175, 238, 238], - "palevioletred": [219, 112, 147], - "papayawhip": [255, 239, 213], - "peachpuff": [255, 218, 185], - "peru": [205, 133, 63], - "pink": [255, 192, 203], - "plum": [221, 160, 221], - "powderblue": [176, 224, 230], - "purple": [128, 0, 128], - "rebeccapurple": [102, 51, 153], - "red": [255, 0, 0], - "rosybrown": [188, 143, 143], - "royalblue": [65, 105, 225], - "saddlebrown": [139, 69, 19], - "salmon": [250, 128, 114], - "sandybrown": [244, 164, 96], - "seagreen": [46, 139, 87], - "seashell": [255, 245, 238], - "sienna": [160, 82, 45], - "silver": [192, 192, 192], - "skyblue": [135, 206, 235], - "slateblue": [106, 90, 205], - "slategray": [112, 128, 144], - "slategrey": [112, 128, 144], - "snow": [255, 250, 250], - "springgreen": [0, 255, 127], - "steelblue": [70, 130, 180], - "tan": [210, 180, 140], - "teal": [0, 128, 128], - "thistle": [216, 191, 216], - "tomato": [255, 99, 71], - "turquoise": [64, 224, 208], - "violet": [238, 130, 238], - "wheat": [245, 222, 179], - "white": [255, 255, 255], - "whitesmoke": [245, 245, 245], - "yellow": [255, 255, 0], - "yellowgreen": [154, 205, 50] -}; - -var conversions = createCommonjsModule(function (module) { -/* MIT license */ - - -// NOTE: conversions should only return primitive values (i.e. arrays, or -// values that give correct `typeof` results). -// do not use box values types (i.e. Number(), String(), etc.) - -var reverseKeywords = {}; -for (var key in colorName) { - if (colorName.hasOwnProperty(key)) { - reverseKeywords[colorName[key]] = key; - } -} - -var convert = module.exports = { - rgb: {channels: 3, labels: 'rgb'}, - hsl: {channels: 3, labels: 'hsl'}, - hsv: {channels: 3, labels: 'hsv'}, - hwb: {channels: 3, labels: 'hwb'}, - cmyk: {channels: 4, labels: 'cmyk'}, - xyz: {channels: 3, labels: 'xyz'}, - lab: {channels: 3, labels: 'lab'}, - lch: {channels: 3, labels: 'lch'}, - hex: {channels: 1, labels: ['hex']}, - keyword: {channels: 1, labels: ['keyword']}, - ansi16: {channels: 1, labels: ['ansi16']}, - ansi256: {channels: 1, labels: ['ansi256']}, - hcg: {channels: 3, labels: ['h', 'c', 'g']}, - apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, - gray: {channels: 1, labels: ['gray']} -}; - -// hide .channels and .labels properties -for (var model in convert) { - if (convert.hasOwnProperty(model)) { - if (!('channels' in convert[model])) { - throw new Error('missing channels property: ' + model); - } - - if (!('labels' in convert[model])) { - throw new Error('missing channel labels property: ' + model); - } - - if (convert[model].labels.length !== convert[model].channels) { - throw new Error('channel and label counts mismatch: ' + model); - } - - var channels = convert[model].channels; - var labels = convert[model].labels; - delete convert[model].channels; - delete convert[model].labels; - Object.defineProperty(convert[model], 'channels', {value: channels}); - Object.defineProperty(convert[model], 'labels', {value: labels}); - } -} - -convert.rgb.hsl = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var min = Math.min(r, g, b); - var max = Math.max(r, g, b); - var delta = max - min; - var h; - var s; - var l; - - if (max === min) { - h = 0; - } else if (r === max) { - h = (g - b) / delta; - } else if (g === max) { - h = 2 + (b - r) / delta; - } else if (b === max) { - h = 4 + (r - g) / delta; - } - - h = Math.min(h * 60, 360); - - if (h < 0) { - h += 360; - } - - l = (min + max) / 2; - - if (max === min) { - s = 0; - } else if (l <= 0.5) { - s = delta / (max + min); - } else { - s = delta / (2 - max - min); - } - - return [h, s * 100, l * 100]; -}; - -convert.rgb.hsv = function (rgb) { - var rdif; - var gdif; - var bdif; - var h; - var s; - - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var v = Math.max(r, g, b); - var diff = v - Math.min(r, g, b); - var diffc = function (c) { - return (v - c) / 6 / diff + 1 / 2; - }; - - if (diff === 0) { - h = s = 0; - } else { - s = diff / v; - rdif = diffc(r); - gdif = diffc(g); - bdif = diffc(b); - - if (r === v) { - h = bdif - gdif; - } else if (g === v) { - h = (1 / 3) + rdif - bdif; - } else if (b === v) { - h = (2 / 3) + gdif - rdif; - } - if (h < 0) { - h += 1; - } else if (h > 1) { - h -= 1; - } - } - - return [ - h * 360, - s * 100, - v * 100 - ]; -}; - -convert.rgb.hwb = function (rgb) { - var r = rgb[0]; - var g = rgb[1]; - var b = rgb[2]; - var h = convert.rgb.hsl(rgb)[0]; - var w = 1 / 255 * Math.min(r, Math.min(g, b)); - - b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); - - return [h, w * 100, b * 100]; -}; - -convert.rgb.cmyk = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var c; - var m; - var y; - var k; - - k = Math.min(1 - r, 1 - g, 1 - b); - c = (1 - r - k) / (1 - k) || 0; - m = (1 - g - k) / (1 - k) || 0; - y = (1 - b - k) / (1 - k) || 0; - - return [c * 100, m * 100, y * 100, k * 100]; -}; - -/** - * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance - * */ -function comparativeDistance(x, y) { - return ( - Math.pow(x[0] - y[0], 2) + - Math.pow(x[1] - y[1], 2) + - Math.pow(x[2] - y[2], 2) - ); -} - -convert.rgb.keyword = function (rgb) { - var reversed = reverseKeywords[rgb]; - if (reversed) { - return reversed; - } - - var currentClosestDistance = Infinity; - var currentClosestKeyword; - - for (var keyword in colorName) { - if (colorName.hasOwnProperty(keyword)) { - var value = colorName[keyword]; - - // Compute comparative distance - var distance = comparativeDistance(rgb, value); - - // Check if its less, if so set as closest - if (distance < currentClosestDistance) { - currentClosestDistance = distance; - currentClosestKeyword = keyword; - } - } - } - - return currentClosestKeyword; -}; - -convert.keyword.rgb = function (keyword) { - return colorName[keyword]; -}; - -convert.rgb.xyz = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - - // assume sRGB - r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); - g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); - b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); - - var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); - var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); - var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); - - return [x * 100, y * 100, z * 100]; -}; - -convert.rgb.lab = function (rgb) { - var xyz = convert.rgb.xyz(rgb); - var x = xyz[0]; - var y = xyz[1]; - var z = xyz[2]; - var l; - var a; - var b; - - x /= 95.047; - y /= 100; - z /= 108.883; - - x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); - - l = (116 * y) - 16; - a = 500 * (x - y); - b = 200 * (y - z); - - return [l, a, b]; -}; - -convert.hsl.rgb = function (hsl) { - var h = hsl[0] / 360; - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var t1; - var t2; - var t3; - var rgb; - var val; - - if (s === 0) { - val = l * 255; - return [val, val, val]; - } - - if (l < 0.5) { - t2 = l * (1 + s); - } else { - t2 = l + s - l * s; - } - - t1 = 2 * l - t2; - - rgb = [0, 0, 0]; - for (var i = 0; i < 3; i++) { - t3 = h + 1 / 3 * -(i - 1); - if (t3 < 0) { - t3++; - } - if (t3 > 1) { - t3--; - } - - if (6 * t3 < 1) { - val = t1 + (t2 - t1) * 6 * t3; - } else if (2 * t3 < 1) { - val = t2; - } else if (3 * t3 < 2) { - val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; - } else { - val = t1; - } - - rgb[i] = val * 255; - } - - return rgb; -}; - -convert.hsl.hsv = function (hsl) { - var h = hsl[0]; - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var smin = s; - var lmin = Math.max(l, 0.01); - var sv; - var v; - - l *= 2; - s *= (l <= 1) ? l : 2 - l; - smin *= lmin <= 1 ? lmin : 2 - lmin; - v = (l + s) / 2; - sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); - - return [h, sv * 100, v * 100]; -}; - -convert.hsv.rgb = function (hsv) { - var h = hsv[0] / 60; - var s = hsv[1] / 100; - var v = hsv[2] / 100; - var hi = Math.floor(h) % 6; - - var f = h - Math.floor(h); - var p = 255 * v * (1 - s); - var q = 255 * v * (1 - (s * f)); - var t = 255 * v * (1 - (s * (1 - f))); - v *= 255; - - switch (hi) { - case 0: - return [v, t, p]; - case 1: - return [q, v, p]; - case 2: - return [p, v, t]; - case 3: - return [p, q, v]; - case 4: - return [t, p, v]; - case 5: - return [v, p, q]; - } -}; - -convert.hsv.hsl = function (hsv) { - var h = hsv[0]; - var s = hsv[1] / 100; - var v = hsv[2] / 100; - var vmin = Math.max(v, 0.01); - var lmin; - var sl; - var l; - - l = (2 - s) * v; - lmin = (2 - s) * vmin; - sl = s * vmin; - sl /= (lmin <= 1) ? lmin : 2 - lmin; - sl = sl || 0; - l /= 2; - - return [h, sl * 100, l * 100]; -}; - -// http://dev.w3.org/csswg/css-color/#hwb-to-rgb -convert.hwb.rgb = function (hwb) { - var h = hwb[0] / 360; - var wh = hwb[1] / 100; - var bl = hwb[2] / 100; - var ratio = wh + bl; - var i; - var v; - var f; - var n; - - // wh + bl cant be > 1 - if (ratio > 1) { - wh /= ratio; - bl /= ratio; - } - - i = Math.floor(6 * h); - v = 1 - bl; - f = 6 * h - i; - - if ((i & 0x01) !== 0) { - f = 1 - f; - } - - n = wh + f * (v - wh); // linear interpolation - - var r; - var g; - var b; - switch (i) { - default: - case 6: - case 0: r = v; g = n; b = wh; break; - case 1: r = n; g = v; b = wh; break; - case 2: r = wh; g = v; b = n; break; - case 3: r = wh; g = n; b = v; break; - case 4: r = n; g = wh; b = v; break; - case 5: r = v; g = wh; b = n; break; - } - - return [r * 255, g * 255, b * 255]; -}; - -convert.cmyk.rgb = function (cmyk) { - var c = cmyk[0] / 100; - var m = cmyk[1] / 100; - var y = cmyk[2] / 100; - var k = cmyk[3] / 100; - var r; - var g; - var b; - - r = 1 - Math.min(1, c * (1 - k) + k); - g = 1 - Math.min(1, m * (1 - k) + k); - b = 1 - Math.min(1, y * (1 - k) + k); - - return [r * 255, g * 255, b * 255]; -}; - -convert.xyz.rgb = function (xyz) { - var x = xyz[0] / 100; - var y = xyz[1] / 100; - var z = xyz[2] / 100; - var r; - var g; - var b; - - r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); - g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); - b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); - - // assume sRGB - r = r > 0.0031308 - ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) - : r * 12.92; - - g = g > 0.0031308 - ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) - : g * 12.92; - - b = b > 0.0031308 - ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) - : b * 12.92; - - r = Math.min(Math.max(0, r), 1); - g = Math.min(Math.max(0, g), 1); - b = Math.min(Math.max(0, b), 1); - - return [r * 255, g * 255, b * 255]; -}; - -convert.xyz.lab = function (xyz) { - var x = xyz[0]; - var y = xyz[1]; - var z = xyz[2]; - var l; - var a; - var b; - - x /= 95.047; - y /= 100; - z /= 108.883; - - x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); - - l = (116 * y) - 16; - a = 500 * (x - y); - b = 200 * (y - z); - - return [l, a, b]; -}; - -convert.lab.xyz = function (lab) { - var l = lab[0]; - var a = lab[1]; - var b = lab[2]; - var x; - var y; - var z; - - y = (l + 16) / 116; - x = a / 500 + y; - z = y - b / 200; - - var y2 = Math.pow(y, 3); - var x2 = Math.pow(x, 3); - var z2 = Math.pow(z, 3); - y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; - x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; - z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; - - x *= 95.047; - y *= 100; - z *= 108.883; - - return [x, y, z]; -}; - -convert.lab.lch = function (lab) { - var l = lab[0]; - var a = lab[1]; - var b = lab[2]; - var hr; - var h; - var c; - - hr = Math.atan2(b, a); - h = hr * 360 / 2 / Math.PI; - - if (h < 0) { - h += 360; - } - - c = Math.sqrt(a * a + b * b); - - return [l, c, h]; -}; - -convert.lch.lab = function (lch) { - var l = lch[0]; - var c = lch[1]; - var h = lch[2]; - var a; - var b; - var hr; - - hr = h / 360 * 2 * Math.PI; - a = c * Math.cos(hr); - b = c * Math.sin(hr); - - return [l, a, b]; -}; - -convert.rgb.ansi16 = function (args) { - var r = args[0]; - var g = args[1]; - var b = args[2]; - var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization - - value = Math.round(value / 50); - - if (value === 0) { - return 30; - } - - var ansi = 30 - + ((Math.round(b / 255) << 2) - | (Math.round(g / 255) << 1) - | Math.round(r / 255)); - - if (value === 2) { - ansi += 60; - } - - return ansi; -}; - -convert.hsv.ansi16 = function (args) { - // optimization here; we already know the value and don't need to get - // it converted for us. - return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); -}; - -convert.rgb.ansi256 = function (args) { - var r = args[0]; - var g = args[1]; - var b = args[2]; - - // we use the extended greyscale palette here, with the exception of - // black and white. normal palette only has 4 greyscale shades. - if (r === g && g === b) { - if (r < 8) { - return 16; - } - - if (r > 248) { - return 231; - } - - return Math.round(((r - 8) / 247) * 24) + 232; - } - - var ansi = 16 - + (36 * Math.round(r / 255 * 5)) - + (6 * Math.round(g / 255 * 5)) - + Math.round(b / 255 * 5); - - return ansi; -}; - -convert.ansi16.rgb = function (args) { - var color = args % 10; - - // handle greyscale - if (color === 0 || color === 7) { - if (args > 50) { - color += 3.5; - } - - color = color / 10.5 * 255; - - return [color, color, color]; - } - - var mult = (~~(args > 50) + 1) * 0.5; - var r = ((color & 1) * mult) * 255; - var g = (((color >> 1) & 1) * mult) * 255; - var b = (((color >> 2) & 1) * mult) * 255; - - return [r, g, b]; -}; - -convert.ansi256.rgb = function (args) { - // handle greyscale - if (args >= 232) { - var c = (args - 232) * 10 + 8; - return [c, c, c]; - } - - args -= 16; - - var rem; - var r = Math.floor(args / 36) / 5 * 255; - var g = Math.floor((rem = args % 36) / 6) / 5 * 255; - var b = (rem % 6) / 5 * 255; - - return [r, g, b]; -}; - -convert.rgb.hex = function (args) { - var integer = ((Math.round(args[0]) & 0xFF) << 16) - + ((Math.round(args[1]) & 0xFF) << 8) - + (Math.round(args[2]) & 0xFF); - - var string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; -}; - -convert.hex.rgb = function (args) { - var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); - if (!match) { - return [0, 0, 0]; - } - - var colorString = match[0]; - - if (match[0].length === 3) { - colorString = colorString.split('').map(function (char) { - return char + char; - }).join(''); - } - - var integer = parseInt(colorString, 16); - var r = (integer >> 16) & 0xFF; - var g = (integer >> 8) & 0xFF; - var b = integer & 0xFF; - - return [r, g, b]; -}; - -convert.rgb.hcg = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var max = Math.max(Math.max(r, g), b); - var min = Math.min(Math.min(r, g), b); - var chroma = (max - min); - var grayscale; - var hue; - - if (chroma < 1) { - grayscale = min / (1 - chroma); - } else { - grayscale = 0; - } - - if (chroma <= 0) { - hue = 0; - } else - if (max === r) { - hue = ((g - b) / chroma) % 6; - } else - if (max === g) { - hue = 2 + (b - r) / chroma; - } else { - hue = 4 + (r - g) / chroma + 4; - } - - hue /= 6; - hue %= 1; - - return [hue * 360, chroma * 100, grayscale * 100]; -}; - -convert.hsl.hcg = function (hsl) { - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var c = 1; - var f = 0; - - if (l < 0.5) { - c = 2.0 * s * l; - } else { - c = 2.0 * s * (1.0 - l); - } - - if (c < 1.0) { - f = (l - 0.5 * c) / (1.0 - c); - } - - return [hsl[0], c * 100, f * 100]; -}; - -convert.hsv.hcg = function (hsv) { - var s = hsv[1] / 100; - var v = hsv[2] / 100; - - var c = s * v; - var f = 0; - - if (c < 1.0) { - f = (v - c) / (1 - c); - } - - return [hsv[0], c * 100, f * 100]; -}; - -convert.hcg.rgb = function (hcg) { - var h = hcg[0] / 360; - var c = hcg[1] / 100; - var g = hcg[2] / 100; - - if (c === 0.0) { - return [g * 255, g * 255, g * 255]; - } - - var pure = [0, 0, 0]; - var hi = (h % 1) * 6; - var v = hi % 1; - var w = 1 - v; - var mg = 0; - - switch (Math.floor(hi)) { - case 0: - pure[0] = 1; pure[1] = v; pure[2] = 0; break; - case 1: - pure[0] = w; pure[1] = 1; pure[2] = 0; break; - case 2: - pure[0] = 0; pure[1] = 1; pure[2] = v; break; - case 3: - pure[0] = 0; pure[1] = w; pure[2] = 1; break; - case 4: - pure[0] = v; pure[1] = 0; pure[2] = 1; break; - default: - pure[0] = 1; pure[1] = 0; pure[2] = w; - } - - mg = (1.0 - c) * g; - - return [ - (c * pure[0] + mg) * 255, - (c * pure[1] + mg) * 255, - (c * pure[2] + mg) * 255 - ]; -}; - -convert.hcg.hsv = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; - - var v = c + g * (1.0 - c); - var f = 0; - - if (v > 0.0) { - f = c / v; - } - - return [hcg[0], f * 100, v * 100]; -}; - -convert.hcg.hsl = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; - - var l = g * (1.0 - c) + 0.5 * c; - var s = 0; - - if (l > 0.0 && l < 0.5) { - s = c / (2 * l); - } else - if (l >= 0.5 && l < 1.0) { - s = c / (2 * (1 - l)); - } - - return [hcg[0], s * 100, l * 100]; -}; - -convert.hcg.hwb = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; - var v = c + g * (1.0 - c); - return [hcg[0], (v - c) * 100, (1 - v) * 100]; -}; - -convert.hwb.hcg = function (hwb) { - var w = hwb[1] / 100; - var b = hwb[2] / 100; - var v = 1 - b; - var c = v - w; - var g = 0; - - if (c < 1) { - g = (v - c) / (1 - c); - } - - return [hwb[0], c * 100, g * 100]; -}; - -convert.apple.rgb = function (apple) { - return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; -}; - -convert.rgb.apple = function (rgb) { - return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; -}; - -convert.gray.rgb = function (args) { - return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; -}; - -convert.gray.hsl = convert.gray.hsv = function (args) { - return [0, 0, args[0]]; -}; - -convert.gray.hwb = function (gray) { - return [0, 100, gray[0]]; -}; - -convert.gray.cmyk = function (gray) { - return [0, 0, 0, gray[0]]; -}; - -convert.gray.lab = function (gray) { - return [gray[0], 0, 0]; -}; - -convert.gray.hex = function (gray) { - var val = Math.round(gray[0] / 100 * 255) & 0xFF; - var integer = (val << 16) + (val << 8) + val; - - var string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; -}; - -convert.rgb.gray = function (rgb) { - var val = (rgb[0] + rgb[1] + rgb[2]) / 3; - return [val / 255 * 100]; -}; -}); -var conversions_1 = conversions.rgb; -var conversions_2 = conversions.hsl; -var conversions_3 = conversions.hsv; -var conversions_4 = conversions.hwb; -var conversions_5 = conversions.cmyk; -var conversions_6 = conversions.xyz; -var conversions_7 = conversions.lab; -var conversions_8 = conversions.lch; -var conversions_9 = conversions.hex; -var conversions_10 = conversions.keyword; -var conversions_11 = conversions.ansi16; -var conversions_12 = conversions.ansi256; -var conversions_13 = conversions.hcg; -var conversions_14 = conversions.apple; -var conversions_15 = conversions.gray; - -/* - this function routes a model to all other models. - - all functions that are routed have a property `.conversion` attached - to the returned synthetic function. This property is an array - of strings, each with the steps in between the 'from' and 'to' - color models (inclusive). - - conversions that are not possible simply are not included. -*/ - -function buildGraph() { - var graph = {}; - // https://jsperf.com/object-keys-vs-for-in-with-closure/3 - var models = Object.keys(conversions); - - for (var len = models.length, i = 0; i < len; i++) { - graph[models[i]] = { - // http://jsperf.com/1-vs-infinity - // micro-opt, but this is simple. - distance: -1, - parent: null - }; - } - - return graph; -} - -// https://en.wikipedia.org/wiki/Breadth-first_search -function deriveBFS(fromModel) { - var graph = buildGraph(); - var queue = [fromModel]; // unshift -> queue -> pop - - graph[fromModel].distance = 0; - - while (queue.length) { - var current = queue.pop(); - var adjacents = Object.keys(conversions[current]); - - for (var len = adjacents.length, i = 0; i < len; i++) { - var adjacent = adjacents[i]; - var node = graph[adjacent]; - - if (node.distance === -1) { - node.distance = graph[current].distance + 1; - node.parent = current; - queue.unshift(adjacent); - } - } - } - - return graph; -} - -function link(from, to) { - return function (args) { - return to(from(args)); - }; -} - -function wrapConversion(toModel, graph) { - var path = [graph[toModel].parent, toModel]; - var fn = conversions[graph[toModel].parent][toModel]; - - var cur = graph[toModel].parent; - while (graph[cur].parent) { - path.unshift(graph[cur].parent); - fn = link(conversions[graph[cur].parent][cur], fn); - cur = graph[cur].parent; - } - - fn.conversion = path; - return fn; -} - -var route = function (fromModel) { - var graph = deriveBFS(fromModel); - var conversion = {}; - - var models = Object.keys(graph); - for (var len = models.length, i = 0; i < len; i++) { - var toModel = models[i]; - var node = graph[toModel]; - - if (node.parent === null) { - // no possible conversion, or this node is the source model. - continue; - } - - conversion[toModel] = wrapConversion(toModel, graph); - } - - return conversion; -}; - -var convert = {}; - -var models = Object.keys(conversions); - -function wrapRaw(fn) { - var wrappedFn = function (args) { - if (args === undefined || args === null) { - return args; - } - - if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } - - return fn(args); - }; - - // preserve .conversion property if there is one - if ('conversion' in fn) { - wrappedFn.conversion = fn.conversion; - } - - return wrappedFn; -} - -function wrapRounded(fn) { - var wrappedFn = function (args) { - if (args === undefined || args === null) { - return args; - } - - if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } - - var result = fn(args); - - // we're assuming the result is an array here. - // see notice in conversions.js; don't use box types - // in conversion functions. - if (typeof result === 'object') { - for (var len = result.length, i = 0; i < len; i++) { - result[i] = Math.round(result[i]); - } - } - - return result; - }; - - // preserve .conversion property if there is one - if ('conversion' in fn) { - wrappedFn.conversion = fn.conversion; - } - - return wrappedFn; -} - -models.forEach(function (fromModel) { - convert[fromModel] = {}; - - Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); - Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); - - var routes = route(fromModel); - var routeModels = Object.keys(routes); - - routeModels.forEach(function (toModel) { - var fn = routes[toModel]; - - convert[fromModel][toModel] = wrapRounded(fn); - convert[fromModel][toModel].raw = wrapRaw(fn); - }); -}); - -var colorConvert = convert; - -var colorName$1 = { - "aliceblue": [240, 248, 255], - "antiquewhite": [250, 235, 215], - "aqua": [0, 255, 255], - "aquamarine": [127, 255, 212], - "azure": [240, 255, 255], - "beige": [245, 245, 220], - "bisque": [255, 228, 196], - "black": [0, 0, 0], - "blanchedalmond": [255, 235, 205], - "blue": [0, 0, 255], - "blueviolet": [138, 43, 226], - "brown": [165, 42, 42], - "burlywood": [222, 184, 135], - "cadetblue": [95, 158, 160], - "chartreuse": [127, 255, 0], - "chocolate": [210, 105, 30], - "coral": [255, 127, 80], - "cornflowerblue": [100, 149, 237], - "cornsilk": [255, 248, 220], - "crimson": [220, 20, 60], - "cyan": [0, 255, 255], - "darkblue": [0, 0, 139], - "darkcyan": [0, 139, 139], - "darkgoldenrod": [184, 134, 11], - "darkgray": [169, 169, 169], - "darkgreen": [0, 100, 0], - "darkgrey": [169, 169, 169], - "darkkhaki": [189, 183, 107], - "darkmagenta": [139, 0, 139], - "darkolivegreen": [85, 107, 47], - "darkorange": [255, 140, 0], - "darkorchid": [153, 50, 204], - "darkred": [139, 0, 0], - "darksalmon": [233, 150, 122], - "darkseagreen": [143, 188, 143], - "darkslateblue": [72, 61, 139], - "darkslategray": [47, 79, 79], - "darkslategrey": [47, 79, 79], - "darkturquoise": [0, 206, 209], - "darkviolet": [148, 0, 211], - "deeppink": [255, 20, 147], - "deepskyblue": [0, 191, 255], - "dimgray": [105, 105, 105], - "dimgrey": [105, 105, 105], - "dodgerblue": [30, 144, 255], - "firebrick": [178, 34, 34], - "floralwhite": [255, 250, 240], - "forestgreen": [34, 139, 34], - "fuchsia": [255, 0, 255], - "gainsboro": [220, 220, 220], - "ghostwhite": [248, 248, 255], - "gold": [255, 215, 0], - "goldenrod": [218, 165, 32], - "gray": [128, 128, 128], - "green": [0, 128, 0], - "greenyellow": [173, 255, 47], - "grey": [128, 128, 128], - "honeydew": [240, 255, 240], - "hotpink": [255, 105, 180], - "indianred": [205, 92, 92], - "indigo": [75, 0, 130], - "ivory": [255, 255, 240], - "khaki": [240, 230, 140], - "lavender": [230, 230, 250], - "lavenderblush": [255, 240, 245], - "lawngreen": [124, 252, 0], - "lemonchiffon": [255, 250, 205], - "lightblue": [173, 216, 230], - "lightcoral": [240, 128, 128], - "lightcyan": [224, 255, 255], - "lightgoldenrodyellow": [250, 250, 210], - "lightgray": [211, 211, 211], - "lightgreen": [144, 238, 144], - "lightgrey": [211, 211, 211], - "lightpink": [255, 182, 193], - "lightsalmon": [255, 160, 122], - "lightseagreen": [32, 178, 170], - "lightskyblue": [135, 206, 250], - "lightslategray": [119, 136, 153], - "lightslategrey": [119, 136, 153], - "lightsteelblue": [176, 196, 222], - "lightyellow": [255, 255, 224], - "lime": [0, 255, 0], - "limegreen": [50, 205, 50], - "linen": [250, 240, 230], - "magenta": [255, 0, 255], - "maroon": [128, 0, 0], - "mediumaquamarine": [102, 205, 170], - "mediumblue": [0, 0, 205], - "mediumorchid": [186, 85, 211], - "mediumpurple": [147, 112, 219], - "mediumseagreen": [60, 179, 113], - "mediumslateblue": [123, 104, 238], - "mediumspringgreen": [0, 250, 154], - "mediumturquoise": [72, 209, 204], - "mediumvioletred": [199, 21, 133], - "midnightblue": [25, 25, 112], - "mintcream": [245, 255, 250], - "mistyrose": [255, 228, 225], - "moccasin": [255, 228, 181], - "navajowhite": [255, 222, 173], - "navy": [0, 0, 128], - "oldlace": [253, 245, 230], - "olive": [128, 128, 0], - "olivedrab": [107, 142, 35], - "orange": [255, 165, 0], - "orangered": [255, 69, 0], - "orchid": [218, 112, 214], - "palegoldenrod": [238, 232, 170], - "palegreen": [152, 251, 152], - "paleturquoise": [175, 238, 238], - "palevioletred": [219, 112, 147], - "papayawhip": [255, 239, 213], - "peachpuff": [255, 218, 185], - "peru": [205, 133, 63], - "pink": [255, 192, 203], - "plum": [221, 160, 221], - "powderblue": [176, 224, 230], - "purple": [128, 0, 128], - "rebeccapurple": [102, 51, 153], - "red": [255, 0, 0], - "rosybrown": [188, 143, 143], - "royalblue": [65, 105, 225], - "saddlebrown": [139, 69, 19], - "salmon": [250, 128, 114], - "sandybrown": [244, 164, 96], - "seagreen": [46, 139, 87], - "seashell": [255, 245, 238], - "sienna": [160, 82, 45], - "silver": [192, 192, 192], - "skyblue": [135, 206, 235], - "slateblue": [106, 90, 205], - "slategray": [112, 128, 144], - "slategrey": [112, 128, 144], - "snow": [255, 250, 250], - "springgreen": [0, 255, 127], - "steelblue": [70, 130, 180], - "tan": [210, 180, 140], - "teal": [0, 128, 128], - "thistle": [216, 191, 216], - "tomato": [255, 99, 71], - "turquoise": [64, 224, 208], - "violet": [238, 130, 238], - "wheat": [245, 222, 179], - "white": [255, 255, 255], - "whitesmoke": [245, 245, 245], - "yellow": [255, 255, 0], - "yellowgreen": [154, 205, 50] -}; - -/* MIT license */ - - -var colorString = { - getRgba: getRgba, - getHsla: getHsla, - getRgb: getRgb, - getHsl: getHsl, - getHwb: getHwb, - getAlpha: getAlpha, - - hexString: hexString, - rgbString: rgbString, - rgbaString: rgbaString, - percentString: percentString, - percentaString: percentaString, - hslString: hslString, - hslaString: hslaString, - hwbString: hwbString, - keyword: keyword -}; - -function getRgba(string) { - if (!string) { - return; - } - var abbr = /^#([a-fA-F0-9]{3,4})$/i, - hex = /^#([a-fA-F0-9]{6}([a-fA-F0-9]{2})?)$/i, - rgba = /^rgba?\(\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*,\s*([+-]?\d+)\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, - per = /^rgba?\(\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*,\s*([+-]?[\d\.]+)\%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)$/i, - keyword = /(\w+)/; - - var rgb = [0, 0, 0], - a = 1, - match = string.match(abbr), - hexAlpha = ""; - if (match) { - match = match[1]; - hexAlpha = match[3]; - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match[i] + match[i], 16); - } - if (hexAlpha) { - a = Math.round((parseInt(hexAlpha + hexAlpha, 16) / 255) * 100) / 100; - } - } - else if (match = string.match(hex)) { - hexAlpha = match[2]; - match = match[1]; - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match.slice(i * 2, i * 2 + 2), 16); - } - if (hexAlpha) { - a = Math.round((parseInt(hexAlpha, 16) / 255) * 100) / 100; - } - } - else if (match = string.match(rgba)) { - for (var i = 0; i < rgb.length; i++) { - rgb[i] = parseInt(match[i + 1]); - } - a = parseFloat(match[4]); - } - else if (match = string.match(per)) { - for (var i = 0; i < rgb.length; i++) { - rgb[i] = Math.round(parseFloat(match[i + 1]) * 2.55); - } - a = parseFloat(match[4]); - } - else if (match = string.match(keyword)) { - if (match[1] == "transparent") { - return [0, 0, 0, 0]; - } - rgb = colorName$1[match[1]]; - if (!rgb) { - return; - } - } - - for (var i = 0; i < rgb.length; i++) { - rgb[i] = scale(rgb[i], 0, 255); - } - if (!a && a != 0) { - a = 1; - } - else { - a = scale(a, 0, 1); - } - rgb[3] = a; - return rgb; -} - -function getHsla(string) { - if (!string) { - return; - } - var hsl = /^hsla?\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; - var match = string.match(hsl); - if (match) { - var alpha = parseFloat(match[4]); - var h = scale(parseInt(match[1]), 0, 360), - s = scale(parseFloat(match[2]), 0, 100), - l = scale(parseFloat(match[3]), 0, 100), - a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); - return [h, s, l, a]; - } -} - -function getHwb(string) { - if (!string) { - return; - } - var hwb = /^hwb\(\s*([+-]?\d+)(?:deg)?\s*,\s*([+-]?[\d\.]+)%\s*,\s*([+-]?[\d\.]+)%\s*(?:,\s*([+-]?[\d\.]+)\s*)?\)/; - var match = string.match(hwb); - if (match) { - var alpha = parseFloat(match[4]); - var h = scale(parseInt(match[1]), 0, 360), - w = scale(parseFloat(match[2]), 0, 100), - b = scale(parseFloat(match[3]), 0, 100), - a = scale(isNaN(alpha) ? 1 : alpha, 0, 1); - return [h, w, b, a]; - } -} - -function getRgb(string) { - var rgba = getRgba(string); - return rgba && rgba.slice(0, 3); -} - -function getHsl(string) { - var hsla = getHsla(string); - return hsla && hsla.slice(0, 3); -} - -function getAlpha(string) { - var vals = getRgba(string); - if (vals) { - return vals[3]; - } - else if (vals = getHsla(string)) { - return vals[3]; - } - else if (vals = getHwb(string)) { - return vals[3]; - } -} - -// generators -function hexString(rgba, a) { - var a = (a !== undefined && rgba.length === 3) ? a : rgba[3]; - return "#" + hexDouble(rgba[0]) - + hexDouble(rgba[1]) - + hexDouble(rgba[2]) - + ( - (a >= 0 && a < 1) - ? hexDouble(Math.round(a * 255)) - : "" - ); -} - -function rgbString(rgba, alpha) { - if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { - return rgbaString(rgba, alpha); - } - return "rgb(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] + ")"; -} - -function rgbaString(rgba, alpha) { - if (alpha === undefined) { - alpha = (rgba[3] !== undefined ? rgba[3] : 1); - } - return "rgba(" + rgba[0] + ", " + rgba[1] + ", " + rgba[2] - + ", " + alpha + ")"; -} - -function percentString(rgba, alpha) { - if (alpha < 1 || (rgba[3] && rgba[3] < 1)) { - return percentaString(rgba, alpha); - } - var r = Math.round(rgba[0]/255 * 100), - g = Math.round(rgba[1]/255 * 100), - b = Math.round(rgba[2]/255 * 100); - - return "rgb(" + r + "%, " + g + "%, " + b + "%)"; -} - -function percentaString(rgba, alpha) { - var r = Math.round(rgba[0]/255 * 100), - g = Math.round(rgba[1]/255 * 100), - b = Math.round(rgba[2]/255 * 100); - return "rgba(" + r + "%, " + g + "%, " + b + "%, " + (alpha || rgba[3] || 1) + ")"; -} - -function hslString(hsla, alpha) { - if (alpha < 1 || (hsla[3] && hsla[3] < 1)) { - return hslaString(hsla, alpha); - } - return "hsl(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%)"; -} - -function hslaString(hsla, alpha) { - if (alpha === undefined) { - alpha = (hsla[3] !== undefined ? hsla[3] : 1); - } - return "hsla(" + hsla[0] + ", " + hsla[1] + "%, " + hsla[2] + "%, " - + alpha + ")"; -} - -// hwb is a bit different than rgb(a) & hsl(a) since there is no alpha specific syntax -// (hwb have alpha optional & 1 is default value) -function hwbString(hwb, alpha) { - if (alpha === undefined) { - alpha = (hwb[3] !== undefined ? hwb[3] : 1); - } - return "hwb(" + hwb[0] + ", " + hwb[1] + "%, " + hwb[2] + "%" - + (alpha !== undefined && alpha !== 1 ? ", " + alpha : "") + ")"; -} - -function keyword(rgb) { - return reverseNames[rgb.slice(0, 3)]; -} - -// helpers -function scale(num, min, max) { - return Math.min(Math.max(min, num), max); -} - -function hexDouble(num) { - var str = num.toString(16).toUpperCase(); - return (str.length < 2) ? "0" + str : str; -} - - -//create a list of reverse color names -var reverseNames = {}; -for (var name in colorName$1) { - reverseNames[colorName$1[name]] = name; -} - -/* MIT license */ - - - -var Color = function (obj) { - if (obj instanceof Color) { - return obj; - } - if (!(this instanceof Color)) { - return new Color(obj); - } - - this.valid = false; - this.values = { - rgb: [0, 0, 0], - hsl: [0, 0, 0], - hsv: [0, 0, 0], - hwb: [0, 0, 0], - cmyk: [0, 0, 0, 0], - alpha: 1 - }; - - // parse Color() argument - var vals; - if (typeof obj === 'string') { - vals = colorString.getRgba(obj); - if (vals) { - this.setValues('rgb', vals); - } else if (vals = colorString.getHsla(obj)) { - this.setValues('hsl', vals); - } else if (vals = colorString.getHwb(obj)) { - this.setValues('hwb', vals); - } - } else if (typeof obj === 'object') { - vals = obj; - if (vals.r !== undefined || vals.red !== undefined) { - this.setValues('rgb', vals); - } else if (vals.l !== undefined || vals.lightness !== undefined) { - this.setValues('hsl', vals); - } else if (vals.v !== undefined || vals.value !== undefined) { - this.setValues('hsv', vals); - } else if (vals.w !== undefined || vals.whiteness !== undefined) { - this.setValues('hwb', vals); - } else if (vals.c !== undefined || vals.cyan !== undefined) { - this.setValues('cmyk', vals); - } - } -}; - -Color.prototype = { - isValid: function () { - return this.valid; - }, - rgb: function () { - return this.setSpace('rgb', arguments); - }, - hsl: function () { - return this.setSpace('hsl', arguments); - }, - hsv: function () { - return this.setSpace('hsv', arguments); - }, - hwb: function () { - return this.setSpace('hwb', arguments); - }, - cmyk: function () { - return this.setSpace('cmyk', arguments); - }, - - rgbArray: function () { - return this.values.rgb; - }, - hslArray: function () { - return this.values.hsl; - }, - hsvArray: function () { - return this.values.hsv; - }, - hwbArray: function () { - var values = this.values; - if (values.alpha !== 1) { - return values.hwb.concat([values.alpha]); - } - return values.hwb; - }, - cmykArray: function () { - return this.values.cmyk; - }, - rgbaArray: function () { - var values = this.values; - return values.rgb.concat([values.alpha]); - }, - hslaArray: function () { - var values = this.values; - return values.hsl.concat([values.alpha]); - }, - alpha: function (val) { - if (val === undefined) { - return this.values.alpha; - } - this.setValues('alpha', val); - return this; - }, - - red: function (val) { - return this.setChannel('rgb', 0, val); - }, - green: function (val) { - return this.setChannel('rgb', 1, val); - }, - blue: function (val) { - return this.setChannel('rgb', 2, val); - }, - hue: function (val) { - if (val) { - val %= 360; - val = val < 0 ? 360 + val : val; - } - return this.setChannel('hsl', 0, val); - }, - saturation: function (val) { - return this.setChannel('hsl', 1, val); - }, - lightness: function (val) { - return this.setChannel('hsl', 2, val); - }, - saturationv: function (val) { - return this.setChannel('hsv', 1, val); - }, - whiteness: function (val) { - return this.setChannel('hwb', 1, val); - }, - blackness: function (val) { - return this.setChannel('hwb', 2, val); - }, - value: function (val) { - return this.setChannel('hsv', 2, val); - }, - cyan: function (val) { - return this.setChannel('cmyk', 0, val); - }, - magenta: function (val) { - return this.setChannel('cmyk', 1, val); - }, - yellow: function (val) { - return this.setChannel('cmyk', 2, val); - }, - black: function (val) { - return this.setChannel('cmyk', 3, val); - }, - - hexString: function () { - return colorString.hexString(this.values.rgb); - }, - rgbString: function () { - return colorString.rgbString(this.values.rgb, this.values.alpha); - }, - rgbaString: function () { - return colorString.rgbaString(this.values.rgb, this.values.alpha); - }, - percentString: function () { - return colorString.percentString(this.values.rgb, this.values.alpha); - }, - hslString: function () { - return colorString.hslString(this.values.hsl, this.values.alpha); - }, - hslaString: function () { - return colorString.hslaString(this.values.hsl, this.values.alpha); - }, - hwbString: function () { - return colorString.hwbString(this.values.hwb, this.values.alpha); - }, - keyword: function () { - return colorString.keyword(this.values.rgb, this.values.alpha); - }, - - rgbNumber: function () { - var rgb = this.values.rgb; - return (rgb[0] << 16) | (rgb[1] << 8) | rgb[2]; - }, - - luminosity: function () { - // http://www.w3.org/TR/WCAG20/#relativeluminancedef - var rgb = this.values.rgb; - var lum = []; - for (var i = 0; i < rgb.length; i++) { - var chan = rgb[i] / 255; - lum[i] = (chan <= 0.03928) ? chan / 12.92 : Math.pow(((chan + 0.055) / 1.055), 2.4); - } - return 0.2126 * lum[0] + 0.7152 * lum[1] + 0.0722 * lum[2]; - }, - - contrast: function (color2) { - // http://www.w3.org/TR/WCAG20/#contrast-ratiodef - var lum1 = this.luminosity(); - var lum2 = color2.luminosity(); - if (lum1 > lum2) { - return (lum1 + 0.05) / (lum2 + 0.05); - } - return (lum2 + 0.05) / (lum1 + 0.05); - }, - - level: function (color2) { - var contrastRatio = this.contrast(color2); - if (contrastRatio >= 7.1) { - return 'AAA'; - } - - return (contrastRatio >= 4.5) ? 'AA' : ''; - }, - - dark: function () { - // YIQ equation from http://24ways.org/2010/calculating-color-contrast - var rgb = this.values.rgb; - var yiq = (rgb[0] * 299 + rgb[1] * 587 + rgb[2] * 114) / 1000; - return yiq < 128; - }, - - light: function () { - return !this.dark(); - }, - - negate: function () { - var rgb = []; - for (var i = 0; i < 3; i++) { - rgb[i] = 255 - this.values.rgb[i]; - } - this.setValues('rgb', rgb); - return this; - }, - - lighten: function (ratio) { - var hsl = this.values.hsl; - hsl[2] += hsl[2] * ratio; - this.setValues('hsl', hsl); - return this; - }, - - darken: function (ratio) { - var hsl = this.values.hsl; - hsl[2] -= hsl[2] * ratio; - this.setValues('hsl', hsl); - return this; - }, - - saturate: function (ratio) { - var hsl = this.values.hsl; - hsl[1] += hsl[1] * ratio; - this.setValues('hsl', hsl); - return this; - }, - - desaturate: function (ratio) { - var hsl = this.values.hsl; - hsl[1] -= hsl[1] * ratio; - this.setValues('hsl', hsl); - return this; - }, - - whiten: function (ratio) { - var hwb = this.values.hwb; - hwb[1] += hwb[1] * ratio; - this.setValues('hwb', hwb); - return this; - }, - - blacken: function (ratio) { - var hwb = this.values.hwb; - hwb[2] += hwb[2] * ratio; - this.setValues('hwb', hwb); - return this; - }, - - greyscale: function () { - var rgb = this.values.rgb; - // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale - var val = rgb[0] * 0.3 + rgb[1] * 0.59 + rgb[2] * 0.11; - this.setValues('rgb', [val, val, val]); - return this; - }, - - clearer: function (ratio) { - var alpha = this.values.alpha; - this.setValues('alpha', alpha - (alpha * ratio)); - return this; - }, - - opaquer: function (ratio) { - var alpha = this.values.alpha; - this.setValues('alpha', alpha + (alpha * ratio)); - return this; - }, - - rotate: function (degrees) { - var hsl = this.values.hsl; - var hue = (hsl[0] + degrees) % 360; - hsl[0] = hue < 0 ? 360 + hue : hue; - this.setValues('hsl', hsl); - return this; - }, - - /** - * Ported from sass implementation in C - * https://github.com/sass/libsass/blob/0e6b4a2850092356aa3ece07c6b249f0221caced/functions.cpp#L209 - */ - mix: function (mixinColor, weight) { - var color1 = this; - var color2 = mixinColor; - var p = weight === undefined ? 0.5 : weight; - - var w = 2 * p - 1; - var a = color1.alpha() - color2.alpha(); - - var w1 = (((w * a === -1) ? w : (w + a) / (1 + w * a)) + 1) / 2.0; - var w2 = 1 - w1; - - return this - .rgb( - w1 * color1.red() + w2 * color2.red(), - w1 * color1.green() + w2 * color2.green(), - w1 * color1.blue() + w2 * color2.blue() - ) - .alpha(color1.alpha() * p + color2.alpha() * (1 - p)); - }, - - toJSON: function () { - return this.rgb(); - }, - - clone: function () { - // NOTE(SB): using node-clone creates a dependency to Buffer when using browserify, - // making the final build way to big to embed in Chart.js. So let's do it manually, - // assuming that values to clone are 1 dimension arrays containing only numbers, - // except 'alpha' which is a number. - var result = new Color(); - var source = this.values; - var target = result.values; - var value, type; - - for (var prop in source) { - if (source.hasOwnProperty(prop)) { - value = source[prop]; - type = ({}).toString.call(value); - if (type === '[object Array]') { - target[prop] = value.slice(0); - } else if (type === '[object Number]') { - target[prop] = value; - } else { - console.error('unexpected color value:', value); - } - } - } - - return result; - } -}; - -Color.prototype.spaces = { - rgb: ['red', 'green', 'blue'], - hsl: ['hue', 'saturation', 'lightness'], - hsv: ['hue', 'saturation', 'value'], - hwb: ['hue', 'whiteness', 'blackness'], - cmyk: ['cyan', 'magenta', 'yellow', 'black'] -}; - -Color.prototype.maxes = { - rgb: [255, 255, 255], - hsl: [360, 100, 100], - hsv: [360, 100, 100], - hwb: [360, 100, 100], - cmyk: [100, 100, 100, 100] -}; - -Color.prototype.getValues = function (space) { - var values = this.values; - var vals = {}; - - for (var i = 0; i < space.length; i++) { - vals[space.charAt(i)] = values[space][i]; - } - - if (values.alpha !== 1) { - vals.a = values.alpha; - } - - // {r: 255, g: 255, b: 255, a: 0.4} - return vals; -}; - -Color.prototype.setValues = function (space, vals) { - var values = this.values; - var spaces = this.spaces; - var maxes = this.maxes; - var alpha = 1; - var i; - - this.valid = true; - - if (space === 'alpha') { - alpha = vals; - } else if (vals.length) { - // [10, 10, 10] - values[space] = vals.slice(0, space.length); - alpha = vals[space.length]; - } else if (vals[space.charAt(0)] !== undefined) { - // {r: 10, g: 10, b: 10} - for (i = 0; i < space.length; i++) { - values[space][i] = vals[space.charAt(i)]; - } - - alpha = vals.a; - } else if (vals[spaces[space][0]] !== undefined) { - // {red: 10, green: 10, blue: 10} - var chans = spaces[space]; - - for (i = 0; i < space.length; i++) { - values[space][i] = vals[chans[i]]; - } - - alpha = vals.alpha; - } - - values.alpha = Math.max(0, Math.min(1, (alpha === undefined ? values.alpha : alpha))); - - if (space === 'alpha') { - return false; - } - - var capped; - - // cap values of the space prior converting all values - for (i = 0; i < space.length; i++) { - capped = Math.max(0, Math.min(maxes[space][i], values[space][i])); - values[space][i] = Math.round(capped); - } - - // convert to all the other color spaces - for (var sname in spaces) { - if (sname !== space) { - values[sname] = colorConvert[space][sname](values[space]); - } - } - - return true; -}; - -Color.prototype.setSpace = function (space, args) { - var vals = args[0]; - - if (vals === undefined) { - // color.rgb() - return this.getValues(space); - } - - // color.rgb(10, 10, 10) - if (typeof vals === 'number') { - vals = Array.prototype.slice.call(args); - } - - this.setValues(space, vals); - return this; -}; - -Color.prototype.setChannel = function (space, index, val) { - var svalues = this.values[space]; - if (val === undefined) { - // color.red() - return svalues[index]; - } else if (val === svalues[index]) { - // color.red(color.red()) - return this; - } - - // color.red(100) - svalues[index] = val; - this.setValues(space, svalues); - - return this; -}; - -if (typeof window !== 'undefined') { - window.Color = Color; -} - -var chartjsColor = Color; - -/** - * @namespace Chart.helpers - */ -var helpers = { - /** - * An empty function that can be used, for example, for optional callback. - */ - noop: function() {}, - - /** - * Returns a unique id, sequentially generated from a global variable. - * @returns {number} - * @function - */ - uid: (function() { - var id = 0; - return function() { - return id++; - }; - }()), - - /** - * Returns true if `value` is neither null nor undefined, else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @since 2.7.0 - */ - isNullOrUndef: function(value) { - return value === null || typeof value === 'undefined'; - }, - - /** - * Returns true if `value` is an array (including typed arrays), else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @function - */ - isArray: function(value) { - if (Array.isArray && Array.isArray(value)) { - return true; - } - var type = Object.prototype.toString.call(value); - if (type.substr(0, 7) === '[object' && type.substr(-6) === 'Array]') { - return true; - } - return false; - }, - - /** - * Returns true if `value` is an object (excluding null), else returns false. - * @param {*} value - The value to test. - * @returns {boolean} - * @since 2.7.0 - */ - isObject: function(value) { - return value !== null && Object.prototype.toString.call(value) === '[object Object]'; - }, - - /** - * Returns true if `value` is a finite number, else returns false - * @param {*} value - The value to test. - * @returns {boolean} - */ - isFinite: function(value) { - return (typeof value === 'number' || value instanceof Number) && isFinite(value); - }, - - /** - * Returns `value` if defined, else returns `defaultValue`. - * @param {*} value - The value to return if defined. - * @param {*} defaultValue - The value to return if `value` is undefined. - * @returns {*} - */ - valueOrDefault: function(value, defaultValue) { - return typeof value === 'undefined' ? defaultValue : value; - }, - - /** - * Returns value at the given `index` in array if defined, else returns `defaultValue`. - * @param {Array} value - The array to lookup for value at `index`. - * @param {number} index - The index in `value` to lookup for value. - * @param {*} defaultValue - The value to return if `value[index]` is undefined. - * @returns {*} - */ - valueAtIndexOrDefault: function(value, index, defaultValue) { - return helpers.valueOrDefault(helpers.isArray(value) ? value[index] : value, defaultValue); - }, - - /** - * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the - * value returned by `fn`. If `fn` is not a function, this method returns undefined. - * @param {function} fn - The function to call. - * @param {Array|undefined|null} args - The arguments with which `fn` should be called. - * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. - * @returns {*} - */ - callback: function(fn, args, thisArg) { - if (fn && typeof fn.call === 'function') { - return fn.apply(thisArg, args); - } - }, - - /** - * Note(SB) for performance sake, this method should only be used when loopable type - * is unknown or in none intensive code (not called often and small loopable). Else - * it's preferable to use a regular for() loop and save extra function calls. - * @param {object|Array} loopable - The object or array to be iterated. - * @param {function} fn - The function to call for each item. - * @param {object} [thisArg] - The value of `this` provided for the call to `fn`. - * @param {boolean} [reverse] - If true, iterates backward on the loopable. - */ - each: function(loopable, fn, thisArg, reverse) { - var i, len, keys; - if (helpers.isArray(loopable)) { - len = loopable.length; - if (reverse) { - for (i = len - 1; i >= 0; i--) { - fn.call(thisArg, loopable[i], i); - } - } else { - for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[i], i); - } - } - } else if (helpers.isObject(loopable)) { - keys = Object.keys(loopable); - len = keys.length; - for (i = 0; i < len; i++) { - fn.call(thisArg, loopable[keys[i]], keys[i]); - } - } - }, - - /** - * Returns true if the `a0` and `a1` arrays have the same content, else returns false. - * @see https://stackoverflow.com/a/14853974 - * @param {Array} a0 - The array to compare - * @param {Array} a1 - The array to compare - * @returns {boolean} - */ - arrayEquals: function(a0, a1) { - var i, ilen, v0, v1; - - if (!a0 || !a1 || a0.length !== a1.length) { - return false; - } - - for (i = 0, ilen = a0.length; i < ilen; ++i) { - v0 = a0[i]; - v1 = a1[i]; - - if (v0 instanceof Array && v1 instanceof Array) { - if (!helpers.arrayEquals(v0, v1)) { - return false; - } - } else if (v0 !== v1) { - // NOTE: two different object instances will never be equal: {x:20} != {x:20} - return false; - } - } - - return true; - }, - - /** - * Returns a deep copy of `source` without keeping references on objects and arrays. - * @param {*} source - The value to clone. - * @returns {*} - */ - clone: function(source) { - if (helpers.isArray(source)) { - return source.map(helpers.clone); - } - - if (helpers.isObject(source)) { - var target = {}; - var keys = Object.keys(source); - var klen = keys.length; - var k = 0; - - for (; k < klen; ++k) { - target[keys[k]] = helpers.clone(source[keys[k]]); - } - - return target; - } - - return source; - }, - - /** - * The default merger when Chart.helpers.merge is called without merger option. - * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback. - * @private - */ - _merger: function(key, target, source, options) { - var tval = target[key]; - var sval = source[key]; - - if (helpers.isObject(tval) && helpers.isObject(sval)) { - helpers.merge(tval, sval, options); - } else { - target[key] = helpers.clone(sval); - } - }, - - /** - * Merges source[key] in target[key] only if target[key] is undefined. - * @private - */ - _mergerIf: function(key, target, source) { - var tval = target[key]; - var sval = source[key]; - - if (helpers.isObject(tval) && helpers.isObject(sval)) { - helpers.mergeIf(tval, sval); - } else if (!target.hasOwnProperty(key)) { - target[key] = helpers.clone(sval); - } - }, - - /** - * Recursively deep copies `source` properties into `target` with the given `options`. - * IMPORTANT: `target` is not cloned and will be updated with `source` properties. - * @param {object} target - The target object in which all sources are merged into. - * @param {object|object[]} source - Object(s) to merge into `target`. - * @param {object} [options] - Merging options: - * @param {function} [options.merger] - The merge method (key, target, source, options) - * @returns {object} The `target` object. - */ - merge: function(target, source, options) { - var sources = helpers.isArray(source) ? source : [source]; - var ilen = sources.length; - var merge, i, keys, klen, k; - - if (!helpers.isObject(target)) { - return target; - } - - options = options || {}; - merge = options.merger || helpers._merger; - - for (i = 0; i < ilen; ++i) { - source = sources[i]; - if (!helpers.isObject(source)) { - continue; - } - - keys = Object.keys(source); - for (k = 0, klen = keys.length; k < klen; ++k) { - merge(keys[k], target, source, options); - } - } - - return target; - }, - - /** - * Recursively deep copies `source` properties into `target` *only* if not defined in target. - * IMPORTANT: `target` is not cloned and will be updated with `source` properties. - * @param {object} target - The target object in which all sources are merged into. - * @param {object|object[]} source - Object(s) to merge into `target`. - * @returns {object} The `target` object. - */ - mergeIf: function(target, source) { - return helpers.merge(target, source, {merger: helpers._mergerIf}); - }, - - /** - * Applies the contents of two or more objects together into the first object. - * @param {object} target - The target object in which all objects are merged into. - * @param {object} arg1 - Object containing additional properties to merge in target. - * @param {object} argN - Additional objects containing properties to merge in target. - * @returns {object} The `target` object. - */ - extend: Object.assign || function(target) { - return helpers.merge(target, [].slice.call(arguments, 1), { - merger: function(key, dst, src) { - dst[key] = src[key]; - } - }); - }, - - /** - * Basic javascript inheritance based on the model created in Backbone.js - */ - inherits: function(extensions) { - var me = this; - var ChartElement = (extensions && extensions.hasOwnProperty('constructor')) ? extensions.constructor : function() { - return me.apply(this, arguments); - }; - - var Surrogate = function() { - this.constructor = ChartElement; - }; - - Surrogate.prototype = me.prototype; - ChartElement.prototype = new Surrogate(); - ChartElement.extend = helpers.inherits; - - if (extensions) { - helpers.extend(ChartElement.prototype, extensions); - } - - ChartElement.__super__ = me.prototype; - return ChartElement; - }, - - _deprecated: function(scope, value, previous, current) { - if (value !== undefined) { - console.warn(scope + ': "' + previous + - '" is deprecated. Please use "' + current + '" instead'); - } - } -}; - -var helpers_core = helpers; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.helpers.callback instead. - * @function Chart.helpers.callCallback - * @deprecated since version 2.6.0 - * @todo remove at version 3 - * @private - */ -helpers.callCallback = helpers.callback; - -/** - * Provided for backward compatibility, use Array.prototype.indexOf instead. - * Array.prototype.indexOf compatibility: Chrome, Opera, Safari, FF1.5+, IE9+ - * @function Chart.helpers.indexOf - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers.indexOf = function(array, item, fromIndex) { - return Array.prototype.indexOf.call(array, item, fromIndex); -}; - -/** - * Provided for backward compatibility, use Chart.helpers.valueOrDefault instead. - * @function Chart.helpers.getValueOrDefault - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers.getValueOrDefault = helpers.valueOrDefault; - -/** - * Provided for backward compatibility, use Chart.helpers.valueAtIndexOrDefault instead. - * @function Chart.helpers.getValueAtIndexOrDefault - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers.getValueAtIndexOrDefault = helpers.valueAtIndexOrDefault; - -/** - * Easing functions adapted from Robert Penner's easing equations. - * @namespace Chart.helpers.easingEffects - * @see http://www.robertpenner.com/easing/ - */ -var effects = { - linear: function(t) { - return t; - }, - - easeInQuad: function(t) { - return t * t; - }, - - easeOutQuad: function(t) { - return -t * (t - 2); - }, - - easeInOutQuad: function(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t; - } - return -0.5 * ((--t) * (t - 2) - 1); - }, - - easeInCubic: function(t) { - return t * t * t; - }, - - easeOutCubic: function(t) { - return (t = t - 1) * t * t + 1; - }, - - easeInOutCubic: function(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t; - } - return 0.5 * ((t -= 2) * t * t + 2); - }, - - easeInQuart: function(t) { - return t * t * t * t; - }, - - easeOutQuart: function(t) { - return -((t = t - 1) * t * t * t - 1); - }, - - easeInOutQuart: function(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t * t; - } - return -0.5 * ((t -= 2) * t * t * t - 2); - }, - - easeInQuint: function(t) { - return t * t * t * t * t; - }, - - easeOutQuint: function(t) { - return (t = t - 1) * t * t * t * t + 1; - }, - - easeInOutQuint: function(t) { - if ((t /= 0.5) < 1) { - return 0.5 * t * t * t * t * t; - } - return 0.5 * ((t -= 2) * t * t * t * t + 2); - }, - - easeInSine: function(t) { - return -Math.cos(t * (Math.PI / 2)) + 1; - }, - - easeOutSine: function(t) { - return Math.sin(t * (Math.PI / 2)); - }, - - easeInOutSine: function(t) { - return -0.5 * (Math.cos(Math.PI * t) - 1); - }, - - easeInExpo: function(t) { - return (t === 0) ? 0 : Math.pow(2, 10 * (t - 1)); - }, - - easeOutExpo: function(t) { - return (t === 1) ? 1 : -Math.pow(2, -10 * t) + 1; - }, - - easeInOutExpo: function(t) { - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if ((t /= 0.5) < 1) { - return 0.5 * Math.pow(2, 10 * (t - 1)); - } - return 0.5 * (-Math.pow(2, -10 * --t) + 2); - }, - - easeInCirc: function(t) { - if (t >= 1) { - return t; - } - return -(Math.sqrt(1 - t * t) - 1); - }, - - easeOutCirc: function(t) { - return Math.sqrt(1 - (t = t - 1) * t); - }, - - easeInOutCirc: function(t) { - if ((t /= 0.5) < 1) { - return -0.5 * (Math.sqrt(1 - t * t) - 1); - } - return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1); - }, - - easeInElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if (!p) { - p = 0.3; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); - }, - - easeOutElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if (!p) { - p = 0.3; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1; - }, - - easeInOutElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if ((t /= 0.5) === 2) { - return 1; - } - if (!p) { - p = 0.45; - } - if (a < 1) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - if (t < 1) { - return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)); - } - return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * 0.5 + 1; - }, - easeInBack: function(t) { - var s = 1.70158; - return t * t * ((s + 1) * t - s); - }, - - easeOutBack: function(t) { - var s = 1.70158; - return (t = t - 1) * t * ((s + 1) * t + s) + 1; - }, - - easeInOutBack: function(t) { - var s = 1.70158; - if ((t /= 0.5) < 1) { - return 0.5 * (t * t * (((s *= (1.525)) + 1) * t - s)); - } - return 0.5 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); - }, - - easeInBounce: function(t) { - return 1 - effects.easeOutBounce(1 - t); - }, - - easeOutBounce: function(t) { - if (t < (1 / 2.75)) { - return 7.5625 * t * t; - } - if (t < (2 / 2.75)) { - return 7.5625 * (t -= (1.5 / 2.75)) * t + 0.75; - } - if (t < (2.5 / 2.75)) { - return 7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375; - } - return 7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375; - }, - - easeInOutBounce: function(t) { - if (t < 0.5) { - return effects.easeInBounce(t * 2) * 0.5; - } - return effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5; - } -}; - -var helpers_easing = { - effects: effects -}; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.helpers.easing.effects instead. - * @function Chart.helpers.easingEffects - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers_core.easingEffects = effects; - -var PI = Math.PI; -var RAD_PER_DEG = PI / 180; -var DOUBLE_PI = PI * 2; -var HALF_PI = PI / 2; -var QUARTER_PI = PI / 4; -var TWO_THIRDS_PI = PI * 2 / 3; - -/** - * @namespace Chart.helpers.canvas - */ -var exports$1 = { - /** - * Clears the entire canvas associated to the given `chart`. - * @param {Chart} chart - The chart for which to clear the canvas. - */ - clear: function(chart) { - chart.ctx.clearRect(0, 0, chart.width, chart.height); - }, - - /** - * Creates a "path" for a rectangle with rounded corners at position (x, y) with a - * given size (width, height) and the same `radius` for all corners. - * @param {CanvasRenderingContext2D} ctx - The canvas 2D Context. - * @param {number} x - The x axis of the coordinate for the rectangle starting point. - * @param {number} y - The y axis of the coordinate for the rectangle starting point. - * @param {number} width - The rectangle's width. - * @param {number} height - The rectangle's height. - * @param {number} radius - The rounded amount (in pixels) for the four corners. - * @todo handle `radius` as top-left, top-right, bottom-right, bottom-left array/object? - */ - roundedRect: function(ctx, x, y, width, height, radius) { - if (radius) { - var r = Math.min(radius, height / 2, width / 2); - var left = x + r; - var top = y + r; - var right = x + width - r; - var bottom = y + height - r; - - ctx.moveTo(x, top); - if (left < right && top < bottom) { - ctx.arc(left, top, r, -PI, -HALF_PI); - ctx.arc(right, top, r, -HALF_PI, 0); - ctx.arc(right, bottom, r, 0, HALF_PI); - ctx.arc(left, bottom, r, HALF_PI, PI); - } else if (left < right) { - ctx.moveTo(left, y); - ctx.arc(right, top, r, -HALF_PI, HALF_PI); - ctx.arc(left, top, r, HALF_PI, PI + HALF_PI); - } else if (top < bottom) { - ctx.arc(left, top, r, -PI, 0); - ctx.arc(left, bottom, r, 0, PI); - } else { - ctx.arc(left, top, r, -PI, PI); - } - ctx.closePath(); - ctx.moveTo(x, y); - } else { - ctx.rect(x, y, width, height); - } - }, - - drawPoint: function(ctx, style, radius, x, y, rotation) { - var type, xOffset, yOffset, size, cornerRadius; - var rad = (rotation || 0) * RAD_PER_DEG; - - if (style && typeof style === 'object') { - type = style.toString(); - if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') { - ctx.save(); - ctx.translate(x, y); - ctx.rotate(rad); - ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height); - ctx.restore(); - return; - } - } - - if (isNaN(radius) || radius <= 0) { - return; - } - - ctx.beginPath(); - - switch (style) { - // Default includes circle - default: - ctx.arc(x, y, radius, 0, DOUBLE_PI); - ctx.closePath(); - break; - case 'triangle': - ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - rad += TWO_THIRDS_PI; - ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - rad += TWO_THIRDS_PI; - ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius); - ctx.closePath(); - break; - case 'rectRounded': - // NOTE: the rounded rect implementation changed to use `arc` instead of - // `quadraticCurveTo` since it generates better results when rect is - // almost a circle. 0.516 (instead of 0.5) produces results with visually - // closer proportion to the previous impl and it is inscribed in the - // circle with `radius`. For more details, see the following PRs: - // https://github.com/chartjs/Chart.js/issues/5597 - // https://github.com/chartjs/Chart.js/issues/5858 - cornerRadius = radius * 0.516; - size = radius - cornerRadius; - xOffset = Math.cos(rad + QUARTER_PI) * size; - yOffset = Math.sin(rad + QUARTER_PI) * size; - ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI); - ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad); - ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI); - ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI); - ctx.closePath(); - break; - case 'rect': - if (!rotation) { - size = Math.SQRT1_2 * radius; - ctx.rect(x - size, y - size, 2 * size, 2 * size); - break; - } - rad += QUARTER_PI; - /* falls through */ - case 'rectRot': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + yOffset, y - xOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.lineTo(x - yOffset, y + xOffset); - ctx.closePath(); - break; - case 'crossRot': - rad += QUARTER_PI; - /* falls through */ - case 'cross': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - break; - case 'star': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - rad += QUARTER_PI; - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - ctx.moveTo(x + yOffset, y - xOffset); - ctx.lineTo(x - yOffset, y + xOffset); - break; - case 'line': - xOffset = Math.cos(rad) * radius; - yOffset = Math.sin(rad) * radius; - ctx.moveTo(x - xOffset, y - yOffset); - ctx.lineTo(x + xOffset, y + yOffset); - break; - case 'dash': - ctx.moveTo(x, y); - ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius); - break; - } - - ctx.fill(); - ctx.stroke(); - }, - - /** - * Returns true if the point is inside the rectangle - * @param {object} point - The point to test - * @param {object} area - The rectangle - * @returns {boolean} - * @private - */ - _isPointInArea: function(point, area) { - var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. - - return point.x > area.left - epsilon && point.x < area.right + epsilon && - point.y > area.top - epsilon && point.y < area.bottom + epsilon; - }, - - clipArea: function(ctx, area) { - ctx.save(); - ctx.beginPath(); - ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top); - ctx.clip(); - }, - - unclipArea: function(ctx) { - ctx.restore(); - }, - - lineTo: function(ctx, previous, target, flip) { - var stepped = target.steppedLine; - if (stepped) { - if (stepped === 'middle') { - var midpoint = (previous.x + target.x) / 2.0; - ctx.lineTo(midpoint, flip ? target.y : previous.y); - ctx.lineTo(midpoint, flip ? previous.y : target.y); - } else if ((stepped === 'after' && !flip) || (stepped !== 'after' && flip)) { - ctx.lineTo(previous.x, target.y); - } else { - ctx.lineTo(target.x, previous.y); - } - ctx.lineTo(target.x, target.y); - return; - } - - if (!target.tension) { - ctx.lineTo(target.x, target.y); - return; - } - - ctx.bezierCurveTo( - flip ? previous.controlPointPreviousX : previous.controlPointNextX, - flip ? previous.controlPointPreviousY : previous.controlPointNextY, - flip ? target.controlPointNextX : target.controlPointPreviousX, - flip ? target.controlPointNextY : target.controlPointPreviousY, - target.x, - target.y); - } -}; - -var helpers_canvas = exports$1; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.helpers.canvas.clear instead. - * @namespace Chart.helpers.clear - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers_core.clear = exports$1.clear; - -/** - * Provided for backward compatibility, use Chart.helpers.canvas.roundedRect instead. - * @namespace Chart.helpers.drawRoundedRectangle - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers_core.drawRoundedRectangle = function(ctx) { - ctx.beginPath(); - exports$1.roundedRect.apply(exports$1, arguments); -}; - -var defaults = { - /** - * @private - */ - _set: function(scope, values) { - return helpers_core.merge(this[scope] || (this[scope] = {}), values); - } -}; - -// TODO(v3): remove 'global' from namespace. all default are global and -// there's inconsistency around which options are under 'global' -defaults._set('global', { - defaultColor: 'rgba(0,0,0,0.1)', - defaultFontColor: '#666', - defaultFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - defaultFontSize: 12, - defaultFontStyle: 'normal', - defaultLineHeight: 1.2, - showLines: true -}); - -var core_defaults = defaults; - -var valueOrDefault = helpers_core.valueOrDefault; - -/** - * Converts the given font object into a CSS font string. - * @param {object} font - A font object. - * @return {string} The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font - * @private - */ -function toFontString(font) { - if (!font || helpers_core.isNullOrUndef(font.size) || helpers_core.isNullOrUndef(font.family)) { - return null; - } - - return (font.style ? font.style + ' ' : '') - + (font.weight ? font.weight + ' ' : '') - + font.size + 'px ' - + font.family; -} - -/** - * @alias Chart.helpers.options - * @namespace - */ -var helpers_options = { - /** - * Converts the given line height `value` in pixels for a specific font `size`. - * @param {number|string} value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em'). - * @param {number} size - The font size (in pixels) used to resolve relative `value`. - * @returns {number} The effective line height in pixels (size * 1.2 if value is invalid). - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height - * @since 2.7.0 - */ - toLineHeight: function(value, size) { - var matches = ('' + value).match(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/); - if (!matches || matches[1] === 'normal') { - return size * 1.2; - } - - value = +matches[2]; - - switch (matches[3]) { - case 'px': - return value; - case '%': - value /= 100; - break; - } - - return size * value; - }, - - /** - * Converts the given value into a padding object with pre-computed width/height. - * @param {number|object} value - If a number, set the value to all TRBL component, - * else, if and object, use defined properties and sets undefined ones to 0. - * @returns {object} The padding values (top, right, bottom, left, width, height) - * @since 2.7.0 - */ - toPadding: function(value) { - var t, r, b, l; - - if (helpers_core.isObject(value)) { - t = +value.top || 0; - r = +value.right || 0; - b = +value.bottom || 0; - l = +value.left || 0; - } else { - t = r = b = l = +value || 0; - } - - return { - top: t, - right: r, - bottom: b, - left: l, - height: t + b, - width: l + r - }; - }, - - /** - * Parses font options and returns the font object. - * @param {object} options - A object that contains font options to be parsed. - * @return {object} The font object. - * @todo Support font.* options and renamed to toFont(). - * @private - */ - _parseFont: function(options) { - var globalDefaults = core_defaults.global; - var size = valueOrDefault(options.fontSize, globalDefaults.defaultFontSize); - var font = { - family: valueOrDefault(options.fontFamily, globalDefaults.defaultFontFamily), - lineHeight: helpers_core.options.toLineHeight(valueOrDefault(options.lineHeight, globalDefaults.defaultLineHeight), size), - size: size, - style: valueOrDefault(options.fontStyle, globalDefaults.defaultFontStyle), - weight: null, - string: '' - }; - - font.string = toFontString(font); - return font; - }, - - /** - * Evaluates the given `inputs` sequentially and returns the first defined value. - * @param {Array} inputs - An array of values, falling back to the last value. - * @param {object} [context] - If defined and the current value is a function, the value - * is called with `context` as first argument and the result becomes the new input. - * @param {number} [index] - If defined and the current value is an array, the value - * at `index` become the new input. - * @param {object} [info] - object to return information about resolution in - * @param {boolean} [info.cacheable] - Will be set to `false` if option is not cacheable. - * @since 2.7.0 - */ - resolve: function(inputs, context, index, info) { - var cacheable = true; - var i, ilen, value; - - for (i = 0, ilen = inputs.length; i < ilen; ++i) { - value = inputs[i]; - if (value === undefined) { - continue; - } - if (context !== undefined && typeof value === 'function') { - value = value(context); - cacheable = false; - } - if (index !== undefined && helpers_core.isArray(value)) { - value = value[index]; - cacheable = false; - } - if (value !== undefined) { - if (info && !cacheable) { - info.cacheable = false; - } - return value; - } - } - } -}; - -/** - * @alias Chart.helpers.math - * @namespace - */ -var exports$2 = { - /** - * Returns an array of factors sorted from 1 to sqrt(value) - * @private - */ - _factorize: function(value) { - var result = []; - var sqrt = Math.sqrt(value); - var i; - - for (i = 1; i < sqrt; i++) { - if (value % i === 0) { - result.push(i); - result.push(value / i); - } - } - if (sqrt === (sqrt | 0)) { // if value is a square number - result.push(sqrt); - } - - result.sort(function(a, b) { - return a - b; - }).pop(); - return result; - }, - - log10: Math.log10 || function(x) { - var exponent = Math.log(x) * Math.LOG10E; // Math.LOG10E = 1 / Math.LN10. - // Check for whole powers of 10, - // which due to floating point rounding error should be corrected. - var powerOf10 = Math.round(exponent); - var isPowerOf10 = x === Math.pow(10, powerOf10); - - return isPowerOf10 ? powerOf10 : exponent; - } -}; - -var helpers_math = exports$2; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.helpers.math.log10 instead. - * @namespace Chart.helpers.log10 - * @deprecated since version 2.9.0 - * @todo remove at version 3 - * @private - */ -helpers_core.log10 = exports$2.log10; - -var getRtlAdapter = function(rectX, width) { - return { - x: function(x) { - return rectX + rectX + width - x; - }, - setWidth: function(w) { - width = w; - }, - textAlign: function(align) { - if (align === 'center') { - return align; - } - return align === 'right' ? 'left' : 'right'; - }, - xPlus: function(x, value) { - return x - value; - }, - leftForLtr: function(x, itemWidth) { - return x - itemWidth; - }, - }; -}; - -var getLtrAdapter = function() { - return { - x: function(x) { - return x; - }, - setWidth: function(w) { // eslint-disable-line no-unused-vars - }, - textAlign: function(align) { - return align; - }, - xPlus: function(x, value) { - return x + value; - }, - leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars - return x; - }, - }; -}; - -var getAdapter = function(rtl, rectX, width) { - return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter(); -}; - -var overrideTextDirection = function(ctx, direction) { - var style, original; - if (direction === 'ltr' || direction === 'rtl') { - style = ctx.canvas.style; - original = [ - style.getPropertyValue('direction'), - style.getPropertyPriority('direction'), - ]; - - style.setProperty('direction', direction, 'important'); - ctx.prevTextDirection = original; - } -}; - -var restoreTextDirection = function(ctx) { - var original = ctx.prevTextDirection; - if (original !== undefined) { - delete ctx.prevTextDirection; - ctx.canvas.style.setProperty('direction', original[0], original[1]); - } -}; - -var helpers_rtl = { - getRtlAdapter: getAdapter, - overrideTextDirection: overrideTextDirection, - restoreTextDirection: restoreTextDirection, -}; - -var helpers$1 = helpers_core; -var easing = helpers_easing; -var canvas = helpers_canvas; -var options = helpers_options; -var math = helpers_math; -var rtl = helpers_rtl; -helpers$1.easing = easing; -helpers$1.canvas = canvas; -helpers$1.options = options; -helpers$1.math = math; -helpers$1.rtl = rtl; - -function interpolate(start, view, model, ease) { - var keys = Object.keys(model); - var i, ilen, key, actual, origin, target, type, c0, c1; - - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - - target = model[key]; - - // if a value is added to the model after pivot() has been called, the view - // doesn't contain it, so let's initialize the view to the target value. - if (!view.hasOwnProperty(key)) { - view[key] = target; - } - - actual = view[key]; - - if (actual === target || key[0] === '_') { - continue; - } - - if (!start.hasOwnProperty(key)) { - start[key] = actual; - } - - origin = start[key]; - - type = typeof target; - - if (type === typeof origin) { - if (type === 'string') { - c0 = chartjsColor(origin); - if (c0.valid) { - c1 = chartjsColor(target); - if (c1.valid) { - view[key] = c1.mix(c0, ease).rgbString(); - continue; - } - } - } else if (helpers$1.isFinite(origin) && helpers$1.isFinite(target)) { - view[key] = origin + (target - origin) * ease; - continue; - } - } - - view[key] = target; - } -} - -var Element = function(configuration) { - helpers$1.extend(this, configuration); - this.initialize.apply(this, arguments); -}; - -helpers$1.extend(Element.prototype, { - _type: undefined, - - initialize: function() { - this.hidden = false; - }, - - pivot: function() { - var me = this; - if (!me._view) { - me._view = helpers$1.extend({}, me._model); - } - me._start = {}; - return me; - }, - - transition: function(ease) { - var me = this; - var model = me._model; - var start = me._start; - var view = me._view; - - // No animation -> No Transition - if (!model || ease === 1) { - me._view = helpers$1.extend({}, model); - me._start = null; - return me; - } - - if (!view) { - view = me._view = {}; - } - - if (!start) { - start = me._start = {}; - } - - interpolate(start, view, model, ease); - - return me; - }, - - tooltipPosition: function() { - return { - x: this._model.x, - y: this._model.y - }; - }, - - hasValue: function() { - return helpers$1.isNumber(this._model.x) && helpers$1.isNumber(this._model.y); - } -}); - -Element.extend = helpers$1.inherits; - -var core_element = Element; - -var exports$3 = core_element.extend({ - chart: null, // the animation associated chart instance - currentStep: 0, // the current animation step - numSteps: 60, // default number of steps - easing: '', // the easing to use for this animation - render: null, // render function used by the animation service - - onAnimationProgress: null, // user specified callback to fire on each step of the animation - onAnimationComplete: null, // user specified callback to fire when the animation finishes -}); - -var core_animation = exports$3; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart.Animation instead - * @prop Chart.Animation#animationObject - * @deprecated since version 2.6.0 - * @todo remove at version 3 - */ -Object.defineProperty(exports$3.prototype, 'animationObject', { - get: function() { - return this; - } -}); - -/** - * Provided for backward compatibility, use Chart.Animation#chart instead - * @prop Chart.Animation#chartInstance - * @deprecated since version 2.6.0 - * @todo remove at version 3 - */ -Object.defineProperty(exports$3.prototype, 'chartInstance', { - get: function() { - return this.chart; - }, - set: function(value) { - this.chart = value; - } -}); - -core_defaults._set('global', { - animation: { - duration: 1000, - easing: 'easeOutQuart', - onProgress: helpers$1.noop, - onComplete: helpers$1.noop - } -}); - -var core_animations = { - animations: [], - request: null, - - /** - * @param {Chart} chart - The chart to animate. - * @param {Chart.Animation} animation - The animation that we will animate. - * @param {number} duration - The animation duration in ms. - * @param {boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions - */ - addAnimation: function(chart, animation, duration, lazy) { - var animations = this.animations; - var i, ilen; - - animation.chart = chart; - animation.startTime = Date.now(); - animation.duration = duration; - - if (!lazy) { - chart.animating = true; - } - - for (i = 0, ilen = animations.length; i < ilen; ++i) { - if (animations[i].chart === chart) { - animations[i] = animation; - return; - } - } - - animations.push(animation); - - // If there are no animations queued, manually kickstart a digest, for lack of a better word - if (animations.length === 1) { - this.requestAnimationFrame(); - } - }, - - cancelAnimation: function(chart) { - var index = helpers$1.findIndex(this.animations, function(animation) { - return animation.chart === chart; - }); - - if (index !== -1) { - this.animations.splice(index, 1); - chart.animating = false; - } - }, - - requestAnimationFrame: function() { - var me = this; - if (me.request === null) { - // Skip animation frame requests until the active one is executed. - // This can happen when processing mouse events, e.g. 'mousemove' - // and 'mouseout' events will trigger multiple renders. - me.request = helpers$1.requestAnimFrame.call(window, function() { - me.request = null; - me.startDigest(); - }); - } - }, - - /** - * @private - */ - startDigest: function() { - var me = this; - - me.advance(); - - // Do we have more stuff to animate? - if (me.animations.length > 0) { - me.requestAnimationFrame(); - } - }, - - /** - * @private - */ - advance: function() { - var animations = this.animations; - var animation, chart, numSteps, nextStep; - var i = 0; - - // 1 animation per chart, so we are looping charts here - while (i < animations.length) { - animation = animations[i]; - chart = animation.chart; - numSteps = animation.numSteps; - - // Make sure that currentStep starts at 1 - // https://github.com/chartjs/Chart.js/issues/6104 - nextStep = Math.floor((Date.now() - animation.startTime) / animation.duration * numSteps) + 1; - animation.currentStep = Math.min(nextStep, numSteps); - - helpers$1.callback(animation.render, [chart, animation], chart); - helpers$1.callback(animation.onAnimationProgress, [animation], chart); - - if (animation.currentStep >= numSteps) { - helpers$1.callback(animation.onAnimationComplete, [animation], chart); - chart.animating = false; - animations.splice(i, 1); - } else { - ++i; - } - } - } -}; - -var resolve = helpers$1.options.resolve; - -var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift']; - -/** - * Hooks the array methods that add or remove values ('push', pop', 'shift', 'splice', - * 'unshift') and notify the listener AFTER the array has been altered. Listeners are - * called on the 'onData*' callbacks (e.g. onDataPush, etc.) with same arguments. - */ -function listenArrayEvents(array, listener) { - if (array._chartjs) { - array._chartjs.listeners.push(listener); - return; - } - - Object.defineProperty(array, '_chartjs', { - configurable: true, - enumerable: false, - value: { - listeners: [listener] - } - }); - - arrayEvents.forEach(function(key) { - var method = 'onData' + key.charAt(0).toUpperCase() + key.slice(1); - var base = array[key]; - - Object.defineProperty(array, key, { - configurable: true, - enumerable: false, - value: function() { - var args = Array.prototype.slice.call(arguments); - var res = base.apply(this, args); - - helpers$1.each(array._chartjs.listeners, function(object) { - if (typeof object[method] === 'function') { - object[method].apply(object, args); - } - }); - - return res; - } - }); - }); -} - -/** - * Removes the given array event listener and cleanup extra attached properties (such as - * the _chartjs stub and overridden methods) if array doesn't have any more listeners. - */ -function unlistenArrayEvents(array, listener) { - var stub = array._chartjs; - if (!stub) { - return; - } - - var listeners = stub.listeners; - var index = listeners.indexOf(listener); - if (index !== -1) { - listeners.splice(index, 1); - } - - if (listeners.length > 0) { - return; - } - - arrayEvents.forEach(function(key) { - delete array[key]; - }); - - delete array._chartjs; -} - -// Base class for all dataset controllers (line, bar, etc) -var DatasetController = function(chart, datasetIndex) { - this.initialize(chart, datasetIndex); -}; - -helpers$1.extend(DatasetController.prototype, { - - /** - * Element type used to generate a meta dataset (e.g. Chart.element.Line). - * @type {Chart.core.element} - */ - datasetElementType: null, - - /** - * Element type used to generate a meta data (e.g. Chart.element.Point). - * @type {Chart.core.element} - */ - dataElementType: null, - - /** - * Dataset element option keys to be resolved in _resolveDatasetElementOptions. - * A derived controller may override this to resolve controller-specific options. - * The keys defined here are for backward compatibility for legend styles. - * @private - */ - _datasetElementOptions: [ - 'backgroundColor', - 'borderCapStyle', - 'borderColor', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'borderWidth' - ], - - /** - * Data element option keys to be resolved in _resolveDataElementOptions. - * A derived controller may override this to resolve controller-specific options. - * The keys defined here are for backward compatibility for legend styles. - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'pointStyle' - ], - - initialize: function(chart, datasetIndex) { - var me = this; - me.chart = chart; - me.index = datasetIndex; - me.linkScales(); - me.addElements(); - me._type = me.getMeta().type; - }, - - updateIndex: function(datasetIndex) { - this.index = datasetIndex; - }, - - linkScales: function() { - var me = this; - var meta = me.getMeta(); - var chart = me.chart; - var scales = chart.scales; - var dataset = me.getDataset(); - var scalesOpts = chart.options.scales; - - if (meta.xAxisID === null || !(meta.xAxisID in scales) || dataset.xAxisID) { - meta.xAxisID = dataset.xAxisID || scalesOpts.xAxes[0].id; - } - if (meta.yAxisID === null || !(meta.yAxisID in scales) || dataset.yAxisID) { - meta.yAxisID = dataset.yAxisID || scalesOpts.yAxes[0].id; - } - }, - - getDataset: function() { - return this.chart.data.datasets[this.index]; - }, - - getMeta: function() { - return this.chart.getDatasetMeta(this.index); - }, - - getScaleForId: function(scaleID) { - return this.chart.scales[scaleID]; - }, - - /** - * @private - */ - _getValueScaleId: function() { - return this.getMeta().yAxisID; - }, - - /** - * @private - */ - _getIndexScaleId: function() { - return this.getMeta().xAxisID; - }, - - /** - * @private - */ - _getValueScale: function() { - return this.getScaleForId(this._getValueScaleId()); - }, - - /** - * @private - */ - _getIndexScale: function() { - return this.getScaleForId(this._getIndexScaleId()); - }, - - reset: function() { - this._update(true); - }, - - /** - * @private - */ - destroy: function() { - if (this._data) { - unlistenArrayEvents(this._data, this); - } - }, - - createMetaDataset: function() { - var me = this; - var type = me.datasetElementType; - return type && new type({ - _chart: me.chart, - _datasetIndex: me.index - }); - }, - - createMetaData: function(index) { - var me = this; - var type = me.dataElementType; - return type && new type({ - _chart: me.chart, - _datasetIndex: me.index, - _index: index - }); - }, - - addElements: function() { - var me = this; - var meta = me.getMeta(); - var data = me.getDataset().data || []; - var metaData = meta.data; - var i, ilen; - - for (i = 0, ilen = data.length; i < ilen; ++i) { - metaData[i] = metaData[i] || me.createMetaData(i); - } - - meta.dataset = meta.dataset || me.createMetaDataset(); - }, - - addElementAndReset: function(index) { - var element = this.createMetaData(index); - this.getMeta().data.splice(index, 0, element); - this.updateElement(element, index, true); - }, - - buildOrUpdateElements: function() { - var me = this; - var dataset = me.getDataset(); - var data = dataset.data || (dataset.data = []); - - // In order to correctly handle data addition/deletion animation (an thus simulate - // real-time charts), we need to monitor these data modifications and synchronize - // the internal meta data accordingly. - if (me._data !== data) { - if (me._data) { - // This case happens when the user replaced the data array instance. - unlistenArrayEvents(me._data, me); - } - - if (data && Object.isExtensible(data)) { - listenArrayEvents(data, me); - } - me._data = data; - } - - // Re-sync meta data in case the user replaced the data array or if we missed - // any updates and so make sure that we handle number of datapoints changing. - me.resyncElements(); - }, - - /** - * Returns the merged user-supplied and default dataset-level options - * @private - */ - _configure: function() { - var me = this; - me._config = helpers$1.merge({}, [ - me.chart.options.datasets[me._type], - me.getDataset(), - ], { - merger: function(key, target, source) { - if (key !== '_meta' && key !== 'data') { - helpers$1._merger(key, target, source); - } - } - }); - }, - - _update: function(reset) { - var me = this; - me._configure(); - me._cachedDataOpts = null; - me.update(reset); - }, - - update: helpers$1.noop, - - transition: function(easingValue) { - var meta = this.getMeta(); - var elements = meta.data || []; - var ilen = elements.length; - var i = 0; - - for (; i < ilen; ++i) { - elements[i].transition(easingValue); - } - - if (meta.dataset) { - meta.dataset.transition(easingValue); - } - }, - - draw: function() { - var meta = this.getMeta(); - var elements = meta.data || []; - var ilen = elements.length; - var i = 0; - - if (meta.dataset) { - meta.dataset.draw(); - } - - for (; i < ilen; ++i) { - elements[i].draw(); - } - }, - - /** - * Returns a set of predefined style properties that should be used to represent the dataset - * or the data if the index is specified - * @param {number} index - data index - * @return {IStyleInterface} style object - */ - getStyle: function(index) { - var me = this; - var meta = me.getMeta(); - var dataset = meta.dataset; - var style; - - me._configure(); - if (dataset && index === undefined) { - style = me._resolveDatasetElementOptions(dataset || {}); - } else { - index = index || 0; - style = me._resolveDataElementOptions(meta.data[index] || {}, index); - } - - if (style.fill === false || style.fill === null) { - style.backgroundColor = style.borderColor; - } - - return style; - }, - - /** - * @private - */ - _resolveDatasetElementOptions: function(element, hover) { - var me = this; - var chart = me.chart; - var datasetOpts = me._config; - var custom = element.custom || {}; - var options = chart.options.elements[me.datasetElementType.prototype._type] || {}; - var elementOptions = me._datasetElementOptions; - var values = {}; - var i, ilen, key, readKey; - - // Scriptable options - var context = { - chart: chart, - dataset: me.getDataset(), - datasetIndex: me.index, - hover: hover - }; - - for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { - key = elementOptions[i]; - readKey = hover ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key; - values[key] = resolve([ - custom[readKey], - datasetOpts[readKey], - options[readKey] - ], context); - } - - return values; - }, - - /** - * @private - */ - _resolveDataElementOptions: function(element, index) { - var me = this; - var custom = element && element.custom; - var cached = me._cachedDataOpts; - if (cached && !custom) { - return cached; - } - var chart = me.chart; - var datasetOpts = me._config; - var options = chart.options.elements[me.dataElementType.prototype._type] || {}; - var elementOptions = me._dataElementOptions; - var values = {}; - - // Scriptable options - var context = { - chart: chart, - dataIndex: index, - dataset: me.getDataset(), - datasetIndex: me.index - }; - - // `resolve` sets cacheable to `false` if any option is indexed or scripted - var info = {cacheable: !custom}; - - var keys, i, ilen, key; - - custom = custom || {}; - - if (helpers$1.isArray(elementOptions)) { - for (i = 0, ilen = elementOptions.length; i < ilen; ++i) { - key = elementOptions[i]; - values[key] = resolve([ - custom[key], - datasetOpts[key], - options[key] - ], context, index, info); - } - } else { - keys = Object.keys(elementOptions); - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - values[key] = resolve([ - custom[key], - datasetOpts[elementOptions[key]], - datasetOpts[key], - options[key] - ], context, index, info); - } - } - - if (info.cacheable) { - me._cachedDataOpts = Object.freeze(values); - } - - return values; - }, - - removeHoverStyle: function(element) { - helpers$1.merge(element._model, element.$previousStyle || {}); - delete element.$previousStyle; - }, - - setHoverStyle: function(element) { - var dataset = this.chart.data.datasets[element._datasetIndex]; - var index = element._index; - var custom = element.custom || {}; - var model = element._model; - var getHoverColor = helpers$1.getHoverColor; - - element.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth - }; - - model.backgroundColor = resolve([custom.hoverBackgroundColor, dataset.hoverBackgroundColor, getHoverColor(model.backgroundColor)], undefined, index); - model.borderColor = resolve([custom.hoverBorderColor, dataset.hoverBorderColor, getHoverColor(model.borderColor)], undefined, index); - model.borderWidth = resolve([custom.hoverBorderWidth, dataset.hoverBorderWidth, model.borderWidth], undefined, index); - }, - - /** - * @private - */ - _removeDatasetHoverStyle: function() { - var element = this.getMeta().dataset; - - if (element) { - this.removeHoverStyle(element); - } - }, - - /** - * @private - */ - _setDatasetHoverStyle: function() { - var element = this.getMeta().dataset; - var prev = {}; - var i, ilen, key, keys, hoverOptions, model; - - if (!element) { - return; - } - - model = element._model; - hoverOptions = this._resolveDatasetElementOptions(element, true); - - keys = Object.keys(hoverOptions); - for (i = 0, ilen = keys.length; i < ilen; ++i) { - key = keys[i]; - prev[key] = model[key]; - model[key] = hoverOptions[key]; - } - - element.$previousStyle = prev; - }, - - /** - * @private - */ - resyncElements: function() { - var me = this; - var meta = me.getMeta(); - var data = me.getDataset().data; - var numMeta = meta.data.length; - var numData = data.length; - - if (numData < numMeta) { - meta.data.splice(numData, numMeta - numData); - } else if (numData > numMeta) { - me.insertElements(numMeta, numData - numMeta); - } - }, - - /** - * @private - */ - insertElements: function(start, count) { - for (var i = 0; i < count; ++i) { - this.addElementAndReset(start + i); - } - }, - - /** - * @private - */ - onDataPush: function() { - var count = arguments.length; - this.insertElements(this.getDataset().data.length - count, count); - }, - - /** - * @private - */ - onDataPop: function() { - this.getMeta().data.pop(); - }, - - /** - * @private - */ - onDataShift: function() { - this.getMeta().data.shift(); - }, - - /** - * @private - */ - onDataSplice: function(start, count) { - this.getMeta().data.splice(start, count); - this.insertElements(start, arguments.length - 2); - }, - - /** - * @private - */ - onDataUnshift: function() { - this.insertElements(0, arguments.length); - } -}); - -DatasetController.extend = helpers$1.inherits; - -var core_datasetController = DatasetController; - -var TAU = Math.PI * 2; - -core_defaults._set('global', { - elements: { - arc: { - backgroundColor: core_defaults.global.defaultColor, - borderColor: '#fff', - borderWidth: 2, - borderAlign: 'center' - } - } -}); - -function clipArc(ctx, arc) { - var startAngle = arc.startAngle; - var endAngle = arc.endAngle; - var pixelMargin = arc.pixelMargin; - var angleMargin = pixelMargin / arc.outerRadius; - var x = arc.x; - var y = arc.y; - - // Draw an inner border by cliping the arc and drawing a double-width border - // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders - ctx.beginPath(); - ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin); - if (arc.innerRadius > pixelMargin) { - angleMargin = pixelMargin / arc.innerRadius; - ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true); - } else { - ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2); - } - ctx.closePath(); - ctx.clip(); -} - -function drawFullCircleBorders(ctx, vm, arc, inner) { - var endAngle = arc.endAngle; - var i; - - if (inner) { - arc.endAngle = arc.startAngle + TAU; - clipArc(ctx, arc); - arc.endAngle = endAngle; - if (arc.endAngle === arc.startAngle && arc.fullCircles) { - arc.endAngle += TAU; - arc.fullCircles--; - } - } - - ctx.beginPath(); - ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true); - for (i = 0; i < arc.fullCircles; ++i) { - ctx.stroke(); - } - - ctx.beginPath(); - ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU); - for (i = 0; i < arc.fullCircles; ++i) { - ctx.stroke(); - } -} - -function drawBorder(ctx, vm, arc) { - var inner = vm.borderAlign === 'inner'; - - if (inner) { - ctx.lineWidth = vm.borderWidth * 2; - ctx.lineJoin = 'round'; - } else { - ctx.lineWidth = vm.borderWidth; - ctx.lineJoin = 'bevel'; - } - - if (arc.fullCircles) { - drawFullCircleBorders(ctx, vm, arc, inner); - } - - if (inner) { - clipArc(ctx, arc); - } - - ctx.beginPath(); - ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle); - ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); - ctx.closePath(); - ctx.stroke(); -} - -var element_arc = core_element.extend({ - _type: 'arc', - - inLabelRange: function(mouseX) { - var vm = this._view; - - if (vm) { - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); - } - return false; - }, - - inRange: function(chartX, chartY) { - var vm = this._view; - - if (vm) { - var pointRelativePosition = helpers$1.getAngleFromPoint(vm, {x: chartX, y: chartY}); - var angle = pointRelativePosition.angle; - var distance = pointRelativePosition.distance; - - // Sanitise angle range - var startAngle = vm.startAngle; - var endAngle = vm.endAngle; - while (endAngle < startAngle) { - endAngle += TAU; - } - while (angle > endAngle) { - angle -= TAU; - } - while (angle < startAngle) { - angle += TAU; - } - - // Check if within the range of the open/close angle - var betweenAngles = (angle >= startAngle && angle <= endAngle); - var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius); - - return (betweenAngles && withinRadius); - } - return false; - }, - - getCenterPoint: function() { - var vm = this._view; - var halfAngle = (vm.startAngle + vm.endAngle) / 2; - var halfRadius = (vm.innerRadius + vm.outerRadius) / 2; - return { - x: vm.x + Math.cos(halfAngle) * halfRadius, - y: vm.y + Math.sin(halfAngle) * halfRadius - }; - }, - - getArea: function() { - var vm = this._view; - return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2)); - }, - - tooltipPosition: function() { - var vm = this._view; - var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2); - var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; - - return { - x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), - y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) - }; - }, - - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0; - var arc = { - x: vm.x, - y: vm.y, - innerRadius: vm.innerRadius, - outerRadius: Math.max(vm.outerRadius - pixelMargin, 0), - pixelMargin: pixelMargin, - startAngle: vm.startAngle, - endAngle: vm.endAngle, - fullCircles: Math.floor(vm.circumference / TAU) - }; - var i; - - ctx.save(); - - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - - if (arc.fullCircles) { - arc.endAngle = arc.startAngle + TAU; - ctx.beginPath(); - ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); - ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); - ctx.closePath(); - for (i = 0; i < arc.fullCircles; ++i) { - ctx.fill(); - } - arc.endAngle = arc.startAngle + vm.circumference % TAU; - } - - ctx.beginPath(); - ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle); - ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true); - ctx.closePath(); - ctx.fill(); - - if (vm.borderWidth) { - drawBorder(ctx, vm, arc); - } - - ctx.restore(); - } -}); - -var valueOrDefault$1 = helpers$1.valueOrDefault; - -var defaultColor = core_defaults.global.defaultColor; - -core_defaults._set('global', { - elements: { - line: { - tension: 0.4, - backgroundColor: defaultColor, - borderWidth: 3, - borderColor: defaultColor, - borderCapStyle: 'butt', - borderDash: [], - borderDashOffset: 0.0, - borderJoinStyle: 'miter', - capBezierPoints: true, - fill: true, // do we fill in the area between the line and its base axis - } - } -}); - -var element_line = core_element.extend({ - _type: 'line', - - draw: function() { - var me = this; - var vm = me._view; - var ctx = me._chart.ctx; - var spanGaps = vm.spanGaps; - var points = me._children.slice(); // clone array - var globalDefaults = core_defaults.global; - var globalOptionLineElements = globalDefaults.elements.line; - var lastDrawnIndex = -1; - var closePath = me._loop; - var index, previous, currentVM; - - if (!points.length) { - return; - } - - if (me._loop) { - for (index = 0; index < points.length; ++index) { - previous = helpers$1.previousItem(points, index); - // If the line has an open path, shift the point array - if (!points[index]._view.skip && previous._view.skip) { - points = points.slice(index).concat(points.slice(0, index)); - closePath = spanGaps; - break; - } - } - // If the line has a close path, add the first point again - if (closePath) { - points.push(points[0]); - } - } - - ctx.save(); - - // Stroke Line Options - ctx.lineCap = vm.borderCapStyle || globalOptionLineElements.borderCapStyle; - - // IE 9 and 10 do not support line dash - if (ctx.setLineDash) { - ctx.setLineDash(vm.borderDash || globalOptionLineElements.borderDash); - } - - ctx.lineDashOffset = valueOrDefault$1(vm.borderDashOffset, globalOptionLineElements.borderDashOffset); - ctx.lineJoin = vm.borderJoinStyle || globalOptionLineElements.borderJoinStyle; - ctx.lineWidth = valueOrDefault$1(vm.borderWidth, globalOptionLineElements.borderWidth); - ctx.strokeStyle = vm.borderColor || globalDefaults.defaultColor; - - // Stroke Line - ctx.beginPath(); - - // First point moves to it's starting position no matter what - currentVM = points[0]._view; - if (!currentVM.skip) { - ctx.moveTo(currentVM.x, currentVM.y); - lastDrawnIndex = 0; - } - - for (index = 1; index < points.length; ++index) { - currentVM = points[index]._view; - previous = lastDrawnIndex === -1 ? helpers$1.previousItem(points, index) : points[lastDrawnIndex]; - - if (!currentVM.skip) { - if ((lastDrawnIndex !== (index - 1) && !spanGaps) || lastDrawnIndex === -1) { - // There was a gap and this is the first point after the gap - ctx.moveTo(currentVM.x, currentVM.y); - } else { - // Line to next point - helpers$1.canvas.lineTo(ctx, previous._view, currentVM); - } - lastDrawnIndex = index; - } - } - - if (closePath) { - ctx.closePath(); - } - - ctx.stroke(); - ctx.restore(); - } -}); - -var valueOrDefault$2 = helpers$1.valueOrDefault; - -var defaultColor$1 = core_defaults.global.defaultColor; - -core_defaults._set('global', { - elements: { - point: { - radius: 3, - pointStyle: 'circle', - backgroundColor: defaultColor$1, - borderColor: defaultColor$1, - borderWidth: 1, - // Hover - hitRadius: 1, - hoverRadius: 4, - hoverBorderWidth: 1 - } - } -}); - -function xRange(mouseX) { - var vm = this._view; - return vm ? (Math.abs(mouseX - vm.x) < vm.radius + vm.hitRadius) : false; -} - -function yRange(mouseY) { - var vm = this._view; - return vm ? (Math.abs(mouseY - vm.y) < vm.radius + vm.hitRadius) : false; -} - -var element_point = core_element.extend({ - _type: 'point', - - inRange: function(mouseX, mouseY) { - var vm = this._view; - return vm ? ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(vm.hitRadius + vm.radius, 2)) : false; - }, - - inLabelRange: xRange, - inXRange: xRange, - inYRange: yRange, - - getCenterPoint: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y - }; - }, - - getArea: function() { - return Math.PI * Math.pow(this._view.radius, 2); - }, - - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y, - padding: vm.radius + vm.borderWidth - }; - }, - - draw: function(chartArea) { - var vm = this._view; - var ctx = this._chart.ctx; - var pointStyle = vm.pointStyle; - var rotation = vm.rotation; - var radius = vm.radius; - var x = vm.x; - var y = vm.y; - var globalDefaults = core_defaults.global; - var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow - - if (vm.skip) { - return; - } - - // Clipping for Points. - if (chartArea === undefined || helpers$1.canvas._isPointInArea(vm, chartArea)) { - ctx.strokeStyle = vm.borderColor || defaultColor; - ctx.lineWidth = valueOrDefault$2(vm.borderWidth, globalDefaults.elements.point.borderWidth); - ctx.fillStyle = vm.backgroundColor || defaultColor; - helpers$1.canvas.drawPoint(ctx, pointStyle, radius, x, y, rotation); - } - } -}); - -var defaultColor$2 = core_defaults.global.defaultColor; - -core_defaults._set('global', { - elements: { - rectangle: { - backgroundColor: defaultColor$2, - borderColor: defaultColor$2, - borderSkipped: 'bottom', - borderWidth: 0 - } - } -}); - -function isVertical(vm) { - return vm && vm.width !== undefined; -} - -/** - * Helper function to get the bounds of the bar regardless of the orientation - * @param bar {Chart.Element.Rectangle} the bar - * @return {Bounds} bounds of the bar - * @private - */ -function getBarBounds(vm) { - var x1, x2, y1, y2, half; - - if (isVertical(vm)) { - half = vm.width / 2; - x1 = vm.x - half; - x2 = vm.x + half; - y1 = Math.min(vm.y, vm.base); - y2 = Math.max(vm.y, vm.base); - } else { - half = vm.height / 2; - x1 = Math.min(vm.x, vm.base); - x2 = Math.max(vm.x, vm.base); - y1 = vm.y - half; - y2 = vm.y + half; - } - - return { - left: x1, - top: y1, - right: x2, - bottom: y2 - }; -} - -function swap(orig, v1, v2) { - return orig === v1 ? v2 : orig === v2 ? v1 : orig; -} - -function parseBorderSkipped(vm) { - var edge = vm.borderSkipped; - var res = {}; - - if (!edge) { - return res; - } - - if (vm.horizontal) { - if (vm.base > vm.x) { - edge = swap(edge, 'left', 'right'); - } - } else if (vm.base < vm.y) { - edge = swap(edge, 'bottom', 'top'); - } - - res[edge] = true; - return res; -} - -function parseBorderWidth(vm, maxW, maxH) { - var value = vm.borderWidth; - var skip = parseBorderSkipped(vm); - var t, r, b, l; - - if (helpers$1.isObject(value)) { - t = +value.top || 0; - r = +value.right || 0; - b = +value.bottom || 0; - l = +value.left || 0; - } else { - t = r = b = l = +value || 0; - } - - return { - t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, - r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, - b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, - l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l - }; -} - -function boundingRects(vm) { - var bounds = getBarBounds(vm); - var width = bounds.right - bounds.left; - var height = bounds.bottom - bounds.top; - var border = parseBorderWidth(vm, width / 2, height / 2); - - return { - outer: { - x: bounds.left, - y: bounds.top, - w: width, - h: height - }, - inner: { - x: bounds.left + border.l, - y: bounds.top + border.t, - w: width - border.l - border.r, - h: height - border.t - border.b - } - }; -} - -function inRange(vm, x, y) { - var skipX = x === null; - var skipY = y === null; - var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); - - return bounds - && (skipX || x >= bounds.left && x <= bounds.right) - && (skipY || y >= bounds.top && y <= bounds.bottom); -} - -var element_rectangle = core_element.extend({ - _type: 'rectangle', - - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - var rects = boundingRects(vm); - var outer = rects.outer; - var inner = rects.inner; - - ctx.fillStyle = vm.backgroundColor; - ctx.fillRect(outer.x, outer.y, outer.w, outer.h); - - if (outer.w === inner.w && outer.h === inner.h) { - return; - } - - ctx.save(); - ctx.beginPath(); - ctx.rect(outer.x, outer.y, outer.w, outer.h); - ctx.clip(); - ctx.fillStyle = vm.borderColor; - ctx.rect(inner.x, inner.y, inner.w, inner.h); - ctx.fill('evenodd'); - ctx.restore(); - }, - - height: function() { - var vm = this._view; - return vm.base - vm.y; - }, - - inRange: function(mouseX, mouseY) { - return inRange(this._view, mouseX, mouseY); - }, - - inLabelRange: function(mouseX, mouseY) { - var vm = this._view; - return isVertical(vm) - ? inRange(vm, mouseX, null) - : inRange(vm, null, mouseY); - }, - - inXRange: function(mouseX) { - return inRange(this._view, mouseX, null); - }, - - inYRange: function(mouseY) { - return inRange(this._view, null, mouseY); - }, - - getCenterPoint: function() { - var vm = this._view; - var x, y; - if (isVertical(vm)) { - x = vm.x; - y = (vm.y + vm.base) / 2; - } else { - x = (vm.x + vm.base) / 2; - y = vm.y; - } - - return {x: x, y: y}; - }, - - getArea: function() { - var vm = this._view; - - return isVertical(vm) - ? vm.width * Math.abs(vm.y - vm.base) - : vm.height * Math.abs(vm.x - vm.base); - }, - - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y - }; - } -}); - -var elements = {}; -var Arc = element_arc; -var Line = element_line; -var Point = element_point; -var Rectangle = element_rectangle; -elements.Arc = Arc; -elements.Line = Line; -elements.Point = Point; -elements.Rectangle = Rectangle; - -var deprecated = helpers$1._deprecated; -var valueOrDefault$3 = helpers$1.valueOrDefault; - -core_defaults._set('bar', { - hover: { - mode: 'label' - }, - - scales: { - xAxes: [{ - type: 'category', - offset: true, - gridLines: { - offsetGridLines: true - } - }], - - yAxes: [{ - type: 'linear' - }] - } -}); - -core_defaults._set('global', { - datasets: { - bar: { - categoryPercentage: 0.8, - barPercentage: 0.9 - } - } -}); - -/** - * Computes the "optimal" sample size to maintain bars equally sized while preventing overlap. - * @private - */ -function computeMinSampleSize(scale, pixels) { - var min = scale._length; - var prev, curr, i, ilen; - - for (i = 1, ilen = pixels.length; i < ilen; ++i) { - min = Math.min(min, Math.abs(pixels[i] - pixels[i - 1])); - } - - for (i = 0, ilen = scale.getTicks().length; i < ilen; ++i) { - curr = scale.getPixelForTick(i); - min = i > 0 ? Math.min(min, Math.abs(curr - prev)) : min; - prev = curr; - } - - return min; -} - -/** - * Computes an "ideal" category based on the absolute bar thickness or, if undefined or null, - * uses the smallest interval (see computeMinSampleSize) that prevents bar overlapping. This - * mode currently always generates bars equally sized (until we introduce scriptable options?). - * @private - */ -function computeFitCategoryTraits(index, ruler, options) { - var thickness = options.barThickness; - var count = ruler.stackCount; - var curr = ruler.pixels[index]; - var min = helpers$1.isNullOrUndef(thickness) - ? computeMinSampleSize(ruler.scale, ruler.pixels) - : -1; - var size, ratio; - - if (helpers$1.isNullOrUndef(thickness)) { - size = min * options.categoryPercentage; - ratio = options.barPercentage; - } else { - // When bar thickness is enforced, category and bar percentages are ignored. - // Note(SB): we could add support for relative bar thickness (e.g. barThickness: '50%') - // and deprecate barPercentage since this value is ignored when thickness is absolute. - size = thickness * count; - ratio = 1; - } - - return { - chunk: size / count, - ratio: ratio, - start: curr - (size / 2) - }; -} - -/** - * Computes an "optimal" category that globally arranges bars side by side (no gap when - * percentage options are 1), based on the previous and following categories. This mode - * generates bars with different widths when data are not evenly spaced. - * @private - */ -function computeFlexCategoryTraits(index, ruler, options) { - var pixels = ruler.pixels; - var curr = pixels[index]; - var prev = index > 0 ? pixels[index - 1] : null; - var next = index < pixels.length - 1 ? pixels[index + 1] : null; - var percent = options.categoryPercentage; - var start, size; - - if (prev === null) { - // first data: its size is double based on the next point or, - // if it's also the last data, we use the scale size. - prev = curr - (next === null ? ruler.end - ruler.start : next - curr); - } - - if (next === null) { - // last data: its size is also double based on the previous point. - next = curr + curr - prev; - } - - start = curr - (curr - Math.min(prev, next)) / 2 * percent; - size = Math.abs(next - prev) / 2 * percent; - - return { - chunk: size / ruler.stackCount, - ratio: options.barPercentage, - start: start - }; -} - -var controller_bar = core_datasetController.extend({ - - dataElementType: elements.Rectangle, - - /** - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderSkipped', - 'borderWidth', - 'barPercentage', - 'barThickness', - 'categoryPercentage', - 'maxBarThickness', - 'minBarLength' - ], - - initialize: function() { - var me = this; - var meta, scaleOpts; - - core_datasetController.prototype.initialize.apply(me, arguments); - - meta = me.getMeta(); - meta.stack = me.getDataset().stack; - meta.bar = true; - - scaleOpts = me._getIndexScale().options; - deprecated('bar chart', scaleOpts.barPercentage, 'scales.[x/y]Axes.barPercentage', 'dataset.barPercentage'); - deprecated('bar chart', scaleOpts.barThickness, 'scales.[x/y]Axes.barThickness', 'dataset.barThickness'); - deprecated('bar chart', scaleOpts.categoryPercentage, 'scales.[x/y]Axes.categoryPercentage', 'dataset.categoryPercentage'); - deprecated('bar chart', me._getValueScale().options.minBarLength, 'scales.[x/y]Axes.minBarLength', 'dataset.minBarLength'); - deprecated('bar chart', scaleOpts.maxBarThickness, 'scales.[x/y]Axes.maxBarThickness', 'dataset.maxBarThickness'); - }, - - update: function(reset) { - var me = this; - var rects = me.getMeta().data; - var i, ilen; - - me._ruler = me.getRuler(); - - for (i = 0, ilen = rects.length; i < ilen; ++i) { - me.updateElement(rects[i], i, reset); - } - }, - - updateElement: function(rectangle, index, reset) { - var me = this; - var meta = me.getMeta(); - var dataset = me.getDataset(); - var options = me._resolveDataElementOptions(rectangle, index); - - rectangle._xScale = me.getScaleForId(meta.xAxisID); - rectangle._yScale = me.getScaleForId(meta.yAxisID); - rectangle._datasetIndex = me.index; - rectangle._index = index; - rectangle._model = { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderSkipped: options.borderSkipped, - borderWidth: options.borderWidth, - datasetLabel: dataset.label, - label: me.chart.data.labels[index] - }; - - if (helpers$1.isArray(dataset.data[index])) { - rectangle._model.borderSkipped = null; - } - - me._updateElementGeometry(rectangle, index, reset, options); - - rectangle.pivot(); - }, - - /** - * @private - */ - _updateElementGeometry: function(rectangle, index, reset, options) { - var me = this; - var model = rectangle._model; - var vscale = me._getValueScale(); - var base = vscale.getBasePixel(); - var horizontal = vscale.isHorizontal(); - var ruler = me._ruler || me.getRuler(); - var vpixels = me.calculateBarValuePixels(me.index, index, options); - var ipixels = me.calculateBarIndexPixels(me.index, index, ruler, options); - - model.horizontal = horizontal; - model.base = reset ? base : vpixels.base; - model.x = horizontal ? reset ? base : vpixels.head : ipixels.center; - model.y = horizontal ? ipixels.center : reset ? base : vpixels.head; - model.height = horizontal ? ipixels.size : undefined; - model.width = horizontal ? undefined : ipixels.size; - }, - - /** - * Returns the stacks based on groups and bar visibility. - * @param {number} [last] - The dataset index - * @returns {string[]} The list of stack IDs - * @private - */ - _getStacks: function(last) { - var me = this; - var scale = me._getIndexScale(); - var metasets = scale._getMatchingVisibleMetas(me._type); - var stacked = scale.options.stacked; - var ilen = metasets.length; - var stacks = []; - var i, meta; - - for (i = 0; i < ilen; ++i) { - meta = metasets[i]; - // stacked | meta.stack - // | found | not found | undefined - // false | x | x | x - // true | | x | - // undefined | | x | x - if (stacked === false || stacks.indexOf(meta.stack) === -1 || - (stacked === undefined && meta.stack === undefined)) { - stacks.push(meta.stack); - } - if (meta.index === last) { - break; - } - } - - return stacks; - }, - - /** - * Returns the effective number of stacks based on groups and bar visibility. - * @private - */ - getStackCount: function() { - return this._getStacks().length; - }, - - /** - * Returns the stack index for the given dataset based on groups and bar visibility. - * @param {number} [datasetIndex] - The dataset index - * @param {string} [name] - The stack name to find - * @returns {number} The stack index - * @private - */ - getStackIndex: function(datasetIndex, name) { - var stacks = this._getStacks(datasetIndex); - var index = (name !== undefined) - ? stacks.indexOf(name) - : -1; // indexOf returns -1 if element is not present - - return (index === -1) - ? stacks.length - 1 - : index; - }, - - /** - * @private - */ - getRuler: function() { - var me = this; - var scale = me._getIndexScale(); - var pixels = []; - var i, ilen; - - for (i = 0, ilen = me.getMeta().data.length; i < ilen; ++i) { - pixels.push(scale.getPixelForValue(null, i, me.index)); - } - - return { - pixels: pixels, - start: scale._startPixel, - end: scale._endPixel, - stackCount: me.getStackCount(), - scale: scale - }; - }, - - /** - * Note: pixel values are not clamped to the scale area. - * @private - */ - calculateBarValuePixels: function(datasetIndex, index, options) { - var me = this; - var chart = me.chart; - var scale = me._getValueScale(); - var isHorizontal = scale.isHorizontal(); - var datasets = chart.data.datasets; - var metasets = scale._getMatchingVisibleMetas(me._type); - var value = scale._parseValue(datasets[datasetIndex].data[index]); - var minBarLength = options.minBarLength; - var stacked = scale.options.stacked; - var stack = me.getMeta().stack; - var start = value.start === undefined ? 0 : value.max >= 0 && value.min >= 0 ? value.min : value.max; - var length = value.start === undefined ? value.end : value.max >= 0 && value.min >= 0 ? value.max - value.min : value.min - value.max; - var ilen = metasets.length; - var i, imeta, ivalue, base, head, size, stackLength; - - if (stacked || (stacked === undefined && stack !== undefined)) { - for (i = 0; i < ilen; ++i) { - imeta = metasets[i]; - - if (imeta.index === datasetIndex) { - break; - } - - if (imeta.stack === stack) { - stackLength = scale._parseValue(datasets[imeta.index].data[index]); - ivalue = stackLength.start === undefined ? stackLength.end : stackLength.min >= 0 && stackLength.max >= 0 ? stackLength.max : stackLength.min; - - if ((value.min < 0 && ivalue < 0) || (value.max >= 0 && ivalue > 0)) { - start += ivalue; - } - } - } - } - - base = scale.getPixelForValue(start); - head = scale.getPixelForValue(start + length); - size = head - base; - - if (minBarLength !== undefined && Math.abs(size) < minBarLength) { - size = minBarLength; - if (length >= 0 && !isHorizontal || length < 0 && isHorizontal) { - head = base - minBarLength; - } else { - head = base + minBarLength; - } - } - - return { - size: size, - base: base, - head: head, - center: head + size / 2 - }; - }, - - /** - * @private - */ - calculateBarIndexPixels: function(datasetIndex, index, ruler, options) { - var me = this; - var range = options.barThickness === 'flex' - ? computeFlexCategoryTraits(index, ruler, options) - : computeFitCategoryTraits(index, ruler, options); - - var stackIndex = me.getStackIndex(datasetIndex, me.getMeta().stack); - var center = range.start + (range.chunk * stackIndex) + (range.chunk / 2); - var size = Math.min( - valueOrDefault$3(options.maxBarThickness, Infinity), - range.chunk * range.ratio); - - return { - base: center - size / 2, - head: center + size / 2, - center: center, - size: size - }; - }, - - draw: function() { - var me = this; - var chart = me.chart; - var scale = me._getValueScale(); - var rects = me.getMeta().data; - var dataset = me.getDataset(); - var ilen = rects.length; - var i = 0; - - helpers$1.canvas.clipArea(chart.ctx, chart.chartArea); - - for (; i < ilen; ++i) { - var val = scale._parseValue(dataset.data[i]); - if (!isNaN(val.min) && !isNaN(val.max)) { - rects[i].draw(); - } - } - - helpers$1.canvas.unclipArea(chart.ctx); - }, - - /** - * @private - */ - _resolveDataElementOptions: function() { - var me = this; - var values = helpers$1.extend({}, core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments)); - var indexOpts = me._getIndexScale().options; - var valueOpts = me._getValueScale().options; - - values.barPercentage = valueOrDefault$3(indexOpts.barPercentage, values.barPercentage); - values.barThickness = valueOrDefault$3(indexOpts.barThickness, values.barThickness); - values.categoryPercentage = valueOrDefault$3(indexOpts.categoryPercentage, values.categoryPercentage); - values.maxBarThickness = valueOrDefault$3(indexOpts.maxBarThickness, values.maxBarThickness); - values.minBarLength = valueOrDefault$3(valueOpts.minBarLength, values.minBarLength); - - return values; - } - -}); - -var valueOrDefault$4 = helpers$1.valueOrDefault; -var resolve$1 = helpers$1.options.resolve; - -core_defaults._set('bubble', { - hover: { - mode: 'single' - }, - - scales: { - xAxes: [{ - type: 'linear', // bubble should probably use a linear scale by default - position: 'bottom', - id: 'x-axis-0' // need an ID so datasets can reference the scale - }], - yAxes: [{ - type: 'linear', - position: 'left', - id: 'y-axis-0' - }] - }, - - tooltips: { - callbacks: { - title: function() { - // Title doesn't make sense for scatter since we format the data as a point - return ''; - }, - label: function(item, data) { - var datasetLabel = data.datasets[item.datasetIndex].label || ''; - var dataPoint = data.datasets[item.datasetIndex].data[item.index]; - return datasetLabel + ': (' + item.xLabel + ', ' + item.yLabel + ', ' + dataPoint.r + ')'; - } - } - } -}); - -var controller_bubble = core_datasetController.extend({ - /** - * @protected - */ - dataElementType: elements.Point, - - /** - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'hoverBackgroundColor', - 'hoverBorderColor', - 'hoverBorderWidth', - 'hoverRadius', - 'hitRadius', - 'pointStyle', - 'rotation' - ], - - /** - * @protected - */ - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var points = meta.data; - - // Update Points - helpers$1.each(points, function(point, index) { - me.updateElement(point, index, reset); - }); - }, - - /** - * @protected - */ - updateElement: function(point, index, reset) { - var me = this; - var meta = me.getMeta(); - var custom = point.custom || {}; - var xScale = me.getScaleForId(meta.xAxisID); - var yScale = me.getScaleForId(meta.yAxisID); - var options = me._resolveDataElementOptions(point, index); - var data = me.getDataset().data[index]; - var dsIndex = me.index; - - var x = reset ? xScale.getPixelForDecimal(0.5) : xScale.getPixelForValue(typeof data === 'object' ? data : NaN, index, dsIndex); - var y = reset ? yScale.getBasePixel() : yScale.getPixelForValue(data, index, dsIndex); - - point._xScale = xScale; - point._yScale = yScale; - point._options = options; - point._datasetIndex = dsIndex; - point._index = index; - point._model = { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - hitRadius: options.hitRadius, - pointStyle: options.pointStyle, - rotation: options.rotation, - radius: reset ? 0 : options.radius, - skip: custom.skip || isNaN(x) || isNaN(y), - x: x, - y: y, - }; - - point.pivot(); - }, - - /** - * @protected - */ - setHoverStyle: function(point) { - var model = point._model; - var options = point._options; - var getHoverColor = helpers$1.getHoverColor; - - point.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; - - model.backgroundColor = valueOrDefault$4(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault$4(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault$4(options.hoverBorderWidth, options.borderWidth); - model.radius = options.radius + options.hoverRadius; - }, - - /** - * @private - */ - _resolveDataElementOptions: function(point, index) { - var me = this; - var chart = me.chart; - var dataset = me.getDataset(); - var custom = point.custom || {}; - var data = dataset.data[index] || {}; - var values = core_datasetController.prototype._resolveDataElementOptions.apply(me, arguments); - - // Scriptable options - var context = { - chart: chart, - dataIndex: index, - dataset: dataset, - datasetIndex: me.index - }; - - // In case values were cached (and thus frozen), we need to clone the values - if (me._cachedDataOpts === values) { - values = helpers$1.extend({}, values); - } - - // Custom radius resolution - values.radius = resolve$1([ - custom.radius, - data.r, - me._config.radius, - chart.options.elements.point.radius - ], context, index); - - return values; - } -}); - -var valueOrDefault$5 = helpers$1.valueOrDefault; - -var PI$1 = Math.PI; -var DOUBLE_PI$1 = PI$1 * 2; -var HALF_PI$1 = PI$1 / 2; - -core_defaults._set('doughnut', { - animation: { - // Boolean - Whether we animate the rotation of the Doughnut - animateRotate: true, - // Boolean - Whether we animate scaling the Doughnut from the centre - animateScale: false - }, - hover: { - mode: 'single' - }, - legendCallback: function(chart) { - var list = document.createElement('ul'); - var data = chart.data; - var datasets = data.datasets; - var labels = data.labels; - var i, ilen, listItem, listItemSpan; - - list.setAttribute('class', chart.id + '-legend'); - if (datasets.length) { - for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { - listItem = list.appendChild(document.createElement('li')); - listItemSpan = listItem.appendChild(document.createElement('span')); - listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; - if (labels[i]) { - listItem.appendChild(document.createTextNode(labels[i])); - } - } - } - - return list.outerHTML; - }, - legend: { - labels: { - generateLabels: function(chart) { - var data = chart.data; - if (data.labels.length && data.datasets.length) { - return data.labels.map(function(label, i) { - var meta = chart.getDatasetMeta(0); - var style = meta.controller.getStyle(i); - - return { - text: label, - fillStyle: style.backgroundColor, - strokeStyle: style.borderColor, - lineWidth: style.borderWidth, - hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, - - // Extra data used for toggling the correct item - index: i - }; - }); - } - return []; - } - }, - - onClick: function(e, legendItem) { - var index = legendItem.index; - var chart = this.chart; - var i, ilen, meta; - - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - // toggle visibility of index if exists - if (meta.data[index]) { - meta.data[index].hidden = !meta.data[index].hidden; - } - } - - chart.update(); - } - }, - - // The percentage of the chart that we cut out of the middle. - cutoutPercentage: 50, - - // The rotation of the chart, where the first data arc begins. - rotation: -HALF_PI$1, - - // The total circumference of the chart. - circumference: DOUBLE_PI$1, - - // Need to override these to give a nice default - tooltips: { - callbacks: { - title: function() { - return ''; - }, - label: function(tooltipItem, data) { - var dataLabel = data.labels[tooltipItem.index]; - var value = ': ' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; - - if (helpers$1.isArray(dataLabel)) { - // show value on first line of multiline label - // need to clone because we are changing the value - dataLabel = dataLabel.slice(); - dataLabel[0] += value; - } else { - dataLabel += value; - } - - return dataLabel; - } - } - } -}); - -var controller_doughnut = core_datasetController.extend({ - - dataElementType: elements.Arc, - - linkScales: helpers$1.noop, - - /** - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'borderAlign', - 'hoverBackgroundColor', - 'hoverBorderColor', - 'hoverBorderWidth', - ], - - // Get index of the dataset in relation to the visible datasets. This allows determining the inner and outer radius correctly - getRingIndex: function(datasetIndex) { - var ringIndex = 0; - - for (var j = 0; j < datasetIndex; ++j) { - if (this.chart.isDatasetVisible(j)) { - ++ringIndex; - } - } - - return ringIndex; - }, - - update: function(reset) { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var ratioX = 1; - var ratioY = 1; - var offsetX = 0; - var offsetY = 0; - var meta = me.getMeta(); - var arcs = meta.data; - var cutout = opts.cutoutPercentage / 100 || 0; - var circumference = opts.circumference; - var chartWeight = me._getRingWeight(me.index); - var maxWidth, maxHeight, i, ilen; - - // If the chart's circumference isn't a full circle, calculate size as a ratio of the width/height of the arc - if (circumference < DOUBLE_PI$1) { - var startAngle = opts.rotation % DOUBLE_PI$1; - startAngle += startAngle >= PI$1 ? -DOUBLE_PI$1 : startAngle < -PI$1 ? DOUBLE_PI$1 : 0; - var endAngle = startAngle + circumference; - var startX = Math.cos(startAngle); - var startY = Math.sin(startAngle); - var endX = Math.cos(endAngle); - var endY = Math.sin(endAngle); - var contains0 = (startAngle <= 0 && endAngle >= 0) || endAngle >= DOUBLE_PI$1; - var contains90 = (startAngle <= HALF_PI$1 && endAngle >= HALF_PI$1) || endAngle >= DOUBLE_PI$1 + HALF_PI$1; - var contains180 = startAngle === -PI$1 || endAngle >= PI$1; - var contains270 = (startAngle <= -HALF_PI$1 && endAngle >= -HALF_PI$1) || endAngle >= PI$1 + HALF_PI$1; - var minX = contains180 ? -1 : Math.min(startX, startX * cutout, endX, endX * cutout); - var minY = contains270 ? -1 : Math.min(startY, startY * cutout, endY, endY * cutout); - var maxX = contains0 ? 1 : Math.max(startX, startX * cutout, endX, endX * cutout); - var maxY = contains90 ? 1 : Math.max(startY, startY * cutout, endY, endY * cutout); - ratioX = (maxX - minX) / 2; - ratioY = (maxY - minY) / 2; - offsetX = -(maxX + minX) / 2; - offsetY = -(maxY + minY) / 2; - } - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); - } - - chart.borderWidth = me.getMaxBorderWidth(); - maxWidth = (chartArea.right - chartArea.left - chart.borderWidth) / ratioX; - maxHeight = (chartArea.bottom - chartArea.top - chart.borderWidth) / ratioY; - chart.outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0); - chart.innerRadius = Math.max(chart.outerRadius * cutout, 0); - chart.radiusLength = (chart.outerRadius - chart.innerRadius) / (me._getVisibleDatasetWeightTotal() || 1); - chart.offsetX = offsetX * chart.outerRadius; - chart.offsetY = offsetY * chart.outerRadius; - - meta.total = me.calculateTotal(); - - me.outerRadius = chart.outerRadius - chart.radiusLength * me._getRingWeightOffset(me.index); - me.innerRadius = Math.max(me.outerRadius - chart.radiusLength * chartWeight, 0); - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - me.updateElement(arcs[i], i, reset); - } - }, - - updateElement: function(arc, index, reset) { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var animationOpts = opts.animation; - var centerX = (chartArea.left + chartArea.right) / 2; - var centerY = (chartArea.top + chartArea.bottom) / 2; - var startAngle = opts.rotation; // non reset case handled later - var endAngle = opts.rotation; // non reset case handled later - var dataset = me.getDataset(); - var circumference = reset && animationOpts.animateRotate ? 0 : arc.hidden ? 0 : me.calculateCircumference(dataset.data[index]) * (opts.circumference / DOUBLE_PI$1); - var innerRadius = reset && animationOpts.animateScale ? 0 : me.innerRadius; - var outerRadius = reset && animationOpts.animateScale ? 0 : me.outerRadius; - var options = arc._options || {}; - - helpers$1.extend(arc, { - // Utility - _datasetIndex: me.index, - _index: index, - - // Desired view properties - _model: { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - borderAlign: options.borderAlign, - x: centerX + chart.offsetX, - y: centerY + chart.offsetY, - startAngle: startAngle, - endAngle: endAngle, - circumference: circumference, - outerRadius: outerRadius, - innerRadius: innerRadius, - label: helpers$1.valueAtIndexOrDefault(dataset.label, index, chart.data.labels[index]) - } - }); - - var model = arc._model; - - // Set correct angles if not resetting - if (!reset || !animationOpts.animateRotate) { - if (index === 0) { - model.startAngle = opts.rotation; - } else { - model.startAngle = me.getMeta().data[index - 1]._model.endAngle; - } - - model.endAngle = model.startAngle + model.circumference; - } - - arc.pivot(); - }, - - calculateTotal: function() { - var dataset = this.getDataset(); - var meta = this.getMeta(); - var total = 0; - var value; - - helpers$1.each(meta.data, function(element, index) { - value = dataset.data[index]; - if (!isNaN(value) && !element.hidden) { - total += Math.abs(value); - } - }); - - /* if (total === 0) { - total = NaN; - }*/ - - return total; - }, - - calculateCircumference: function(value) { - var total = this.getMeta().total; - if (total > 0 && !isNaN(value)) { - return DOUBLE_PI$1 * (Math.abs(value) / total); - } - return 0; - }, - - // gets the max border or hover width to properly scale pie charts - getMaxBorderWidth: function(arcs) { - var me = this; - var max = 0; - var chart = me.chart; - var i, ilen, meta, arc, controller, options, borderWidth, hoverWidth; - - if (!arcs) { - // Find the outmost visible dataset - for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { - if (chart.isDatasetVisible(i)) { - meta = chart.getDatasetMeta(i); - arcs = meta.data; - if (i !== me.index) { - controller = meta.controller; - } - break; - } - } - } - - if (!arcs) { - return 0; - } - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - arc = arcs[i]; - if (controller) { - controller._configure(); - options = controller._resolveDataElementOptions(arc, i); - } else { - options = arc._options; - } - if (options.borderAlign !== 'inner') { - borderWidth = options.borderWidth; - hoverWidth = options.hoverBorderWidth; - - max = borderWidth > max ? borderWidth : max; - max = hoverWidth > max ? hoverWidth : max; - } - } - return max; - }, - - /** - * @protected - */ - setHoverStyle: function(arc) { - var model = arc._model; - var options = arc._options; - var getHoverColor = helpers$1.getHoverColor; - - arc.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - }; - - model.backgroundColor = valueOrDefault$5(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault$5(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault$5(options.hoverBorderWidth, options.borderWidth); - }, - - /** - * Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly - * @private - */ - _getRingWeightOffset: function(datasetIndex) { - var ringWeightOffset = 0; - - for (var i = 0; i < datasetIndex; ++i) { - if (this.chart.isDatasetVisible(i)) { - ringWeightOffset += this._getRingWeight(i); - } - } - - return ringWeightOffset; - }, - - /** - * @private - */ - _getRingWeight: function(dataSetIndex) { - return Math.max(valueOrDefault$5(this.chart.data.datasets[dataSetIndex].weight, 1), 0); - }, - - /** - * Returns the sum of all visibile data set weights. This value can be 0. - * @private - */ - _getVisibleDatasetWeightTotal: function() { - return this._getRingWeightOffset(this.chart.data.datasets.length); - } -}); - -core_defaults._set('horizontalBar', { - hover: { - mode: 'index', - axis: 'y' - }, - - scales: { - xAxes: [{ - type: 'linear', - position: 'bottom' - }], - - yAxes: [{ - type: 'category', - position: 'left', - offset: true, - gridLines: { - offsetGridLines: true - } - }] - }, - - elements: { - rectangle: { - borderSkipped: 'left' - } - }, - - tooltips: { - mode: 'index', - axis: 'y' - } -}); - -core_defaults._set('global', { - datasets: { - horizontalBar: { - categoryPercentage: 0.8, - barPercentage: 0.9 - } - } -}); - -var controller_horizontalBar = controller_bar.extend({ - /** - * @private - */ - _getValueScaleId: function() { - return this.getMeta().xAxisID; - }, - - /** - * @private - */ - _getIndexScaleId: function() { - return this.getMeta().yAxisID; - } -}); - -var valueOrDefault$6 = helpers$1.valueOrDefault; -var resolve$2 = helpers$1.options.resolve; -var isPointInArea = helpers$1.canvas._isPointInArea; - -core_defaults._set('line', { - showLines: true, - spanGaps: false, - - hover: { - mode: 'label' - }, - - scales: { - xAxes: [{ - type: 'category', - id: 'x-axis-0' - }], - yAxes: [{ - type: 'linear', - id: 'y-axis-0' - }] - } -}); - -function scaleClip(scale, halfBorderWidth) { - var tickOpts = scale && scale.options.ticks || {}; - var reverse = tickOpts.reverse; - var min = tickOpts.min === undefined ? halfBorderWidth : 0; - var max = tickOpts.max === undefined ? halfBorderWidth : 0; - return { - start: reverse ? max : min, - end: reverse ? min : max - }; -} - -function defaultClip(xScale, yScale, borderWidth) { - var halfBorderWidth = borderWidth / 2; - var x = scaleClip(xScale, halfBorderWidth); - var y = scaleClip(yScale, halfBorderWidth); - - return { - top: y.end, - right: x.end, - bottom: y.start, - left: x.start - }; -} - -function toClip(value) { - var t, r, b, l; - - if (helpers$1.isObject(value)) { - t = value.top; - r = value.right; - b = value.bottom; - l = value.left; - } else { - t = r = b = l = value; - } - - return { - top: t, - right: r, - bottom: b, - left: l - }; -} - - -var controller_line = core_datasetController.extend({ - - datasetElementType: elements.Line, - - dataElementType: elements.Point, - - /** - * @private - */ - _datasetElementOptions: [ - 'backgroundColor', - 'borderCapStyle', - 'borderColor', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'borderWidth', - 'cubicInterpolationMode', - 'fill' - ], - - /** - * @private - */ - _dataElementOptions: { - backgroundColor: 'pointBackgroundColor', - borderColor: 'pointBorderColor', - borderWidth: 'pointBorderWidth', - hitRadius: 'pointHitRadius', - hoverBackgroundColor: 'pointHoverBackgroundColor', - hoverBorderColor: 'pointHoverBorderColor', - hoverBorderWidth: 'pointHoverBorderWidth', - hoverRadius: 'pointHoverRadius', - pointStyle: 'pointStyle', - radius: 'pointRadius', - rotation: 'pointRotation' - }, - - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var line = meta.dataset; - var points = meta.data || []; - var options = me.chart.options; - var config = me._config; - var showLine = me._showLine = valueOrDefault$6(config.showLine, options.showLines); - var i, ilen; - - me._xScale = me.getScaleForId(meta.xAxisID); - me._yScale = me.getScaleForId(meta.yAxisID); - - // Update Line - if (showLine) { - // Compatibility: If the properties are defined with only the old name, use those values - if (config.tension !== undefined && config.lineTension === undefined) { - config.lineTension = config.tension; - } - - // Utility - line._scale = me._yScale; - line._datasetIndex = me.index; - // Data - line._children = points; - // Model - line._model = me._resolveDatasetElementOptions(line); - - line.pivot(); - } - - // Update Points - for (i = 0, ilen = points.length; i < ilen; ++i) { - me.updateElement(points[i], i, reset); - } - - if (showLine && line._model.tension !== 0) { - me.updateBezierControlPoints(); - } - - // Now pivot the point for animation - for (i = 0, ilen = points.length; i < ilen; ++i) { - points[i].pivot(); - } - }, - - updateElement: function(point, index, reset) { - var me = this; - var meta = me.getMeta(); - var custom = point.custom || {}; - var dataset = me.getDataset(); - var datasetIndex = me.index; - var value = dataset.data[index]; - var xScale = me._xScale; - var yScale = me._yScale; - var lineModel = meta.dataset._model; - var x, y; - - var options = me._resolveDataElementOptions(point, index); - - x = xScale.getPixelForValue(typeof value === 'object' ? value : NaN, index, datasetIndex); - y = reset ? yScale.getBasePixel() : me.calculatePointY(value, index, datasetIndex); - - // Utility - point._xScale = xScale; - point._yScale = yScale; - point._options = options; - point._datasetIndex = datasetIndex; - point._index = index; - - // Desired view properties - point._model = { - x: x, - y: y, - skip: custom.skip || isNaN(x) || isNaN(y), - // Appearance - radius: options.radius, - pointStyle: options.pointStyle, - rotation: options.rotation, - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - tension: valueOrDefault$6(custom.tension, lineModel ? lineModel.tension : 0), - steppedLine: lineModel ? lineModel.steppedLine : false, - // Tooltip - hitRadius: options.hitRadius - }; - }, - - /** - * @private - */ - _resolveDatasetElementOptions: function(element) { - var me = this; - var config = me._config; - var custom = element.custom || {}; - var options = me.chart.options; - var lineOptions = options.elements.line; - var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); - - // The default behavior of lines is to break at null values, according - // to https://github.com/chartjs/Chart.js/issues/2435#issuecomment-216718158 - // This option gives lines the ability to span gaps - values.spanGaps = valueOrDefault$6(config.spanGaps, options.spanGaps); - values.tension = valueOrDefault$6(config.lineTension, lineOptions.tension); - values.steppedLine = resolve$2([custom.steppedLine, config.steppedLine, lineOptions.stepped]); - values.clip = toClip(valueOrDefault$6(config.clip, defaultClip(me._xScale, me._yScale, values.borderWidth))); - - return values; - }, - - calculatePointY: function(value, index, datasetIndex) { - var me = this; - var chart = me.chart; - var yScale = me._yScale; - var sumPos = 0; - var sumNeg = 0; - var i, ds, dsMeta, stackedRightValue, rightValue, metasets, ilen; - - if (yScale.options.stacked) { - rightValue = +yScale.getRightValue(value); - metasets = chart._getSortedVisibleDatasetMetas(); - ilen = metasets.length; - - for (i = 0; i < ilen; ++i) { - dsMeta = metasets[i]; - if (dsMeta.index === datasetIndex) { - break; - } - - ds = chart.data.datasets[dsMeta.index]; - if (dsMeta.type === 'line' && dsMeta.yAxisID === yScale.id) { - stackedRightValue = +yScale.getRightValue(ds.data[index]); - if (stackedRightValue < 0) { - sumNeg += stackedRightValue || 0; - } else { - sumPos += stackedRightValue || 0; - } - } - } - - if (rightValue < 0) { - return yScale.getPixelForValue(sumNeg + rightValue); - } - return yScale.getPixelForValue(sumPos + rightValue); - } - return yScale.getPixelForValue(value); - }, - - updateBezierControlPoints: function() { - var me = this; - var chart = me.chart; - var meta = me.getMeta(); - var lineModel = meta.dataset._model; - var area = chart.chartArea; - var points = meta.data || []; - var i, ilen, model, controlPoints; - - // Only consider points that are drawn in case the spanGaps option is used - if (lineModel.spanGaps) { - points = points.filter(function(pt) { - return !pt._model.skip; - }); - } - - function capControlPoint(pt, min, max) { - return Math.max(Math.min(pt, max), min); - } - - if (lineModel.cubicInterpolationMode === 'monotone') { - helpers$1.splineCurveMonotone(points); - } else { - for (i = 0, ilen = points.length; i < ilen; ++i) { - model = points[i]._model; - controlPoints = helpers$1.splineCurve( - helpers$1.previousItem(points, i)._model, - model, - helpers$1.nextItem(points, i)._model, - lineModel.tension - ); - model.controlPointPreviousX = controlPoints.previous.x; - model.controlPointPreviousY = controlPoints.previous.y; - model.controlPointNextX = controlPoints.next.x; - model.controlPointNextY = controlPoints.next.y; - } - } - - if (chart.options.elements.line.capBezierPoints) { - for (i = 0, ilen = points.length; i < ilen; ++i) { - model = points[i]._model; - if (isPointInArea(model, area)) { - if (i > 0 && isPointInArea(points[i - 1]._model, area)) { - model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right); - model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom); - } - if (i < points.length - 1 && isPointInArea(points[i + 1]._model, area)) { - model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right); - model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom); - } - } - } - } - }, - - draw: function() { - var me = this; - var chart = me.chart; - var meta = me.getMeta(); - var points = meta.data || []; - var area = chart.chartArea; - var canvas = chart.canvas; - var i = 0; - var ilen = points.length; - var clip; - - if (me._showLine) { - clip = meta.dataset._model.clip; - - helpers$1.canvas.clipArea(chart.ctx, { - left: clip.left === false ? 0 : area.left - clip.left, - right: clip.right === false ? canvas.width : area.right + clip.right, - top: clip.top === false ? 0 : area.top - clip.top, - bottom: clip.bottom === false ? canvas.height : area.bottom + clip.bottom - }); - - meta.dataset.draw(); - - helpers$1.canvas.unclipArea(chart.ctx); - } - - // Draw the points - for (; i < ilen; ++i) { - points[i].draw(area); - } - }, - - /** - * @protected - */ - setHoverStyle: function(point) { - var model = point._model; - var options = point._options; - var getHoverColor = helpers$1.getHoverColor; - - point.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; - - model.backgroundColor = valueOrDefault$6(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault$6(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault$6(options.hoverBorderWidth, options.borderWidth); - model.radius = valueOrDefault$6(options.hoverRadius, options.radius); - }, -}); - -var resolve$3 = helpers$1.options.resolve; - -core_defaults._set('polarArea', { - scale: { - type: 'radialLinear', - angleLines: { - display: false - }, - gridLines: { - circular: true - }, - pointLabels: { - display: false - }, - ticks: { - beginAtZero: true - } - }, - - // Boolean - Whether to animate the rotation of the chart - animation: { - animateRotate: true, - animateScale: true - }, - - startAngle: -0.5 * Math.PI, - legendCallback: function(chart) { - var list = document.createElement('ul'); - var data = chart.data; - var datasets = data.datasets; - var labels = data.labels; - var i, ilen, listItem, listItemSpan; - - list.setAttribute('class', chart.id + '-legend'); - if (datasets.length) { - for (i = 0, ilen = datasets[0].data.length; i < ilen; ++i) { - listItem = list.appendChild(document.createElement('li')); - listItemSpan = listItem.appendChild(document.createElement('span')); - listItemSpan.style.backgroundColor = datasets[0].backgroundColor[i]; - if (labels[i]) { - listItem.appendChild(document.createTextNode(labels[i])); - } - } - } - - return list.outerHTML; - }, - legend: { - labels: { - generateLabels: function(chart) { - var data = chart.data; - if (data.labels.length && data.datasets.length) { - return data.labels.map(function(label, i) { - var meta = chart.getDatasetMeta(0); - var style = meta.controller.getStyle(i); - - return { - text: label, - fillStyle: style.backgroundColor, - strokeStyle: style.borderColor, - lineWidth: style.borderWidth, - hidden: isNaN(data.datasets[0].data[i]) || meta.data[i].hidden, - - // Extra data used for toggling the correct item - index: i - }; - }); - } - return []; - } - }, - - onClick: function(e, legendItem) { - var index = legendItem.index; - var chart = this.chart; - var i, ilen, meta; - - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - meta = chart.getDatasetMeta(i); - meta.data[index].hidden = !meta.data[index].hidden; - } - - chart.update(); - } - }, - - // Need to override these to give a nice default - tooltips: { - callbacks: { - title: function() { - return ''; - }, - label: function(item, data) { - return data.labels[item.index] + ': ' + item.yLabel; - } - } - } -}); - -var controller_polarArea = core_datasetController.extend({ - - dataElementType: elements.Arc, - - linkScales: helpers$1.noop, - - /** - * @private - */ - _dataElementOptions: [ - 'backgroundColor', - 'borderColor', - 'borderWidth', - 'borderAlign', - 'hoverBackgroundColor', - 'hoverBorderColor', - 'hoverBorderWidth', - ], - - /** - * @private - */ - _getIndexScaleId: function() { - return this.chart.scale.id; - }, - - /** - * @private - */ - _getValueScaleId: function() { - return this.chart.scale.id; - }, - - update: function(reset) { - var me = this; - var dataset = me.getDataset(); - var meta = me.getMeta(); - var start = me.chart.options.startAngle || 0; - var starts = me._starts = []; - var angles = me._angles = []; - var arcs = meta.data; - var i, ilen, angle; - - me._updateRadius(); - - meta.count = me.countVisibleElements(); - - for (i = 0, ilen = dataset.data.length; i < ilen; i++) { - starts[i] = start; - angle = me._computeAngle(i); - angles[i] = angle; - start += angle; - } - - for (i = 0, ilen = arcs.length; i < ilen; ++i) { - arcs[i]._options = me._resolveDataElementOptions(arcs[i], i); - me.updateElement(arcs[i], i, reset); - } - }, - - /** - * @private - */ - _updateRadius: function() { - var me = this; - var chart = me.chart; - var chartArea = chart.chartArea; - var opts = chart.options; - var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top); - - chart.outerRadius = Math.max(minSize / 2, 0); - chart.innerRadius = Math.max(opts.cutoutPercentage ? (chart.outerRadius / 100) * (opts.cutoutPercentage) : 1, 0); - chart.radiusLength = (chart.outerRadius - chart.innerRadius) / chart.getVisibleDatasetCount(); - - me.outerRadius = chart.outerRadius - (chart.radiusLength * me.index); - me.innerRadius = me.outerRadius - chart.radiusLength; - }, - - updateElement: function(arc, index, reset) { - var me = this; - var chart = me.chart; - var dataset = me.getDataset(); - var opts = chart.options; - var animationOpts = opts.animation; - var scale = chart.scale; - var labels = chart.data.labels; - - var centerX = scale.xCenter; - var centerY = scale.yCenter; - - // var negHalfPI = -0.5 * Math.PI; - var datasetStartAngle = opts.startAngle; - var distance = arc.hidden ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); - var startAngle = me._starts[index]; - var endAngle = startAngle + (arc.hidden ? 0 : me._angles[index]); - - var resetRadius = animationOpts.animateScale ? 0 : scale.getDistanceFromCenterForValue(dataset.data[index]); - var options = arc._options || {}; - - helpers$1.extend(arc, { - // Utility - _datasetIndex: me.index, - _index: index, - _scale: scale, - - // Desired view properties - _model: { - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - borderAlign: options.borderAlign, - x: centerX, - y: centerY, - innerRadius: 0, - outerRadius: reset ? resetRadius : distance, - startAngle: reset && animationOpts.animateRotate ? datasetStartAngle : startAngle, - endAngle: reset && animationOpts.animateRotate ? datasetStartAngle : endAngle, - label: helpers$1.valueAtIndexOrDefault(labels, index, labels[index]) - } - }); - - arc.pivot(); - }, - - countVisibleElements: function() { - var dataset = this.getDataset(); - var meta = this.getMeta(); - var count = 0; - - helpers$1.each(meta.data, function(element, index) { - if (!isNaN(dataset.data[index]) && !element.hidden) { - count++; - } - }); - - return count; - }, - - /** - * @protected - */ - setHoverStyle: function(arc) { - var model = arc._model; - var options = arc._options; - var getHoverColor = helpers$1.getHoverColor; - var valueOrDefault = helpers$1.valueOrDefault; - - arc.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - }; - - model.backgroundColor = valueOrDefault(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault(options.hoverBorderWidth, options.borderWidth); - }, - - /** - * @private - */ - _computeAngle: function(index) { - var me = this; - var count = this.getMeta().count; - var dataset = me.getDataset(); - var meta = me.getMeta(); - - if (isNaN(dataset.data[index]) || meta.data[index].hidden) { - return 0; - } - - // Scriptable options - var context = { - chart: me.chart, - dataIndex: index, - dataset: dataset, - datasetIndex: me.index - }; - - return resolve$3([ - me.chart.options.elements.arc.angle, - (2 * Math.PI) / count - ], context, index); - } -}); - -core_defaults._set('pie', helpers$1.clone(core_defaults.doughnut)); -core_defaults._set('pie', { - cutoutPercentage: 0 -}); - -// Pie charts are Doughnut chart with different defaults -var controller_pie = controller_doughnut; - -var valueOrDefault$7 = helpers$1.valueOrDefault; - -core_defaults._set('radar', { - spanGaps: false, - scale: { - type: 'radialLinear' - }, - elements: { - line: { - fill: 'start', - tension: 0 // no bezier in radar - } - } -}); - -var controller_radar = core_datasetController.extend({ - datasetElementType: elements.Line, - - dataElementType: elements.Point, - - linkScales: helpers$1.noop, - - /** - * @private - */ - _datasetElementOptions: [ - 'backgroundColor', - 'borderWidth', - 'borderColor', - 'borderCapStyle', - 'borderDash', - 'borderDashOffset', - 'borderJoinStyle', - 'fill' - ], - - /** - * @private - */ - _dataElementOptions: { - backgroundColor: 'pointBackgroundColor', - borderColor: 'pointBorderColor', - borderWidth: 'pointBorderWidth', - hitRadius: 'pointHitRadius', - hoverBackgroundColor: 'pointHoverBackgroundColor', - hoverBorderColor: 'pointHoverBorderColor', - hoverBorderWidth: 'pointHoverBorderWidth', - hoverRadius: 'pointHoverRadius', - pointStyle: 'pointStyle', - radius: 'pointRadius', - rotation: 'pointRotation' - }, - - /** - * @private - */ - _getIndexScaleId: function() { - return this.chart.scale.id; - }, - - /** - * @private - */ - _getValueScaleId: function() { - return this.chart.scale.id; - }, - - update: function(reset) { - var me = this; - var meta = me.getMeta(); - var line = meta.dataset; - var points = meta.data || []; - var scale = me.chart.scale; - var config = me._config; - var i, ilen; - - // Compatibility: If the properties are defined with only the old name, use those values - if (config.tension !== undefined && config.lineTension === undefined) { - config.lineTension = config.tension; - } - - // Utility - line._scale = scale; - line._datasetIndex = me.index; - // Data - line._children = points; - line._loop = true; - // Model - line._model = me._resolveDatasetElementOptions(line); - - line.pivot(); - - // Update Points - for (i = 0, ilen = points.length; i < ilen; ++i) { - me.updateElement(points[i], i, reset); - } - - // Update bezier control points - me.updateBezierControlPoints(); - - // Now pivot the point for animation - for (i = 0, ilen = points.length; i < ilen; ++i) { - points[i].pivot(); - } - }, - - updateElement: function(point, index, reset) { - var me = this; - var custom = point.custom || {}; - var dataset = me.getDataset(); - var scale = me.chart.scale; - var pointPosition = scale.getPointPositionForValue(index, dataset.data[index]); - var options = me._resolveDataElementOptions(point, index); - var lineModel = me.getMeta().dataset._model; - var x = reset ? scale.xCenter : pointPosition.x; - var y = reset ? scale.yCenter : pointPosition.y; - - // Utility - point._scale = scale; - point._options = options; - point._datasetIndex = me.index; - point._index = index; - - // Desired view properties - point._model = { - x: x, // value not used in dataset scale, but we want a consistent API between scales - y: y, - skip: custom.skip || isNaN(x) || isNaN(y), - // Appearance - radius: options.radius, - pointStyle: options.pointStyle, - rotation: options.rotation, - backgroundColor: options.backgroundColor, - borderColor: options.borderColor, - borderWidth: options.borderWidth, - tension: valueOrDefault$7(custom.tension, lineModel ? lineModel.tension : 0), - - // Tooltip - hitRadius: options.hitRadius - }; - }, - - /** - * @private - */ - _resolveDatasetElementOptions: function() { - var me = this; - var config = me._config; - var options = me.chart.options; - var values = core_datasetController.prototype._resolveDatasetElementOptions.apply(me, arguments); - - values.spanGaps = valueOrDefault$7(config.spanGaps, options.spanGaps); - values.tension = valueOrDefault$7(config.lineTension, options.elements.line.tension); - - return values; - }, - - updateBezierControlPoints: function() { - var me = this; - var meta = me.getMeta(); - var area = me.chart.chartArea; - var points = meta.data || []; - var i, ilen, model, controlPoints; - - // Only consider points that are drawn in case the spanGaps option is used - if (meta.dataset._model.spanGaps) { - points = points.filter(function(pt) { - return !pt._model.skip; - }); - } - - function capControlPoint(pt, min, max) { - return Math.max(Math.min(pt, max), min); - } - - for (i = 0, ilen = points.length; i < ilen; ++i) { - model = points[i]._model; - controlPoints = helpers$1.splineCurve( - helpers$1.previousItem(points, i, true)._model, - model, - helpers$1.nextItem(points, i, true)._model, - model.tension - ); - - // Prevent the bezier going outside of the bounds of the graph - model.controlPointPreviousX = capControlPoint(controlPoints.previous.x, area.left, area.right); - model.controlPointPreviousY = capControlPoint(controlPoints.previous.y, area.top, area.bottom); - model.controlPointNextX = capControlPoint(controlPoints.next.x, area.left, area.right); - model.controlPointNextY = capControlPoint(controlPoints.next.y, area.top, area.bottom); - } - }, - - setHoverStyle: function(point) { - var model = point._model; - var options = point._options; - var getHoverColor = helpers$1.getHoverColor; - - point.$previousStyle = { - backgroundColor: model.backgroundColor, - borderColor: model.borderColor, - borderWidth: model.borderWidth, - radius: model.radius - }; - - model.backgroundColor = valueOrDefault$7(options.hoverBackgroundColor, getHoverColor(options.backgroundColor)); - model.borderColor = valueOrDefault$7(options.hoverBorderColor, getHoverColor(options.borderColor)); - model.borderWidth = valueOrDefault$7(options.hoverBorderWidth, options.borderWidth); - model.radius = valueOrDefault$7(options.hoverRadius, options.radius); - } -}); - -core_defaults._set('scatter', { - hover: { - mode: 'single' - }, - - scales: { - xAxes: [{ - id: 'x-axis-1', // need an ID so datasets can reference the scale - type: 'linear', // scatter should not use a category axis - position: 'bottom' - }], - yAxes: [{ - id: 'y-axis-1', - type: 'linear', - position: 'left' - }] - }, - - tooltips: { - callbacks: { - title: function() { - return ''; // doesn't make sense for scatter since data are formatted as a point - }, - label: function(item) { - return '(' + item.xLabel + ', ' + item.yLabel + ')'; - } - } - } -}); - -core_defaults._set('global', { - datasets: { - scatter: { - showLine: false - } - } -}); - -// Scatter charts use line controllers -var controller_scatter = controller_line; - -// NOTE export a map in which the key represents the controller type, not -// the class, and so must be CamelCase in order to be correctly retrieved -// by the controller in core.controller.js (`controllers[meta.type]`). - -var controllers = { - bar: controller_bar, - bubble: controller_bubble, - doughnut: controller_doughnut, - horizontalBar: controller_horizontalBar, - line: controller_line, - polarArea: controller_polarArea, - pie: controller_pie, - radar: controller_radar, - scatter: controller_scatter -}; - -/** - * Helper function to get relative position for an event - * @param {Event|IEvent} event - The event to get the position for - * @param {Chart} chart - The chart - * @returns {object} the event position - */ -function getRelativePosition(e, chart) { - if (e.native) { - return { - x: e.x, - y: e.y - }; - } - - return helpers$1.getRelativePosition(e, chart); -} - -/** - * Helper function to traverse all of the visible elements in the chart - * @param {Chart} chart - the chart - * @param {function} handler - the callback to execute for each visible item - */ -function parseVisibleItems(chart, handler) { - var metasets = chart._getSortedVisibleDatasetMetas(); - var metadata, i, j, ilen, jlen, element; - - for (i = 0, ilen = metasets.length; i < ilen; ++i) { - metadata = metasets[i].data; - for (j = 0, jlen = metadata.length; j < jlen; ++j) { - element = metadata[j]; - if (!element._view.skip) { - handler(element); - } - } - } -} - -/** - * Helper function to get the items that intersect the event position - * @param {ChartElement[]} items - elements to filter - * @param {object} position - the point to be nearest to - * @return {ChartElement[]} the nearest items - */ -function getIntersectItems(chart, position) { - var elements = []; - - parseVisibleItems(chart, function(element) { - if (element.inRange(position.x, position.y)) { - elements.push(element); - } - }); - - return elements; -} - -/** - * Helper function to get the items nearest to the event position considering all visible items in teh chart - * @param {Chart} chart - the chart to look at elements from - * @param {object} position - the point to be nearest to - * @param {boolean} intersect - if true, only consider items that intersect the position - * @param {function} distanceMetric - function to provide the distance between points - * @return {ChartElement[]} the nearest items - */ -function getNearestItems(chart, position, intersect, distanceMetric) { - var minDistance = Number.POSITIVE_INFINITY; - var nearestItems = []; - - parseVisibleItems(chart, function(element) { - if (intersect && !element.inRange(position.x, position.y)) { - return; - } - - var center = element.getCenterPoint(); - var distance = distanceMetric(position, center); - if (distance < minDistance) { - nearestItems = [element]; - minDistance = distance; - } else if (distance === minDistance) { - // Can have multiple items at the same distance in which case we sort by size - nearestItems.push(element); - } - }); - - return nearestItems; -} - -/** - * Get a distance metric function for two points based on the - * axis mode setting - * @param {string} axis - the axis mode. x|y|xy - */ -function getDistanceMetricForAxis(axis) { - var useX = axis.indexOf('x') !== -1; - var useY = axis.indexOf('y') !== -1; - - return function(pt1, pt2) { - var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0; - var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0; - return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2)); - }; -} - -function indexMode(chart, e, options) { - var position = getRelativePosition(e, chart); - // Default axis for index mode is 'x' to match old behaviour - options.axis = options.axis || 'x'; - var distanceMetric = getDistanceMetricForAxis(options.axis); - var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); - var elements = []; - - if (!items.length) { - return []; - } - - chart._getSortedVisibleDatasetMetas().forEach(function(meta) { - var element = meta.data[items[0]._index]; - - // don't count items that are skipped (null data) - if (element && !element._view.skip) { - elements.push(element); - } - }); - - return elements; -} - -/** - * @interface IInteractionOptions - */ -/** - * If true, only consider items that intersect the point - * @name IInterfaceOptions#boolean - * @type Boolean - */ - -/** - * Contains interaction related functions - * @namespace Chart.Interaction - */ -var core_interaction = { - // Helper function for different modes - modes: { - single: function(chart, e) { - var position = getRelativePosition(e, chart); - var elements = []; - - parseVisibleItems(chart, function(element) { - if (element.inRange(position.x, position.y)) { - elements.push(element); - return elements; - } - }); - - return elements.slice(0, 1); - }, - - /** - * @function Chart.Interaction.modes.label - * @deprecated since version 2.4.0 - * @todo remove at version 3 - * @private - */ - label: indexMode, - - /** - * Returns items at the same index. If the options.intersect parameter is true, we only return items if we intersect something - * If the options.intersect mode is false, we find the nearest item and return the items at the same index as that item - * @function Chart.Interaction.modes.index - * @since v2.4.0 - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use during interaction - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - index: indexMode, - - /** - * Returns items in the same dataset. If the options.intersect parameter is true, we only return items if we intersect something - * If the options.intersect is false, we find the nearest item and return the items in that dataset - * @function Chart.Interaction.modes.dataset - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use during interaction - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - dataset: function(chart, e, options) { - var position = getRelativePosition(e, chart); - options.axis = options.axis || 'xy'; - var distanceMetric = getDistanceMetricForAxis(options.axis); - var items = options.intersect ? getIntersectItems(chart, position) : getNearestItems(chart, position, false, distanceMetric); - - if (items.length > 0) { - items = chart.getDatasetMeta(items[0]._datasetIndex).data; - } - - return items; - }, - - /** - * @function Chart.Interaction.modes.x-axis - * @deprecated since version 2.4.0. Use index mode and intersect == true - * @todo remove at version 3 - * @private - */ - 'x-axis': function(chart, e) { - return indexMode(chart, e, {intersect: false}); - }, - - /** - * Point mode returns all elements that hit test based on the event position - * of the event - * @function Chart.Interaction.modes.intersect - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - point: function(chart, e) { - var position = getRelativePosition(e, chart); - return getIntersectItems(chart, position); - }, - - /** - * nearest mode returns the element closest to the point - * @function Chart.Interaction.modes.intersect - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - nearest: function(chart, e, options) { - var position = getRelativePosition(e, chart); - options.axis = options.axis || 'xy'; - var distanceMetric = getDistanceMetricForAxis(options.axis); - return getNearestItems(chart, position, options.intersect, distanceMetric); - }, - - /** - * x mode returns the elements that hit-test at the current x coordinate - * @function Chart.Interaction.modes.x - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - x: function(chart, e, options) { - var position = getRelativePosition(e, chart); - var items = []; - var intersectsItem = false; - - parseVisibleItems(chart, function(element) { - if (element.inXRange(position.x)) { - items.push(element); - } - - if (element.inRange(position.x, position.y)) { - intersectsItem = true; - } - }); - - // If we want to trigger on an intersect and we don't have any items - // that intersect the position, return nothing - if (options.intersect && !intersectsItem) { - items = []; - } - return items; - }, - - /** - * y mode returns the elements that hit-test at the current y coordinate - * @function Chart.Interaction.modes.y - * @param {Chart} chart - the chart we are returning items from - * @param {Event} e - the event we are find things at - * @param {IInteractionOptions} options - options to use - * @return {Chart.Element[]} Array of elements that are under the point. If none are found, an empty array is returned - */ - y: function(chart, e, options) { - var position = getRelativePosition(e, chart); - var items = []; - var intersectsItem = false; - - parseVisibleItems(chart, function(element) { - if (element.inYRange(position.y)) { - items.push(element); - } - - if (element.inRange(position.x, position.y)) { - intersectsItem = true; - } - }); - - // If we want to trigger on an intersect and we don't have any items - // that intersect the position, return nothing - if (options.intersect && !intersectsItem) { - items = []; - } - return items; - } - } -}; - -var extend = helpers$1.extend; - -function filterByPosition(array, position) { - return helpers$1.where(array, function(v) { - return v.pos === position; - }); -} - -function sortByWeight(array, reverse) { - return array.sort(function(a, b) { - var v0 = reverse ? b : a; - var v1 = reverse ? a : b; - return v0.weight === v1.weight ? - v0.index - v1.index : - v0.weight - v1.weight; - }); -} - -function wrapBoxes(boxes) { - var layoutBoxes = []; - var i, ilen, box; - - for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) { - box = boxes[i]; - layoutBoxes.push({ - index: i, - box: box, - pos: box.position, - horizontal: box.isHorizontal(), - weight: box.weight - }); - } - return layoutBoxes; -} - -function setLayoutDims(layouts, params) { - var i, ilen, layout; - for (i = 0, ilen = layouts.length; i < ilen; ++i) { - layout = layouts[i]; - // store width used instead of chartArea.w in fitBoxes - layout.width = layout.horizontal - ? layout.box.fullWidth && params.availableWidth - : params.vBoxMaxWidth; - // store height used instead of chartArea.h in fitBoxes - layout.height = layout.horizontal && params.hBoxMaxHeight; - } -} - -function buildLayoutBoxes(boxes) { - var layoutBoxes = wrapBoxes(boxes); - var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true); - var right = sortByWeight(filterByPosition(layoutBoxes, 'right')); - var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true); - var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom')); - - return { - leftAndTop: left.concat(top), - rightAndBottom: right.concat(bottom), - chartArea: filterByPosition(layoutBoxes, 'chartArea'), - vertical: left.concat(right), - horizontal: top.concat(bottom) - }; -} - -function getCombinedMax(maxPadding, chartArea, a, b) { - return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]); -} - -function updateDims(chartArea, params, layout) { - var box = layout.box; - var maxPadding = chartArea.maxPadding; - var newWidth, newHeight; - - if (layout.size) { - // this layout was already counted for, lets first reduce old size - chartArea[layout.pos] -= layout.size; - } - layout.size = layout.horizontal ? box.height : box.width; - chartArea[layout.pos] += layout.size; - - if (box.getPadding) { - var boxPadding = box.getPadding(); - maxPadding.top = Math.max(maxPadding.top, boxPadding.top); - maxPadding.left = Math.max(maxPadding.left, boxPadding.left); - maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom); - maxPadding.right = Math.max(maxPadding.right, boxPadding.right); - } - - newWidth = params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'); - newHeight = params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'); - - if (newWidth !== chartArea.w || newHeight !== chartArea.h) { - chartArea.w = newWidth; - chartArea.h = newHeight; - - // return true if chart area changed in layout's direction - return layout.horizontal ? newWidth !== chartArea.w : newHeight !== chartArea.h; - } -} - -function handleMaxPadding(chartArea) { - var maxPadding = chartArea.maxPadding; - - function updatePos(pos) { - var change = Math.max(maxPadding[pos] - chartArea[pos], 0); - chartArea[pos] += change; - return change; - } - chartArea.y += updatePos('top'); - chartArea.x += updatePos('left'); - updatePos('right'); - updatePos('bottom'); -} - -function getMargins(horizontal, chartArea) { - var maxPadding = chartArea.maxPadding; - - function marginForPositions(positions) { - var margin = {left: 0, top: 0, right: 0, bottom: 0}; - positions.forEach(function(pos) { - margin[pos] = Math.max(chartArea[pos], maxPadding[pos]); - }); - return margin; - } - - return horizontal - ? marginForPositions(['left', 'right']) - : marginForPositions(['top', 'bottom']); -} - -function fitBoxes(boxes, chartArea, params) { - var refitBoxes = []; - var i, ilen, layout, box, refit, changed; - - for (i = 0, ilen = boxes.length; i < ilen; ++i) { - layout = boxes[i]; - box = layout.box; - - box.update( - layout.width || chartArea.w, - layout.height || chartArea.h, - getMargins(layout.horizontal, chartArea) - ); - if (updateDims(chartArea, params, layout)) { - changed = true; - if (refitBoxes.length) { - // Dimensions changed and there were non full width boxes before this - // -> we have to refit those - refit = true; - } - } - if (!box.fullWidth) { // fullWidth boxes don't need to be re-fitted in any case - refitBoxes.push(layout); - } - } - - return refit ? fitBoxes(refitBoxes, chartArea, params) || changed : changed; -} - -function placeBoxes(boxes, chartArea, params) { - var userPadding = params.padding; - var x = chartArea.x; - var y = chartArea.y; - var i, ilen, layout, box; - - for (i = 0, ilen = boxes.length; i < ilen; ++i) { - layout = boxes[i]; - box = layout.box; - if (layout.horizontal) { - box.left = box.fullWidth ? userPadding.left : chartArea.left; - box.right = box.fullWidth ? params.outerWidth - userPadding.right : chartArea.left + chartArea.w; - box.top = y; - box.bottom = y + box.height; - box.width = box.right - box.left; - y = box.bottom; - } else { - box.left = x; - box.right = x + box.width; - box.top = chartArea.top; - box.bottom = chartArea.top + chartArea.h; - box.height = box.bottom - box.top; - x = box.right; - } - } - - chartArea.x = x; - chartArea.y = y; -} - -core_defaults._set('global', { - layout: { - padding: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - } -}); - -/** - * @interface ILayoutItem - * @prop {string} position - The position of the item in the chart layout. Possible values are - * 'left', 'top', 'right', 'bottom', and 'chartArea' - * @prop {number} weight - The weight used to sort the item. Higher weights are further away from the chart area - * @prop {boolean} fullWidth - if true, and the item is horizontal, then push vertical boxes down - * @prop {function} isHorizontal - returns true if the layout item is horizontal (ie. top or bottom) - * @prop {function} update - Takes two parameters: width and height. Returns size of item - * @prop {function} getPadding - Returns an object with padding on the edges - * @prop {number} width - Width of item. Must be valid after update() - * @prop {number} height - Height of item. Must be valid after update() - * @prop {number} left - Left edge of the item. Set by layout system and cannot be used in update - * @prop {number} top - Top edge of the item. Set by layout system and cannot be used in update - * @prop {number} right - Right edge of the item. Set by layout system and cannot be used in update - * @prop {number} bottom - Bottom edge of the item. Set by layout system and cannot be used in update - */ - -// The layout service is very self explanatory. It's responsible for the layout within a chart. -// Scales, Legends and Plugins all rely on the layout service and can easily register to be placed anywhere they need -// It is this service's responsibility of carrying out that layout. -var core_layouts = { - defaults: {}, - - /** - * Register a box to a chart. - * A box is simply a reference to an object that requires layout. eg. Scales, Legend, Title. - * @param {Chart} chart - the chart to use - * @param {ILayoutItem} item - the item to add to be layed out - */ - addBox: function(chart, item) { - if (!chart.boxes) { - chart.boxes = []; - } - - // initialize item with default values - item.fullWidth = item.fullWidth || false; - item.position = item.position || 'top'; - item.weight = item.weight || 0; - item._layers = item._layers || function() { - return [{ - z: 0, - draw: function() { - item.draw.apply(item, arguments); - } - }]; - }; - - chart.boxes.push(item); - }, - - /** - * Remove a layoutItem from a chart - * @param {Chart} chart - the chart to remove the box from - * @param {ILayoutItem} layoutItem - the item to remove from the layout - */ - removeBox: function(chart, layoutItem) { - var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1; - if (index !== -1) { - chart.boxes.splice(index, 1); - } - }, - - /** - * Sets (or updates) options on the given `item`. - * @param {Chart} chart - the chart in which the item lives (or will be added to) - * @param {ILayoutItem} item - the item to configure with the given options - * @param {object} options - the new item options. - */ - configure: function(chart, item, options) { - var props = ['fullWidth', 'position', 'weight']; - var ilen = props.length; - var i = 0; - var prop; - - for (; i < ilen; ++i) { - prop = props[i]; - if (options.hasOwnProperty(prop)) { - item[prop] = options[prop]; - } - } - }, - - /** - * Fits boxes of the given chart into the given size by having each box measure itself - * then running a fitting algorithm - * @param {Chart} chart - the chart - * @param {number} width - the width to fit into - * @param {number} height - the height to fit into - */ - update: function(chart, width, height) { - if (!chart) { - return; - } - - var layoutOptions = chart.options.layout || {}; - var padding = helpers$1.options.toPadding(layoutOptions.padding); - - var availableWidth = width - padding.width; - var availableHeight = height - padding.height; - var boxes = buildLayoutBoxes(chart.boxes); - var verticalBoxes = boxes.vertical; - var horizontalBoxes = boxes.horizontal; - - // Essentially we now have any number of boxes on each of the 4 sides. - // Our canvas looks like the following. - // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and - // B1 is the bottom axis - // There are also 4 quadrant-like locations (left to right instead of clockwise) reserved for chart overlays - // These locations are single-box locations only, when trying to register a chartArea location that is already taken, - // an error will be thrown. - // - // |----------------------------------------------------| - // | T1 (Full Width) | - // |----------------------------------------------------| - // | | | T2 | | - // | |----|-------------------------------------|----| - // | | | C1 | | C2 | | - // | | |----| |----| | - // | | | | | - // | L1 | L2 | ChartArea (C0) | R1 | - // | | | | | - // | | |----| |----| | - // | | | C3 | | C4 | | - // | |----|-------------------------------------|----| - // | | | B1 | | - // |----------------------------------------------------| - // | B2 (Full Width) | - // |----------------------------------------------------| - // - - var params = Object.freeze({ - outerWidth: width, - outerHeight: height, - padding: padding, - availableWidth: availableWidth, - vBoxMaxWidth: availableWidth / 2 / verticalBoxes.length, - hBoxMaxHeight: availableHeight / 2 - }); - var chartArea = extend({ - maxPadding: extend({}, padding), - w: availableWidth, - h: availableHeight, - x: padding.left, - y: padding.top - }, padding); - - setLayoutDims(verticalBoxes.concat(horizontalBoxes), params); - - // First fit vertical boxes - fitBoxes(verticalBoxes, chartArea, params); - - // Then fit horizontal boxes - if (fitBoxes(horizontalBoxes, chartArea, params)) { - // if the area changed, re-fit vertical boxes - fitBoxes(verticalBoxes, chartArea, params); - } - - handleMaxPadding(chartArea); - - // Finally place the boxes to correct coordinates - placeBoxes(boxes.leftAndTop, chartArea, params); - - // Move to opposite side of chart - chartArea.x += chartArea.w; - chartArea.y += chartArea.h; - - placeBoxes(boxes.rightAndBottom, chartArea, params); - - chart.chartArea = { - left: chartArea.left, - top: chartArea.top, - right: chartArea.left + chartArea.w, - bottom: chartArea.top + chartArea.h - }; - - // Finally update boxes in chartArea (radial scale for example) - helpers$1.each(boxes.chartArea, function(layout) { - var box = layout.box; - extend(box, chart.chartArea); - box.update(chartArea.w, chartArea.h); - }); - } -}; - -/** - * Platform fallback implementation (minimal). - * @see https://github.com/chartjs/Chart.js/pull/4591#issuecomment-319575939 - */ - -var platform_basic = { - acquireContext: function(item) { - if (item && item.canvas) { - // Support for any object associated to a canvas (including a context2d) - item = item.canvas; - } - - return item && item.getContext('2d') || null; - } -}; - -var platform_dom = "/*\n * DOM element rendering detection\n * https://davidwalsh.name/detect-node-insertion\n */\n@keyframes chartjs-render-animation {\n\tfrom { opacity: 0.99; }\n\tto { opacity: 1; }\n}\n\n.chartjs-render-monitor {\n\tanimation: chartjs-render-animation 0.001s;\n}\n\n/*\n * DOM element resizing detection\n * https://github.com/marcj/css-element-queries\n */\n.chartjs-size-monitor,\n.chartjs-size-monitor-expand,\n.chartjs-size-monitor-shrink {\n\tposition: absolute;\n\tdirection: ltr;\n\tleft: 0;\n\ttop: 0;\n\tright: 0;\n\tbottom: 0;\n\toverflow: hidden;\n\tpointer-events: none;\n\tvisibility: hidden;\n\tz-index: -1;\n}\n\n.chartjs-size-monitor-expand > div {\n\tposition: absolute;\n\twidth: 1000000px;\n\theight: 1000000px;\n\tleft: 0;\n\ttop: 0;\n}\n\n.chartjs-size-monitor-shrink > div {\n\tposition: absolute;\n\twidth: 200%;\n\theight: 200%;\n\tleft: 0;\n\ttop: 0;\n}\n"; - -var platform_dom$1 = /*#__PURE__*/Object.freeze({ -__proto__: null, -'default': platform_dom -}); - -var stylesheet = getCjsExportFromNamespace(platform_dom$1); - -var EXPANDO_KEY = '$chartjs'; -var CSS_PREFIX = 'chartjs-'; -var CSS_SIZE_MONITOR = CSS_PREFIX + 'size-monitor'; -var CSS_RENDER_MONITOR = CSS_PREFIX + 'render-monitor'; -var CSS_RENDER_ANIMATION = CSS_PREFIX + 'render-animation'; -var ANIMATION_START_EVENTS = ['animationstart', 'webkitAnimationStart']; - -/** - * DOM event types -> Chart.js event types. - * Note: only events with different types are mapped. - * @see https://developer.mozilla.org/en-US/docs/Web/Events - */ -var EVENT_TYPES = { - touchstart: 'mousedown', - touchmove: 'mousemove', - touchend: 'mouseup', - pointerenter: 'mouseenter', - pointerdown: 'mousedown', - pointermove: 'mousemove', - pointerup: 'mouseup', - pointerleave: 'mouseout', - pointerout: 'mouseout' -}; - -/** - * The "used" size is the final value of a dimension property after all calculations have - * been performed. This method uses the computed style of `element` but returns undefined - * if the computed style is not expressed in pixels. That can happen in some cases where - * `element` has a size relative to its parent and this last one is not yet displayed, - * for example because of `display: none` on a parent node. - * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value - * @returns {number} Size in pixels or undefined if unknown. - */ -function readUsedSize(element, property) { - var value = helpers$1.getStyle(element, property); - var matches = value && value.match(/^(\d+)(\.\d+)?px$/); - return matches ? Number(matches[1]) : undefined; -} - -/** - * Initializes the canvas style and render size without modifying the canvas display size, - * since responsiveness is handled by the controller.resize() method. The config is used - * to determine the aspect ratio to apply in case no explicit height has been specified. - */ -function initCanvas(canvas, config) { - var style = canvas.style; - - // NOTE(SB) canvas.getAttribute('width') !== canvas.width: in the first case it - // returns null or '' if no explicit value has been set to the canvas attribute. - var renderHeight = canvas.getAttribute('height'); - var renderWidth = canvas.getAttribute('width'); - - // Chart.js modifies some canvas values that we want to restore on destroy - canvas[EXPANDO_KEY] = { - initial: { - height: renderHeight, - width: renderWidth, - style: { - display: style.display, - height: style.height, - width: style.width - } - } - }; - - // Force canvas to display as block to avoid extra space caused by inline - // elements, which would interfere with the responsive resize process. - // https://github.com/chartjs/Chart.js/issues/2538 - style.display = style.display || 'block'; - - if (renderWidth === null || renderWidth === '') { - var displayWidth = readUsedSize(canvas, 'width'); - if (displayWidth !== undefined) { - canvas.width = displayWidth; - } - } - - if (renderHeight === null || renderHeight === '') { - if (canvas.style.height === '') { - // If no explicit render height and style height, let's apply the aspect ratio, - // which one can be specified by the user but also by charts as default option - // (i.e. options.aspectRatio). If not specified, use canvas aspect ratio of 2. - canvas.height = canvas.width / (config.options.aspectRatio || 2); - } else { - var displayHeight = readUsedSize(canvas, 'height'); - if (displayWidth !== undefined) { - canvas.height = displayHeight; - } - } - } - - return canvas; -} - -/** - * Detects support for options object argument in addEventListener. - * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support - * @private - */ -var supportsEventListenerOptions = (function() { - var supports = false; - try { - var options = Object.defineProperty({}, 'passive', { - // eslint-disable-next-line getter-return - get: function() { - supports = true; - } - }); - window.addEventListener('e', null, options); - } catch (e) { - // continue regardless of error - } - return supports; -}()); - -// Default passive to true as expected by Chrome for 'touchstart' and 'touchend' events. -// https://github.com/chartjs/Chart.js/issues/4287 -var eventListenerOptions = supportsEventListenerOptions ? {passive: true} : false; - -function addListener(node, type, listener) { - node.addEventListener(type, listener, eventListenerOptions); -} - -function removeListener(node, type, listener) { - node.removeEventListener(type, listener, eventListenerOptions); -} - -function createEvent(type, chart, x, y, nativeEvent) { - return { - type: type, - chart: chart, - native: nativeEvent || null, - x: x !== undefined ? x : null, - y: y !== undefined ? y : null, - }; -} - -function fromNativeEvent(event, chart) { - var type = EVENT_TYPES[event.type] || event.type; - var pos = helpers$1.getRelativePosition(event, chart); - return createEvent(type, chart, pos.x, pos.y, event); -} - -function throttled(fn, thisArg) { - var ticking = false; - var args = []; - - return function() { - args = Array.prototype.slice.call(arguments); - thisArg = thisArg || this; - - if (!ticking) { - ticking = true; - helpers$1.requestAnimFrame.call(window, function() { - ticking = false; - fn.apply(thisArg, args); - }); - } - }; -} - -function createDiv(cls) { - var el = document.createElement('div'); - el.className = cls || ''; - return el; -} - -// Implementation based on https://github.com/marcj/css-element-queries -function createResizer(handler) { - var maxSize = 1000000; - - // NOTE(SB) Don't use innerHTML because it could be considered unsafe. - // https://github.com/chartjs/Chart.js/issues/5902 - var resizer = createDiv(CSS_SIZE_MONITOR); - var expand = createDiv(CSS_SIZE_MONITOR + '-expand'); - var shrink = createDiv(CSS_SIZE_MONITOR + '-shrink'); - - expand.appendChild(createDiv()); - shrink.appendChild(createDiv()); - - resizer.appendChild(expand); - resizer.appendChild(shrink); - resizer._reset = function() { - expand.scrollLeft = maxSize; - expand.scrollTop = maxSize; - shrink.scrollLeft = maxSize; - shrink.scrollTop = maxSize; - }; - - var onScroll = function() { - resizer._reset(); - handler(); - }; - - addListener(expand, 'scroll', onScroll.bind(expand, 'expand')); - addListener(shrink, 'scroll', onScroll.bind(shrink, 'shrink')); - - return resizer; -} - -// https://davidwalsh.name/detect-node-insertion -function watchForRender(node, handler) { - var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); - var proxy = expando.renderProxy = function(e) { - if (e.animationName === CSS_RENDER_ANIMATION) { - handler(); - } - }; - - helpers$1.each(ANIMATION_START_EVENTS, function(type) { - addListener(node, type, proxy); - }); - - // #4737: Chrome might skip the CSS animation when the CSS_RENDER_MONITOR class - // is removed then added back immediately (same animation frame?). Accessing the - // `offsetParent` property will force a reflow and re-evaluate the CSS animation. - // https://gist.github.com/paulirish/5d52fb081b3570c81e3a#box-metrics - // https://github.com/chartjs/Chart.js/issues/4737 - expando.reflow = !!node.offsetParent; - - node.classList.add(CSS_RENDER_MONITOR); -} - -function unwatchForRender(node) { - var expando = node[EXPANDO_KEY] || {}; - var proxy = expando.renderProxy; - - if (proxy) { - helpers$1.each(ANIMATION_START_EVENTS, function(type) { - removeListener(node, type, proxy); - }); - - delete expando.renderProxy; - } - - node.classList.remove(CSS_RENDER_MONITOR); -} - -function addResizeListener(node, listener, chart) { - var expando = node[EXPANDO_KEY] || (node[EXPANDO_KEY] = {}); - - // Let's keep track of this added resizer and thus avoid DOM query when removing it. - var resizer = expando.resizer = createResizer(throttled(function() { - if (expando.resizer) { - var container = chart.options.maintainAspectRatio && node.parentNode; - var w = container ? container.clientWidth : 0; - listener(createEvent('resize', chart)); - if (container && container.clientWidth < w && chart.canvas) { - // If the container size shrank during chart resize, let's assume - // scrollbar appeared. So we resize again with the scrollbar visible - - // effectively making chart smaller and the scrollbar hidden again. - // Because we are inside `throttled`, and currently `ticking`, scroll - // events are ignored during this whole 2 resize process. - // If we assumed wrong and something else happened, we are resizing - // twice in a frame (potential performance issue) - listener(createEvent('resize', chart)); - } - } - })); - - // The resizer needs to be attached to the node parent, so we first need to be - // sure that `node` is attached to the DOM before injecting the resizer element. - watchForRender(node, function() { - if (expando.resizer) { - var container = node.parentNode; - if (container && container !== resizer.parentNode) { - container.insertBefore(resizer, container.firstChild); - } - - // The container size might have changed, let's reset the resizer state. - resizer._reset(); - } - }); -} - -function removeResizeListener(node) { - var expando = node[EXPANDO_KEY] || {}; - var resizer = expando.resizer; - - delete expando.resizer; - unwatchForRender(node); - - if (resizer && resizer.parentNode) { - resizer.parentNode.removeChild(resizer); - } -} - -/** - * Injects CSS styles inline if the styles are not already present. - * @param {HTMLDocument|ShadowRoot} rootNode - the node to contain the <style>. - * @param {string} css - the CSS to be injected. - */ -function injectCSS(rootNode, css) { - // https://stackoverflow.com/q/3922139 - var expando = rootNode[EXPANDO_KEY] || (rootNode[EXPANDO_KEY] = {}); - if (!expando.containsStyles) { - expando.containsStyles = true; - css = '/* Chart.js */\n' + css; - var style = document.createElement('style'); - style.setAttribute('type', 'text/css'); - style.appendChild(document.createTextNode(css)); - rootNode.appendChild(style); - } -} - -var platform_dom$2 = { - /** - * When `true`, prevents the automatic injection of the stylesheet required to - * correctly detect when the chart is added to the DOM and then resized. This - * switch has been added to allow external stylesheet (`dist/Chart(.min)?.js`) - * to be manually imported to make this library compatible with any CSP. - * See https://github.com/chartjs/Chart.js/issues/5208 - */ - disableCSSInjection: false, - - /** - * This property holds whether this platform is enabled for the current environment. - * Currently used by platform.js to select the proper implementation. - * @private - */ - _enabled: typeof window !== 'undefined' && typeof document !== 'undefined', - - /** - * Initializes resources that depend on platform options. - * @param {HTMLCanvasElement} canvas - The Canvas element. - * @private - */ - _ensureLoaded: function(canvas) { - if (!this.disableCSSInjection) { - // If the canvas is in a shadow DOM, then the styles must also be inserted - // into the same shadow DOM. - // https://github.com/chartjs/Chart.js/issues/5763 - var root = canvas.getRootNode ? canvas.getRootNode() : document; - var targetNode = root.host ? root : document.head; - injectCSS(targetNode, stylesheet); - } - }, - - acquireContext: function(item, config) { - if (typeof item === 'string') { - item = document.getElementById(item); - } else if (item.length) { - // Support for array based queries (such as jQuery) - item = item[0]; - } - - if (item && item.canvas) { - // Support for any object associated to a canvas (including a context2d) - item = item.canvas; - } - - // To prevent canvas fingerprinting, some add-ons undefine the getContext - // method, for example: https://github.com/kkapsner/CanvasBlocker - // https://github.com/chartjs/Chart.js/issues/2807 - var context = item && item.getContext && item.getContext('2d'); - - // `instanceof HTMLCanvasElement/CanvasRenderingContext2D` fails when the item is - // inside an iframe or when running in a protected environment. We could guess the - // types from their toString() value but let's keep things flexible and assume it's - // a sufficient condition if the item has a context2D which has item as `canvas`. - // https://github.com/chartjs/Chart.js/issues/3887 - // https://github.com/chartjs/Chart.js/issues/4102 - // https://github.com/chartjs/Chart.js/issues/4152 - if (context && context.canvas === item) { - // Load platform resources on first chart creation, to make it possible to - // import the library before setting platform options. - this._ensureLoaded(item); - initCanvas(item, config); - return context; - } - - return null; - }, - - releaseContext: function(context) { - var canvas = context.canvas; - if (!canvas[EXPANDO_KEY]) { - return; - } - - var initial = canvas[EXPANDO_KEY].initial; - ['height', 'width'].forEach(function(prop) { - var value = initial[prop]; - if (helpers$1.isNullOrUndef(value)) { - canvas.removeAttribute(prop); - } else { - canvas.setAttribute(prop, value); - } - }); - - helpers$1.each(initial.style || {}, function(value, key) { - canvas.style[key] = value; - }); - - // The canvas render size might have been changed (and thus the state stack discarded), - // we can't use save() and restore() to restore the initial state. So make sure that at - // least the canvas context is reset to the default state by setting the canvas width. - // https://www.w3.org/TR/2011/WD-html5-20110525/the-canvas-element.html - // eslint-disable-next-line no-self-assign - canvas.width = canvas.width; - - delete canvas[EXPANDO_KEY]; - }, - - addEventListener: function(chart, type, listener) { - var canvas = chart.canvas; - if (type === 'resize') { - // Note: the resize event is not supported on all browsers. - addResizeListener(canvas, listener, chart); - return; - } - - var expando = listener[EXPANDO_KEY] || (listener[EXPANDO_KEY] = {}); - var proxies = expando.proxies || (expando.proxies = {}); - var proxy = proxies[chart.id + '_' + type] = function(event) { - listener(fromNativeEvent(event, chart)); - }; - - addListener(canvas, type, proxy); - }, - - removeEventListener: function(chart, type, listener) { - var canvas = chart.canvas; - if (type === 'resize') { - // Note: the resize event is not supported on all browsers. - removeResizeListener(canvas); - return; - } - - var expando = listener[EXPANDO_KEY] || {}; - var proxies = expando.proxies || {}; - var proxy = proxies[chart.id + '_' + type]; - if (!proxy) { - return; - } - - removeListener(canvas, type, proxy); - } -}; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use EventTarget.addEventListener instead. - * EventTarget.addEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener - * @function Chart.helpers.addEvent - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers$1.addEvent = addListener; - -/** - * Provided for backward compatibility, use EventTarget.removeEventListener instead. - * EventTarget.removeEventListener compatibility: Chrome, Opera 7, Safari, FF1.5+, IE9+ - * @see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener - * @function Chart.helpers.removeEvent - * @deprecated since version 2.7.0 - * @todo remove at version 3 - * @private - */ -helpers$1.removeEvent = removeListener; - -// @TODO Make possible to select another platform at build time. -var implementation = platform_dom$2._enabled ? platform_dom$2 : platform_basic; - -/** - * @namespace Chart.platform - * @see https://chartjs.gitbooks.io/proposals/content/Platform.html - * @since 2.4.0 - */ -var platform = helpers$1.extend({ - /** - * @since 2.7.0 - */ - initialize: function() {}, - - /** - * Called at chart construction time, returns a context2d instance implementing - * the [W3C Canvas 2D Context API standard]{@link https://www.w3.org/TR/2dcontext/}. - * @param {*} item - The native item from which to acquire context (platform specific) - * @param {object} options - The chart options - * @returns {CanvasRenderingContext2D} context2d instance - */ - acquireContext: function() {}, - - /** - * Called at chart destruction time, releases any resources associated to the context - * previously returned by the acquireContext() method. - * @param {CanvasRenderingContext2D} context - The context2d instance - * @returns {boolean} true if the method succeeded, else false - */ - releaseContext: function() {}, - - /** - * Registers the specified listener on the given chart. - * @param {Chart} chart - Chart from which to listen for event - * @param {string} type - The ({@link IEvent}) type to listen for - * @param {function} listener - Receives a notification (an object that implements - * the {@link IEvent} interface) when an event of the specified type occurs. - */ - addEventListener: function() {}, - - /** - * Removes the specified listener previously registered with addEventListener. - * @param {Chart} chart - Chart from which to remove the listener - * @param {string} type - The ({@link IEvent}) type to remove - * @param {function} listener - The listener function to remove from the event target. - */ - removeEventListener: function() {} - -}, implementation); - -core_defaults._set('global', { - plugins: {} -}); - -/** - * The plugin service singleton - * @namespace Chart.plugins - * @since 2.1.0 - */ -var core_plugins = { - /** - * Globally registered plugins. - * @private - */ - _plugins: [], - - /** - * This identifier is used to invalidate the descriptors cache attached to each chart - * when a global plugin is registered or unregistered. In this case, the cache ID is - * incremented and descriptors are regenerated during following API calls. - * @private - */ - _cacheId: 0, - - /** - * Registers the given plugin(s) if not already registered. - * @param {IPlugin[]|IPlugin} plugins plugin instance(s). - */ - register: function(plugins) { - var p = this._plugins; - ([]).concat(plugins).forEach(function(plugin) { - if (p.indexOf(plugin) === -1) { - p.push(plugin); - } - }); - - this._cacheId++; - }, - - /** - * Unregisters the given plugin(s) only if registered. - * @param {IPlugin[]|IPlugin} plugins plugin instance(s). - */ - unregister: function(plugins) { - var p = this._plugins; - ([]).concat(plugins).forEach(function(plugin) { - var idx = p.indexOf(plugin); - if (idx !== -1) { - p.splice(idx, 1); - } - }); - - this._cacheId++; - }, - - /** - * Remove all registered plugins. - * @since 2.1.5 - */ - clear: function() { - this._plugins = []; - this._cacheId++; - }, - - /** - * Returns the number of registered plugins? - * @returns {number} - * @since 2.1.5 - */ - count: function() { - return this._plugins.length; - }, - - /** - * Returns all registered plugin instances. - * @returns {IPlugin[]} array of plugin objects. - * @since 2.1.5 - */ - getAll: function() { - return this._plugins; - }, - - /** - * Calls enabled plugins for `chart` on the specified hook and with the given args. - * This method immediately returns as soon as a plugin explicitly returns false. The - * returned value can be used, for instance, to interrupt the current action. - * @param {Chart} chart - The chart instance for which plugins should be called. - * @param {string} hook - The name of the plugin method to call (e.g. 'beforeUpdate'). - * @param {Array} [args] - Extra arguments to apply to the hook call. - * @returns {boolean} false if any of the plugins return false, else returns true. - */ - notify: function(chart, hook, args) { - var descriptors = this.descriptors(chart); - var ilen = descriptors.length; - var i, descriptor, plugin, params, method; - - for (i = 0; i < ilen; ++i) { - descriptor = descriptors[i]; - plugin = descriptor.plugin; - method = plugin[hook]; - if (typeof method === 'function') { - params = [chart].concat(args || []); - params.push(descriptor.options); - if (method.apply(plugin, params) === false) { - return false; - } - } - } - - return true; - }, - - /** - * Returns descriptors of enabled plugins for the given chart. - * @returns {object[]} [{ plugin, options }] - * @private - */ - descriptors: function(chart) { - var cache = chart.$plugins || (chart.$plugins = {}); - if (cache.id === this._cacheId) { - return cache.descriptors; - } - - var plugins = []; - var descriptors = []; - var config = (chart && chart.config) || {}; - var options = (config.options && config.options.plugins) || {}; - - this._plugins.concat(config.plugins || []).forEach(function(plugin) { - var idx = plugins.indexOf(plugin); - if (idx !== -1) { - return; - } - - var id = plugin.id; - var opts = options[id]; - if (opts === false) { - return; - } - - if (opts === true) { - opts = helpers$1.clone(core_defaults.global.plugins[id]); - } - - plugins.push(plugin); - descriptors.push({ - plugin: plugin, - options: opts || {} - }); - }); - - cache.descriptors = descriptors; - cache.id = this._cacheId; - return descriptors; - }, - - /** - * Invalidates cache for the given chart: descriptors hold a reference on plugin option, - * but in some cases, this reference can be changed by the user when updating options. - * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 - * @private - */ - _invalidate: function(chart) { - delete chart.$plugins; - } -}; - -var core_scaleService = { - // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then - // use the new chart options to grab the correct scale - constructors: {}, - // Use a registration function so that we can move to an ES6 map when we no longer need to support - // old browsers - - // Scale config defaults - defaults: {}, - registerScaleType: function(type, scaleConstructor, scaleDefaults) { - this.constructors[type] = scaleConstructor; - this.defaults[type] = helpers$1.clone(scaleDefaults); - }, - getScaleConstructor: function(type) { - return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; - }, - getScaleDefaults: function(type) { - // Return the scale defaults merged with the global settings so that we always use the latest ones - return this.defaults.hasOwnProperty(type) ? helpers$1.merge({}, [core_defaults.scale, this.defaults[type]]) : {}; - }, - updateScaleDefaults: function(type, additions) { - var me = this; - if (me.defaults.hasOwnProperty(type)) { - me.defaults[type] = helpers$1.extend(me.defaults[type], additions); - } - }, - addScalesToLayout: function(chart) { - // Adds each scale to the chart.boxes array to be sized accordingly - helpers$1.each(chart.scales, function(scale) { - // Set ILayoutItem parameters for backwards compatibility - scale.fullWidth = scale.options.fullWidth; - scale.position = scale.options.position; - scale.weight = scale.options.weight; - core_layouts.addBox(chart, scale); - }); - } -}; - -var valueOrDefault$8 = helpers$1.valueOrDefault; -var getRtlHelper = helpers$1.rtl.getRtlAdapter; - -core_defaults._set('global', { - tooltips: { - enabled: true, - custom: null, - mode: 'nearest', - position: 'average', - intersect: true, - backgroundColor: 'rgba(0,0,0,0.8)', - titleFontStyle: 'bold', - titleSpacing: 2, - titleMarginBottom: 6, - titleFontColor: '#fff', - titleAlign: 'left', - bodySpacing: 2, - bodyFontColor: '#fff', - bodyAlign: 'left', - footerFontStyle: 'bold', - footerSpacing: 2, - footerMarginTop: 6, - footerFontColor: '#fff', - footerAlign: 'left', - yPadding: 6, - xPadding: 6, - caretPadding: 2, - caretSize: 5, - cornerRadius: 6, - multiKeyBackground: '#fff', - displayColors: true, - borderColor: 'rgba(0,0,0,0)', - borderWidth: 0, - callbacks: { - // Args are: (tooltipItems, data) - beforeTitle: helpers$1.noop, - title: function(tooltipItems, data) { - var title = ''; - var labels = data.labels; - var labelCount = labels ? labels.length : 0; - - if (tooltipItems.length > 0) { - var item = tooltipItems[0]; - if (item.label) { - title = item.label; - } else if (item.xLabel) { - title = item.xLabel; - } else if (labelCount > 0 && item.index < labelCount) { - title = labels[item.index]; - } - } - - return title; - }, - afterTitle: helpers$1.noop, - - // Args are: (tooltipItems, data) - beforeBody: helpers$1.noop, - - // Args are: (tooltipItem, data) - beforeLabel: helpers$1.noop, - label: function(tooltipItem, data) { - var label = data.datasets[tooltipItem.datasetIndex].label || ''; - - if (label) { - label += ': '; - } - if (!helpers$1.isNullOrUndef(tooltipItem.value)) { - label += tooltipItem.value; - } else { - label += tooltipItem.yLabel; - } - return label; - }, - labelColor: function(tooltipItem, chart) { - var meta = chart.getDatasetMeta(tooltipItem.datasetIndex); - var activeElement = meta.data[tooltipItem.index]; - var view = activeElement._view; - return { - borderColor: view.borderColor, - backgroundColor: view.backgroundColor - }; - }, - labelTextColor: function() { - return this._options.bodyFontColor; - }, - afterLabel: helpers$1.noop, - - // Args are: (tooltipItems, data) - afterBody: helpers$1.noop, - - // Args are: (tooltipItems, data) - beforeFooter: helpers$1.noop, - footer: helpers$1.noop, - afterFooter: helpers$1.noop - } - } -}); - -var positioners = { - /** - * Average mode places the tooltip at the average position of the elements shown - * @function Chart.Tooltip.positioners.average - * @param elements {ChartElement[]} the elements being displayed in the tooltip - * @returns {object} tooltip position - */ - average: function(elements) { - if (!elements.length) { - return false; - } - - var i, len; - var x = 0; - var y = 0; - var count = 0; - - for (i = 0, len = elements.length; i < len; ++i) { - var el = elements[i]; - if (el && el.hasValue()) { - var pos = el.tooltipPosition(); - x += pos.x; - y += pos.y; - ++count; - } - } - - return { - x: x / count, - y: y / count - }; - }, - - /** - * Gets the tooltip position nearest of the item nearest to the event position - * @function Chart.Tooltip.positioners.nearest - * @param elements {Chart.Element[]} the tooltip elements - * @param eventPosition {object} the position of the event in canvas coordinates - * @returns {object} the tooltip position - */ - nearest: function(elements, eventPosition) { - var x = eventPosition.x; - var y = eventPosition.y; - var minDistance = Number.POSITIVE_INFINITY; - var i, len, nearestElement; - - for (i = 0, len = elements.length; i < len; ++i) { - var el = elements[i]; - if (el && el.hasValue()) { - var center = el.getCenterPoint(); - var d = helpers$1.distanceBetweenPoints(eventPosition, center); - - if (d < minDistance) { - minDistance = d; - nearestElement = el; - } - } - } - - if (nearestElement) { - var tp = nearestElement.tooltipPosition(); - x = tp.x; - y = tp.y; - } - - return { - x: x, - y: y - }; - } -}; - -// Helper to push or concat based on if the 2nd parameter is an array or not -function pushOrConcat(base, toPush) { - if (toPush) { - if (helpers$1.isArray(toPush)) { - // base = base.concat(toPush); - Array.prototype.push.apply(base, toPush); - } else { - base.push(toPush); - } - } - - return base; -} - -/** - * Returns array of strings split by newline - * @param {string} value - The value to split by newline. - * @returns {string[]} value if newline present - Returned from String split() method - * @function - */ -function splitNewlines(str) { - if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) { - return str.split('\n'); - } - return str; -} - - -/** - * Private helper to create a tooltip item model - * @param element - the chart element (point, arc, bar) to create the tooltip item for - * @return new tooltip item - */ -function createTooltipItem(element) { - var xScale = element._xScale; - var yScale = element._yScale || element._scale; // handle radar || polarArea charts - var index = element._index; - var datasetIndex = element._datasetIndex; - var controller = element._chart.getDatasetMeta(datasetIndex).controller; - var indexScale = controller._getIndexScale(); - var valueScale = controller._getValueScale(); - - return { - xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', - yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', - label: indexScale ? '' + indexScale.getLabelForIndex(index, datasetIndex) : '', - value: valueScale ? '' + valueScale.getLabelForIndex(index, datasetIndex) : '', - index: index, - datasetIndex: datasetIndex, - x: element._model.x, - y: element._model.y - }; -} - -/** - * Helper to get the reset model for the tooltip - * @param tooltipOpts {object} the tooltip options - */ -function getBaseModel(tooltipOpts) { - var globalDefaults = core_defaults.global; - - return { - // Positioning - xPadding: tooltipOpts.xPadding, - yPadding: tooltipOpts.yPadding, - xAlign: tooltipOpts.xAlign, - yAlign: tooltipOpts.yAlign, - - // Drawing direction and text direction - rtl: tooltipOpts.rtl, - textDirection: tooltipOpts.textDirection, - - // Body - bodyFontColor: tooltipOpts.bodyFontColor, - _bodyFontFamily: valueOrDefault$8(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), - _bodyFontStyle: valueOrDefault$8(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), - _bodyAlign: tooltipOpts.bodyAlign, - bodyFontSize: valueOrDefault$8(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), - bodySpacing: tooltipOpts.bodySpacing, - - // Title - titleFontColor: tooltipOpts.titleFontColor, - _titleFontFamily: valueOrDefault$8(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), - _titleFontStyle: valueOrDefault$8(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), - titleFontSize: valueOrDefault$8(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), - _titleAlign: tooltipOpts.titleAlign, - titleSpacing: tooltipOpts.titleSpacing, - titleMarginBottom: tooltipOpts.titleMarginBottom, - - // Footer - footerFontColor: tooltipOpts.footerFontColor, - _footerFontFamily: valueOrDefault$8(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), - _footerFontStyle: valueOrDefault$8(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), - footerFontSize: valueOrDefault$8(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), - _footerAlign: tooltipOpts.footerAlign, - footerSpacing: tooltipOpts.footerSpacing, - footerMarginTop: tooltipOpts.footerMarginTop, - - // Appearance - caretSize: tooltipOpts.caretSize, - cornerRadius: tooltipOpts.cornerRadius, - backgroundColor: tooltipOpts.backgroundColor, - opacity: 0, - legendColorBackground: tooltipOpts.multiKeyBackground, - displayColors: tooltipOpts.displayColors, - borderColor: tooltipOpts.borderColor, - borderWidth: tooltipOpts.borderWidth - }; -} - -/** - * Get the size of the tooltip - */ -function getTooltipSize(tooltip, model) { - var ctx = tooltip._chart.ctx; - - var height = model.yPadding * 2; // Tooltip Padding - var width = 0; - - // Count of all lines in the body - var body = model.body; - var combinedBodyLength = body.reduce(function(count, bodyItem) { - return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; - }, 0); - combinedBodyLength += model.beforeBody.length + model.afterBody.length; - - var titleLineCount = model.title.length; - var footerLineCount = model.footer.length; - var titleFontSize = model.titleFontSize; - var bodyFontSize = model.bodyFontSize; - var footerFontSize = model.footerFontSize; - - height += titleLineCount * titleFontSize; // Title Lines - height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing - height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin - height += combinedBodyLength * bodyFontSize; // Body Lines - height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing - height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin - height += footerLineCount * (footerFontSize); // Footer Lines - height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing - - // Title width - var widthPadding = 0; - var maxLineWidth = function(line) { - width = Math.max(width, ctx.measureText(line).width + widthPadding); - }; - - ctx.font = helpers$1.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily); - helpers$1.each(model.title, maxLineWidth); - - // Body width - ctx.font = helpers$1.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily); - helpers$1.each(model.beforeBody.concat(model.afterBody), maxLineWidth); - - // Body lines may include some extra width due to the color box - widthPadding = model.displayColors ? (bodyFontSize + 2) : 0; - helpers$1.each(body, function(bodyItem) { - helpers$1.each(bodyItem.before, maxLineWidth); - helpers$1.each(bodyItem.lines, maxLineWidth); - helpers$1.each(bodyItem.after, maxLineWidth); - }); - - // Reset back to 0 - widthPadding = 0; - - // Footer width - ctx.font = helpers$1.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily); - helpers$1.each(model.footer, maxLineWidth); - - // Add padding - width += 2 * model.xPadding; - - return { - width: width, - height: height - }; -} - -/** - * Helper to get the alignment of a tooltip given the size - */ -function determineAlignment(tooltip, size) { - var model = tooltip._model; - var chart = tooltip._chart; - var chartArea = tooltip._chart.chartArea; - var xAlign = 'center'; - var yAlign = 'center'; - - if (model.y < size.height) { - yAlign = 'top'; - } else if (model.y > (chart.height - size.height)) { - yAlign = 'bottom'; - } - - var lf, rf; // functions to determine left, right alignment - var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart - var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges - var midX = (chartArea.left + chartArea.right) / 2; - var midY = (chartArea.top + chartArea.bottom) / 2; - - if (yAlign === 'center') { - lf = function(x) { - return x <= midX; - }; - rf = function(x) { - return x > midX; - }; - } else { - lf = function(x) { - return x <= (size.width / 2); - }; - rf = function(x) { - return x >= (chart.width - (size.width / 2)); - }; - } - - olf = function(x) { - return x + size.width + model.caretSize + model.caretPadding > chart.width; - }; - orf = function(x) { - return x - size.width - model.caretSize - model.caretPadding < 0; - }; - yf = function(y) { - return y <= midY ? 'top' : 'bottom'; - }; - - if (lf(model.x)) { - xAlign = 'left'; - - // Is tooltip too wide and goes over the right side of the chart.? - if (olf(model.x)) { - xAlign = 'center'; - yAlign = yf(model.y); - } - } else if (rf(model.x)) { - xAlign = 'right'; - - // Is tooltip too wide and goes outside left edge of canvas? - if (orf(model.x)) { - xAlign = 'center'; - yAlign = yf(model.y); - } - } - - var opts = tooltip._options; - return { - xAlign: opts.xAlign ? opts.xAlign : xAlign, - yAlign: opts.yAlign ? opts.yAlign : yAlign - }; -} - -/** - * Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment - */ -function getBackgroundPoint(vm, size, alignment, chart) { - // Background Position - var x = vm.x; - var y = vm.y; - - var caretSize = vm.caretSize; - var caretPadding = vm.caretPadding; - var cornerRadius = vm.cornerRadius; - var xAlign = alignment.xAlign; - var yAlign = alignment.yAlign; - var paddingAndSize = caretSize + caretPadding; - var radiusAndPadding = cornerRadius + caretPadding; - - if (xAlign === 'right') { - x -= size.width; - } else if (xAlign === 'center') { - x -= (size.width / 2); - if (x + size.width > chart.width) { - x = chart.width - size.width; - } - if (x < 0) { - x = 0; - } - } - - if (yAlign === 'top') { - y += paddingAndSize; - } else if (yAlign === 'bottom') { - y -= size.height + paddingAndSize; - } else { - y -= (size.height / 2); - } - - if (yAlign === 'center') { - if (xAlign === 'left') { - x += paddingAndSize; - } else if (xAlign === 'right') { - x -= paddingAndSize; - } - } else if (xAlign === 'left') { - x -= radiusAndPadding; - } else if (xAlign === 'right') { - x += radiusAndPadding; - } - - return { - x: x, - y: y - }; -} - -function getAlignedX(vm, align) { - return align === 'center' - ? vm.x + vm.width / 2 - : align === 'right' - ? vm.x + vm.width - vm.xPadding - : vm.x + vm.xPadding; -} - -/** - * Helper to build before and after body lines - */ -function getBeforeAfterBodyLines(callback) { - return pushOrConcat([], splitNewlines(callback)); -} - -var exports$4 = core_element.extend({ - initialize: function() { - this._model = getBaseModel(this._options); - this._lastActive = []; - }, - - // Get the title - // Args are: (tooltipItem, data) - getTitle: function() { - var me = this; - var opts = me._options; - var callbacks = opts.callbacks; - - var beforeTitle = callbacks.beforeTitle.apply(me, arguments); - var title = callbacks.title.apply(me, arguments); - var afterTitle = callbacks.afterTitle.apply(me, arguments); - - var lines = []; - lines = pushOrConcat(lines, splitNewlines(beforeTitle)); - lines = pushOrConcat(lines, splitNewlines(title)); - lines = pushOrConcat(lines, splitNewlines(afterTitle)); - - return lines; - }, - - // Args are: (tooltipItem, data) - getBeforeBody: function() { - return getBeforeAfterBodyLines(this._options.callbacks.beforeBody.apply(this, arguments)); - }, - - // Args are: (tooltipItem, data) - getBody: function(tooltipItems, data) { - var me = this; - var callbacks = me._options.callbacks; - var bodyItems = []; - - helpers$1.each(tooltipItems, function(tooltipItem) { - var bodyItem = { - before: [], - lines: [], - after: [] - }; - pushOrConcat(bodyItem.before, splitNewlines(callbacks.beforeLabel.call(me, tooltipItem, data))); - pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data)); - pushOrConcat(bodyItem.after, splitNewlines(callbacks.afterLabel.call(me, tooltipItem, data))); - - bodyItems.push(bodyItem); - }); - - return bodyItems; - }, - - // Args are: (tooltipItem, data) - getAfterBody: function() { - return getBeforeAfterBodyLines(this._options.callbacks.afterBody.apply(this, arguments)); - }, - - // Get the footer and beforeFooter and afterFooter lines - // Args are: (tooltipItem, data) - getFooter: function() { - var me = this; - var callbacks = me._options.callbacks; - - var beforeFooter = callbacks.beforeFooter.apply(me, arguments); - var footer = callbacks.footer.apply(me, arguments); - var afterFooter = callbacks.afterFooter.apply(me, arguments); - - var lines = []; - lines = pushOrConcat(lines, splitNewlines(beforeFooter)); - lines = pushOrConcat(lines, splitNewlines(footer)); - lines = pushOrConcat(lines, splitNewlines(afterFooter)); - - return lines; - }, - - update: function(changed) { - var me = this; - var opts = me._options; - - // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition - // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time - // which breaks any animations. - var existingModel = me._model; - var model = me._model = getBaseModel(opts); - var active = me._active; - - var data = me._data; - - // In the case where active.length === 0 we need to keep these at existing values for good animations - var alignment = { - xAlign: existingModel.xAlign, - yAlign: existingModel.yAlign - }; - var backgroundPoint = { - x: existingModel.x, - y: existingModel.y - }; - var tooltipSize = { - width: existingModel.width, - height: existingModel.height - }; - var tooltipPosition = { - x: existingModel.caretX, - y: existingModel.caretY - }; - - var i, len; - - if (active.length) { - model.opacity = 1; - - var labelColors = []; - var labelTextColors = []; - tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition); - - var tooltipItems = []; - for (i = 0, len = active.length; i < len; ++i) { - tooltipItems.push(createTooltipItem(active[i])); - } - - // If the user provided a filter function, use it to modify the tooltip items - if (opts.filter) { - tooltipItems = tooltipItems.filter(function(a) { - return opts.filter(a, data); - }); - } - - // If the user provided a sorting function, use it to modify the tooltip items - if (opts.itemSort) { - tooltipItems = tooltipItems.sort(function(a, b) { - return opts.itemSort(a, b, data); - }); - } - - // Determine colors for boxes - helpers$1.each(tooltipItems, function(tooltipItem) { - labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart)); - labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart)); - }); - - - // Build the Text Lines - model.title = me.getTitle(tooltipItems, data); - model.beforeBody = me.getBeforeBody(tooltipItems, data); - model.body = me.getBody(tooltipItems, data); - model.afterBody = me.getAfterBody(tooltipItems, data); - model.footer = me.getFooter(tooltipItems, data); - - // Initial positioning and colors - model.x = tooltipPosition.x; - model.y = tooltipPosition.y; - model.caretPadding = opts.caretPadding; - model.labelColors = labelColors; - model.labelTextColors = labelTextColors; - - // data points - model.dataPoints = tooltipItems; - - // We need to determine alignment of the tooltip - tooltipSize = getTooltipSize(this, model); - alignment = determineAlignment(this, tooltipSize); - // Final Size and Position - backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); - } else { - model.opacity = 0; - } - - model.xAlign = alignment.xAlign; - model.yAlign = alignment.yAlign; - model.x = backgroundPoint.x; - model.y = backgroundPoint.y; - model.width = tooltipSize.width; - model.height = tooltipSize.height; - - // Point where the caret on the tooltip points to - model.caretX = tooltipPosition.x; - model.caretY = tooltipPosition.y; - - me._model = model; - - if (changed && opts.custom) { - opts.custom.call(me, model); - } - - return me; - }, - - drawCaret: function(tooltipPoint, size) { - var ctx = this._chart.ctx; - var vm = this._view; - var caretPosition = this.getCaretPosition(tooltipPoint, size, vm); - - ctx.lineTo(caretPosition.x1, caretPosition.y1); - ctx.lineTo(caretPosition.x2, caretPosition.y2); - ctx.lineTo(caretPosition.x3, caretPosition.y3); - }, - getCaretPosition: function(tooltipPoint, size, vm) { - var x1, x2, x3, y1, y2, y3; - var caretSize = vm.caretSize; - var cornerRadius = vm.cornerRadius; - var xAlign = vm.xAlign; - var yAlign = vm.yAlign; - var ptX = tooltipPoint.x; - var ptY = tooltipPoint.y; - var width = size.width; - var height = size.height; - - if (yAlign === 'center') { - y2 = ptY + (height / 2); - - if (xAlign === 'left') { - x1 = ptX; - x2 = x1 - caretSize; - x3 = x1; - - y1 = y2 + caretSize; - y3 = y2 - caretSize; - } else { - x1 = ptX + width; - x2 = x1 + caretSize; - x3 = x1; - - y1 = y2 - caretSize; - y3 = y2 + caretSize; - } - } else { - if (xAlign === 'left') { - x2 = ptX + cornerRadius + (caretSize); - x1 = x2 - caretSize; - x3 = x2 + caretSize; - } else if (xAlign === 'right') { - x2 = ptX + width - cornerRadius - caretSize; - x1 = x2 - caretSize; - x3 = x2 + caretSize; - } else { - x2 = vm.caretX; - x1 = x2 - caretSize; - x3 = x2 + caretSize; - } - if (yAlign === 'top') { - y1 = ptY; - y2 = y1 - caretSize; - y3 = y1; - } else { - y1 = ptY + height; - y2 = y1 + caretSize; - y3 = y1; - // invert drawing order - var tmp = x3; - x3 = x1; - x1 = tmp; - } - } - return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; - }, - - drawTitle: function(pt, vm, ctx) { - var title = vm.title; - var length = title.length; - var titleFontSize, titleSpacing, i; - - if (length) { - var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); - - pt.x = getAlignedX(vm, vm._titleAlign); - - ctx.textAlign = rtlHelper.textAlign(vm._titleAlign); - ctx.textBaseline = 'middle'; - - titleFontSize = vm.titleFontSize; - titleSpacing = vm.titleSpacing; - - ctx.fillStyle = vm.titleFontColor; - ctx.font = helpers$1.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); - - for (i = 0; i < length; ++i) { - ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2); - pt.y += titleFontSize + titleSpacing; // Line Height and spacing - - if (i + 1 === length) { - pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing - } - } - } - }, - - drawBody: function(pt, vm, ctx) { - var bodyFontSize = vm.bodyFontSize; - var bodySpacing = vm.bodySpacing; - var bodyAlign = vm._bodyAlign; - var body = vm.body; - var drawColorBoxes = vm.displayColors; - var xLinePadding = 0; - var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0; - - var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); - - var fillLineOfText = function(line) { - ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2); - pt.y += bodyFontSize + bodySpacing; - }; - - var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen; - var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign); - - ctx.textAlign = bodyAlign; - ctx.textBaseline = 'middle'; - ctx.font = helpers$1.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); - - pt.x = getAlignedX(vm, bodyAlignForCalculation); - - // Before body lines - ctx.fillStyle = vm.bodyFontColor; - helpers$1.each(vm.beforeBody, fillLineOfText); - - xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right' - ? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2) - : 0; - - // Draw body lines now - for (i = 0, ilen = body.length; i < ilen; ++i) { - bodyItem = body[i]; - textColor = vm.labelTextColors[i]; - labelColors = vm.labelColors[i]; - - ctx.fillStyle = textColor; - helpers$1.each(bodyItem.before, fillLineOfText); - - lines = bodyItem.lines; - for (j = 0, jlen = lines.length; j < jlen; ++j) { - // Draw Legend-like boxes if needed - if (drawColorBoxes) { - var rtlColorX = rtlHelper.x(colorX); - - // Fill a white rect so that colours merge nicely if the opacity is < 1 - ctx.fillStyle = vm.legendColorBackground; - ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); - - // Border - ctx.lineWidth = 1; - ctx.strokeStyle = labelColors.borderColor; - ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize); - - // Inner square - ctx.fillStyle = labelColors.backgroundColor; - ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); - ctx.fillStyle = textColor; - } - - fillLineOfText(lines[j]); - } - - helpers$1.each(bodyItem.after, fillLineOfText); - } - - // Reset back to 0 for after body - xLinePadding = 0; - - // After body lines - helpers$1.each(vm.afterBody, fillLineOfText); - pt.y -= bodySpacing; // Remove last body spacing - }, - - drawFooter: function(pt, vm, ctx) { - var footer = vm.footer; - var length = footer.length; - var footerFontSize, i; - - if (length) { - var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width); - - pt.x = getAlignedX(vm, vm._footerAlign); - pt.y += vm.footerMarginTop; - - ctx.textAlign = rtlHelper.textAlign(vm._footerAlign); - ctx.textBaseline = 'middle'; - - footerFontSize = vm.footerFontSize; - - ctx.fillStyle = vm.footerFontColor; - ctx.font = helpers$1.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); - - for (i = 0; i < length; ++i) { - ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2); - pt.y += footerFontSize + vm.footerSpacing; - } - } - }, - - drawBackground: function(pt, vm, ctx, tooltipSize) { - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; - var xAlign = vm.xAlign; - var yAlign = vm.yAlign; - var x = pt.x; - var y = pt.y; - var width = tooltipSize.width; - var height = tooltipSize.height; - var radius = vm.cornerRadius; - - ctx.beginPath(); - ctx.moveTo(x + radius, y); - if (yAlign === 'top') { - this.drawCaret(pt, tooltipSize); - } - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - if (yAlign === 'center' && xAlign === 'right') { - this.drawCaret(pt, tooltipSize); - } - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - if (yAlign === 'bottom') { - this.drawCaret(pt, tooltipSize); - } - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - if (yAlign === 'center' && xAlign === 'left') { - this.drawCaret(pt, tooltipSize); - } - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - - ctx.fill(); - - if (vm.borderWidth > 0) { - ctx.stroke(); - } - }, - - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - - if (vm.opacity === 0) { - return; - } - - var tooltipSize = { - width: vm.width, - height: vm.height - }; - var pt = { - x: vm.x, - y: vm.y - }; - - // IE11/Edge does not like very small opacities, so snap to 0 - var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity; - - // Truthy/falsey value for empty tooltip - var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; - - if (this._options.enabled && hasTooltipContent) { - ctx.save(); - ctx.globalAlpha = opacity; - - // Draw Background - this.drawBackground(pt, vm, ctx, tooltipSize); - - // Draw Title, Body, and Footer - pt.y += vm.yPadding; - - helpers$1.rtl.overrideTextDirection(ctx, vm.textDirection); - - // Titles - this.drawTitle(pt, vm, ctx); - - // Body - this.drawBody(pt, vm, ctx); - - // Footer - this.drawFooter(pt, vm, ctx); - - helpers$1.rtl.restoreTextDirection(ctx, vm.textDirection); - - ctx.restore(); - } - }, - - /** - * Handle an event - * @private - * @param {IEvent} event - The event to handle - * @returns {boolean} true if the tooltip changed - */ - handleEvent: function(e) { - var me = this; - var options = me._options; - var changed = false; - - me._lastActive = me._lastActive || []; - - // Find Active Elements for tooltips - if (e.type === 'mouseout') { - me._active = []; - } else { - me._active = me._chart.getElementsAtEventForMode(e, options.mode, options); - if (options.reverse) { - me._active.reverse(); - } - } - - // Remember Last Actives - changed = !helpers$1.arrayEquals(me._active, me._lastActive); - - // Only handle target event on tooltip change - if (changed) { - me._lastActive = me._active; - - if (options.enabled || options.custom) { - me._eventPosition = { - x: e.x, - y: e.y - }; - - me.update(true); - me.pivot(); - } - } - - return changed; - } -}); - -/** - * @namespace Chart.Tooltip.positioners - */ -var positioners_1 = positioners; - -var core_tooltip = exports$4; -core_tooltip.positioners = positioners_1; - -var valueOrDefault$9 = helpers$1.valueOrDefault; - -core_defaults._set('global', { - elements: {}, - events: [ - 'mousemove', - 'mouseout', - 'click', - 'touchstart', - 'touchmove' - ], - hover: { - onHover: null, - mode: 'nearest', - intersect: true, - animationDuration: 400 - }, - onClick: null, - maintainAspectRatio: true, - responsive: true, - responsiveAnimationDuration: 0 -}); - -/** - * Recursively merge the given config objects representing the `scales` option - * by incorporating scale defaults in `xAxes` and `yAxes` array items, then - * returns a deep copy of the result, thus doesn't alter inputs. - */ -function mergeScaleConfig(/* config objects ... */) { - return helpers$1.merge({}, [].slice.call(arguments), { - merger: function(key, target, source, options) { - if (key === 'xAxes' || key === 'yAxes') { - var slen = source[key].length; - var i, type, scale; - - if (!target[key]) { - target[key] = []; - } - - for (i = 0; i < slen; ++i) { - scale = source[key][i]; - type = valueOrDefault$9(scale.type, key === 'xAxes' ? 'category' : 'linear'); - - if (i >= target[key].length) { - target[key].push({}); - } - - if (!target[key][i].type || (scale.type && scale.type !== target[key][i].type)) { - // new/untyped scale or type changed: let's apply the new defaults - // then merge source scale to correctly overwrite the defaults. - helpers$1.merge(target[key][i], [core_scaleService.getScaleDefaults(type), scale]); - } else { - // scales type are the same - helpers$1.merge(target[key][i], scale); - } - } - } else { - helpers$1._merger(key, target, source, options); - } - } - }); -} - -/** - * Recursively merge the given config objects as the root options by handling - * default scale options for the `scales` and `scale` properties, then returns - * a deep copy of the result, thus doesn't alter inputs. - */ -function mergeConfig(/* config objects ... */) { - return helpers$1.merge({}, [].slice.call(arguments), { - merger: function(key, target, source, options) { - var tval = target[key] || {}; - var sval = source[key]; - - if (key === 'scales') { - // scale config merging is complex. Add our own function here for that - target[key] = mergeScaleConfig(tval, sval); - } else if (key === 'scale') { - // used in polar area & radar charts since there is only one scale - target[key] = helpers$1.merge(tval, [core_scaleService.getScaleDefaults(sval.type), sval]); - } else { - helpers$1._merger(key, target, source, options); - } - } - }); -} - -function initConfig(config) { - config = config || {}; - - // Do NOT use mergeConfig for the data object because this method merges arrays - // and so would change references to labels and datasets, preventing data updates. - var data = config.data = config.data || {}; - data.datasets = data.datasets || []; - data.labels = data.labels || []; - - config.options = mergeConfig( - core_defaults.global, - core_defaults[config.type], - config.options || {}); - - return config; -} - -function updateConfig(chart) { - var newOptions = chart.options; - - helpers$1.each(chart.scales, function(scale) { - core_layouts.removeBox(chart, scale); - }); - - newOptions = mergeConfig( - core_defaults.global, - core_defaults[chart.config.type], - newOptions); - - chart.options = chart.config.options = newOptions; - chart.ensureScalesHaveIDs(); - chart.buildOrUpdateScales(); - - // Tooltip - chart.tooltip._options = newOptions.tooltips; - chart.tooltip.initialize(); -} - -function nextAvailableScaleId(axesOpts, prefix, index) { - var id; - var hasId = function(obj) { - return obj.id === id; - }; - - do { - id = prefix + index++; - } while (helpers$1.findIndex(axesOpts, hasId) >= 0); - - return id; -} - -function positionIsHorizontal(position) { - return position === 'top' || position === 'bottom'; -} - -function compare2Level(l1, l2) { - return function(a, b) { - return a[l1] === b[l1] - ? a[l2] - b[l2] - : a[l1] - b[l1]; - }; -} - -var Chart = function(item, config) { - this.construct(item, config); - return this; -}; - -helpers$1.extend(Chart.prototype, /** @lends Chart */ { - /** - * @private - */ - construct: function(item, config) { - var me = this; - - config = initConfig(config); - - var context = platform.acquireContext(item, config); - var canvas = context && context.canvas; - var height = canvas && canvas.height; - var width = canvas && canvas.width; - - me.id = helpers$1.uid(); - me.ctx = context; - me.canvas = canvas; - me.config = config; - me.width = width; - me.height = height; - me.aspectRatio = height ? width / height : null; - me.options = config.options; - me._bufferedRender = false; - me._layers = []; - - /** - * Provided for backward compatibility, Chart and Chart.Controller have been merged, - * the "instance" still need to be defined since it might be called from plugins. - * @prop Chart#chart - * @deprecated since version 2.6.0 - * @todo remove at version 3 - * @private - */ - me.chart = me; - me.controller = me; // chart.chart.controller #inception - - // Add the chart instance to the global namespace - Chart.instances[me.id] = me; - - // Define alias to the config data: `chart.data === chart.config.data` - Object.defineProperty(me, 'data', { - get: function() { - return me.config.data; - }, - set: function(value) { - me.config.data = value; - } - }); - - if (!context || !canvas) { - // The given item is not a compatible context2d element, let's return before finalizing - // the chart initialization but after setting basic chart / controller properties that - // can help to figure out that the chart is not valid (e.g chart.canvas !== null); - // https://github.com/chartjs/Chart.js/issues/2807 - console.error("Failed to create chart: can't acquire context from the given item"); - return; - } - - me.initialize(); - me.update(); - }, - - /** - * @private - */ - initialize: function() { - var me = this; - - // Before init plugin notification - core_plugins.notify(me, 'beforeInit'); - - helpers$1.retinaScale(me, me.options.devicePixelRatio); - - me.bindEvents(); - - if (me.options.responsive) { - // Initial resize before chart draws (must be silent to preserve initial animations). - me.resize(true); - } - - me.initToolTip(); - - // After init plugin notification - core_plugins.notify(me, 'afterInit'); - - return me; - }, - - clear: function() { - helpers$1.canvas.clear(this); - return this; - }, - - stop: function() { - // Stops any current animation loop occurring - core_animations.cancelAnimation(this); - return this; - }, - - resize: function(silent) { - var me = this; - var options = me.options; - var canvas = me.canvas; - var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null; - - // the canvas render width and height will be casted to integers so make sure that - // the canvas display style uses the same integer values to avoid blurring effect. - - // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed - var newWidth = Math.max(0, Math.floor(helpers$1.getMaximumWidth(canvas))); - var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers$1.getMaximumHeight(canvas))); - - if (me.width === newWidth && me.height === newHeight) { - return; - } - - canvas.width = me.width = newWidth; - canvas.height = me.height = newHeight; - canvas.style.width = newWidth + 'px'; - canvas.style.height = newHeight + 'px'; - - helpers$1.retinaScale(me, options.devicePixelRatio); - - if (!silent) { - // Notify any plugins about the resize - var newSize = {width: newWidth, height: newHeight}; - core_plugins.notify(me, 'resize', [newSize]); - - // Notify of resize - if (options.onResize) { - options.onResize(me, newSize); - } - - me.stop(); - me.update({ - duration: options.responsiveAnimationDuration - }); - } - }, - - ensureScalesHaveIDs: function() { - var options = this.options; - var scalesOptions = options.scales || {}; - var scaleOptions = options.scale; - - helpers$1.each(scalesOptions.xAxes, function(xAxisOptions, index) { - if (!xAxisOptions.id) { - xAxisOptions.id = nextAvailableScaleId(scalesOptions.xAxes, 'x-axis-', index); - } - }); - - helpers$1.each(scalesOptions.yAxes, function(yAxisOptions, index) { - if (!yAxisOptions.id) { - yAxisOptions.id = nextAvailableScaleId(scalesOptions.yAxes, 'y-axis-', index); - } - }); - - if (scaleOptions) { - scaleOptions.id = scaleOptions.id || 'scale'; - } - }, - - /** - * Builds a map of scale ID to scale object for future lookup. - */ - buildOrUpdateScales: function() { - var me = this; - var options = me.options; - var scales = me.scales || {}; - var items = []; - var updated = Object.keys(scales).reduce(function(obj, id) { - obj[id] = false; - return obj; - }, {}); - - if (options.scales) { - items = items.concat( - (options.scales.xAxes || []).map(function(xAxisOptions) { - return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'}; - }), - (options.scales.yAxes || []).map(function(yAxisOptions) { - return {options: yAxisOptions, dtype: 'linear', dposition: 'left'}; - }) - ); - } - - if (options.scale) { - items.push({ - options: options.scale, - dtype: 'radialLinear', - isDefault: true, - dposition: 'chartArea' - }); - } - - helpers$1.each(items, function(item) { - var scaleOptions = item.options; - var id = scaleOptions.id; - var scaleType = valueOrDefault$9(scaleOptions.type, item.dtype); - - if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) { - scaleOptions.position = item.dposition; - } - - updated[id] = true; - var scale = null; - if (id in scales && scales[id].type === scaleType) { - scale = scales[id]; - scale.options = scaleOptions; - scale.ctx = me.ctx; - scale.chart = me; - } else { - var scaleClass = core_scaleService.getScaleConstructor(scaleType); - if (!scaleClass) { - return; - } - scale = new scaleClass({ - id: id, - type: scaleType, - options: scaleOptions, - ctx: me.ctx, - chart: me - }); - scales[scale.id] = scale; - } - - scale.mergeTicksOptions(); - - // TODO(SB): I think we should be able to remove this custom case (options.scale) - // and consider it as a regular scale part of the "scales"" map only! This would - // make the logic easier and remove some useless? custom code. - if (item.isDefault) { - me.scale = scale; - } - }); - // clear up discarded scales - helpers$1.each(updated, function(hasUpdated, id) { - if (!hasUpdated) { - delete scales[id]; - } - }); - - me.scales = scales; - - core_scaleService.addScalesToLayout(this); - }, - - buildOrUpdateControllers: function() { - var me = this; - var newControllers = []; - var datasets = me.data.datasets; - var i, ilen; - - for (i = 0, ilen = datasets.length; i < ilen; i++) { - var dataset = datasets[i]; - var meta = me.getDatasetMeta(i); - var type = dataset.type || me.config.type; - - if (meta.type && meta.type !== type) { - me.destroyDatasetMeta(i); - meta = me.getDatasetMeta(i); - } - meta.type = type; - meta.order = dataset.order || 0; - meta.index = i; - - if (meta.controller) { - meta.controller.updateIndex(i); - meta.controller.linkScales(); - } else { - var ControllerClass = controllers[meta.type]; - if (ControllerClass === undefined) { - throw new Error('"' + meta.type + '" is not a chart type.'); - } - - meta.controller = new ControllerClass(me, i); - newControllers.push(meta.controller); - } - } - - return newControllers; - }, - - /** - * Reset the elements of all datasets - * @private - */ - resetElements: function() { - var me = this; - helpers$1.each(me.data.datasets, function(dataset, datasetIndex) { - me.getDatasetMeta(datasetIndex).controller.reset(); - }, me); - }, - - /** - * Resets the chart back to it's state before the initial animation - */ - reset: function() { - this.resetElements(); - this.tooltip.initialize(); - }, - - update: function(config) { - var me = this; - var i, ilen; - - if (!config || typeof config !== 'object') { - // backwards compatibility - config = { - duration: config, - lazy: arguments[1] - }; - } - - updateConfig(me); - - // plugins options references might have change, let's invalidate the cache - // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167 - core_plugins._invalidate(me); - - if (core_plugins.notify(me, 'beforeUpdate') === false) { - return; - } - - // In case the entire data object changed - me.tooltip._data = me.data; - - // Make sure dataset controllers are updated and new controllers are reset - var newControllers = me.buildOrUpdateControllers(); - - // Make sure all dataset controllers have correct meta data counts - for (i = 0, ilen = me.data.datasets.length; i < ilen; i++) { - me.getDatasetMeta(i).controller.buildOrUpdateElements(); - } - - me.updateLayout(); - - // Can only reset the new controllers after the scales have been updated - if (me.options.animation && me.options.animation.duration) { - helpers$1.each(newControllers, function(controller) { - controller.reset(); - }); - } - - me.updateDatasets(); - - // Need to reset tooltip in case it is displayed with elements that are removed - // after update. - me.tooltip.initialize(); - - // Last active contains items that were previously in the tooltip. - // When we reset the tooltip, we need to clear it - me.lastActive = []; - - // Do this before render so that any plugins that need final scale updates can use it - core_plugins.notify(me, 'afterUpdate'); - - me._layers.sort(compare2Level('z', '_idx')); - - if (me._bufferedRender) { - me._bufferedRequest = { - duration: config.duration, - easing: config.easing, - lazy: config.lazy - }; - } else { - me.render(config); - } - }, - - /** - * Updates the chart layout unless a plugin returns `false` to the `beforeLayout` - * hook, in which case, plugins will not be called on `afterLayout`. - * @private - */ - updateLayout: function() { - var me = this; - - if (core_plugins.notify(me, 'beforeLayout') === false) { - return; - } - - core_layouts.update(this, this.width, this.height); - - me._layers = []; - helpers$1.each(me.boxes, function(box) { - // _configure is called twice, once in core.scale.update and once here. - // Here the boxes are fully updated and at their final positions. - if (box._configure) { - box._configure(); - } - me._layers.push.apply(me._layers, box._layers()); - }, me); - - me._layers.forEach(function(item, index) { - item._idx = index; - }); - - /** - * Provided for backward compatibility, use `afterLayout` instead. - * @method IPlugin#afterScaleUpdate - * @deprecated since version 2.5.0 - * @todo remove at version 3 - * @private - */ - core_plugins.notify(me, 'afterScaleUpdate'); - core_plugins.notify(me, 'afterLayout'); - }, - - /** - * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate` - * hook, in which case, plugins will not be called on `afterDatasetsUpdate`. - * @private - */ - updateDatasets: function() { - var me = this; - - if (core_plugins.notify(me, 'beforeDatasetsUpdate') === false) { - return; - } - - for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { - me.updateDataset(i); - } - - core_plugins.notify(me, 'afterDatasetsUpdate'); - }, - - /** - * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate` - * hook, in which case, plugins will not be called on `afterDatasetUpdate`. - * @private - */ - updateDataset: function(index) { - var me = this; - var meta = me.getDatasetMeta(index); - var args = { - meta: meta, - index: index - }; - - if (core_plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) { - return; - } - - meta.controller._update(); - - core_plugins.notify(me, 'afterDatasetUpdate', [args]); - }, - - render: function(config) { - var me = this; - - if (!config || typeof config !== 'object') { - // backwards compatibility - config = { - duration: config, - lazy: arguments[1] - }; - } - - var animationOptions = me.options.animation; - var duration = valueOrDefault$9(config.duration, animationOptions && animationOptions.duration); - var lazy = config.lazy; - - if (core_plugins.notify(me, 'beforeRender') === false) { - return; - } - - var onComplete = function(animation) { - core_plugins.notify(me, 'afterRender'); - helpers$1.callback(animationOptions && animationOptions.onComplete, [animation], me); - }; - - if (animationOptions && duration) { - var animation = new core_animation({ - numSteps: duration / 16.66, // 60 fps - easing: config.easing || animationOptions.easing, - - render: function(chart, animationObject) { - var easingFunction = helpers$1.easing.effects[animationObject.easing]; - var currentStep = animationObject.currentStep; - var stepDecimal = currentStep / animationObject.numSteps; - - chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep); - }, - - onAnimationProgress: animationOptions.onProgress, - onAnimationComplete: onComplete - }); - - core_animations.addAnimation(me, animation, duration, lazy); - } else { - me.draw(); - - // See https://github.com/chartjs/Chart.js/issues/3781 - onComplete(new core_animation({numSteps: 0, chart: me})); - } - - return me; - }, - - draw: function(easingValue) { - var me = this; - var i, layers; - - me.clear(); - - if (helpers$1.isNullOrUndef(easingValue)) { - easingValue = 1; - } - - me.transition(easingValue); - - if (me.width <= 0 || me.height <= 0) { - return; - } - - if (core_plugins.notify(me, 'beforeDraw', [easingValue]) === false) { - return; - } - - // Because of plugin hooks (before/afterDatasetsDraw), datasets can't - // currently be part of layers. Instead, we draw - // layers <= 0 before(default, backward compat), and the rest after - layers = me._layers; - for (i = 0; i < layers.length && layers[i].z <= 0; ++i) { - layers[i].draw(me.chartArea); - } - - me.drawDatasets(easingValue); - - // Rest of layers - for (; i < layers.length; ++i) { - layers[i].draw(me.chartArea); - } - - me._drawTooltip(easingValue); - - core_plugins.notify(me, 'afterDraw', [easingValue]); - }, - - /** - * @private - */ - transition: function(easingValue) { - var me = this; - - for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) { - if (me.isDatasetVisible(i)) { - me.getDatasetMeta(i).controller.transition(easingValue); - } - } - - me.tooltip.transition(easingValue); - }, - - /** - * @private - */ - _getSortedDatasetMetas: function(filterVisible) { - var me = this; - var datasets = me.data.datasets || []; - var result = []; - var i, ilen; - - for (i = 0, ilen = datasets.length; i < ilen; ++i) { - if (!filterVisible || me.isDatasetVisible(i)) { - result.push(me.getDatasetMeta(i)); - } - } - - result.sort(compare2Level('order', 'index')); - - return result; - }, - - /** - * @private - */ - _getSortedVisibleDatasetMetas: function() { - return this._getSortedDatasetMetas(true); - }, - - /** - * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw` - * hook, in which case, plugins will not be called on `afterDatasetsDraw`. - * @private - */ - drawDatasets: function(easingValue) { - var me = this; - var metasets, i; - - if (core_plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) { - return; - } - - metasets = me._getSortedVisibleDatasetMetas(); - for (i = metasets.length - 1; i >= 0; --i) { - me.drawDataset(metasets[i], easingValue); - } - - core_plugins.notify(me, 'afterDatasetsDraw', [easingValue]); - }, - - /** - * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw` - * hook, in which case, plugins will not be called on `afterDatasetDraw`. - * @private - */ - drawDataset: function(meta, easingValue) { - var me = this; - var args = { - meta: meta, - index: meta.index, - easingValue: easingValue - }; - - if (core_plugins.notify(me, 'beforeDatasetDraw', [args]) === false) { - return; - } - - meta.controller.draw(easingValue); - - core_plugins.notify(me, 'afterDatasetDraw', [args]); - }, - - /** - * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw` - * hook, in which case, plugins will not be called on `afterTooltipDraw`. - * @private - */ - _drawTooltip: function(easingValue) { - var me = this; - var tooltip = me.tooltip; - var args = { - tooltip: tooltip, - easingValue: easingValue - }; - - if (core_plugins.notify(me, 'beforeTooltipDraw', [args]) === false) { - return; - } - - tooltip.draw(); - - core_plugins.notify(me, 'afterTooltipDraw', [args]); - }, - - /** - * Get the single element that was clicked on - * @return An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw - */ - getElementAtEvent: function(e) { - return core_interaction.modes.single(this, e); - }, - - getElementsAtEvent: function(e) { - return core_interaction.modes.label(this, e, {intersect: true}); - }, - - getElementsAtXAxis: function(e) { - return core_interaction.modes['x-axis'](this, e, {intersect: true}); - }, - - getElementsAtEventForMode: function(e, mode, options) { - var method = core_interaction.modes[mode]; - if (typeof method === 'function') { - return method(this, e, options); - } - - return []; - }, - - getDatasetAtEvent: function(e) { - return core_interaction.modes.dataset(this, e, {intersect: true}); - }, - - getDatasetMeta: function(datasetIndex) { - var me = this; - var dataset = me.data.datasets[datasetIndex]; - if (!dataset._meta) { - dataset._meta = {}; - } - - var meta = dataset._meta[me.id]; - if (!meta) { - meta = dataset._meta[me.id] = { - type: null, - data: [], - dataset: null, - controller: null, - hidden: null, // See isDatasetVisible() comment - xAxisID: null, - yAxisID: null, - order: dataset.order || 0, - index: datasetIndex - }; - } - - return meta; - }, - - getVisibleDatasetCount: function() { - var count = 0; - for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) { - if (this.isDatasetVisible(i)) { - count++; - } - } - return count; - }, - - isDatasetVisible: function(datasetIndex) { - var meta = this.getDatasetMeta(datasetIndex); - - // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false, - // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned. - return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden; - }, - - generateLegend: function() { - return this.options.legendCallback(this); - }, - - /** - * @private - */ - destroyDatasetMeta: function(datasetIndex) { - var id = this.id; - var dataset = this.data.datasets[datasetIndex]; - var meta = dataset._meta && dataset._meta[id]; - - if (meta) { - meta.controller.destroy(); - delete dataset._meta[id]; - } - }, - - destroy: function() { - var me = this; - var canvas = me.canvas; - var i, ilen; - - me.stop(); - - // dataset controllers need to cleanup associated data - for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) { - me.destroyDatasetMeta(i); - } - - if (canvas) { - me.unbindEvents(); - helpers$1.canvas.clear(me); - platform.releaseContext(me.ctx); - me.canvas = null; - me.ctx = null; - } - - core_plugins.notify(me, 'destroy'); - - delete Chart.instances[me.id]; - }, - - toBase64Image: function() { - return this.canvas.toDataURL.apply(this.canvas, arguments); - }, - - initToolTip: function() { - var me = this; - me.tooltip = new core_tooltip({ - _chart: me, - _chartInstance: me, // deprecated, backward compatibility - _data: me.data, - _options: me.options.tooltips - }, me); - }, - - /** - * @private - */ - bindEvents: function() { - var me = this; - var listeners = me._listeners = {}; - var listener = function() { - me.eventHandler.apply(me, arguments); - }; - - helpers$1.each(me.options.events, function(type) { - platform.addEventListener(me, type, listener); - listeners[type] = listener; - }); - - // Elements used to detect size change should not be injected for non responsive charts. - // See https://github.com/chartjs/Chart.js/issues/2210 - if (me.options.responsive) { - listener = function() { - me.resize(); - }; - - platform.addEventListener(me, 'resize', listener); - listeners.resize = listener; - } - }, - - /** - * @private - */ - unbindEvents: function() { - var me = this; - var listeners = me._listeners; - if (!listeners) { - return; - } - - delete me._listeners; - helpers$1.each(listeners, function(listener, type) { - platform.removeEventListener(me, type, listener); - }); - }, - - updateHoverStyle: function(elements, mode, enabled) { - var prefix = enabled ? 'set' : 'remove'; - var element, i, ilen; - - for (i = 0, ilen = elements.length; i < ilen; ++i) { - element = elements[i]; - if (element) { - this.getDatasetMeta(element._datasetIndex).controller[prefix + 'HoverStyle'](element); - } - } - - if (mode === 'dataset') { - this.getDatasetMeta(elements[0]._datasetIndex).controller['_' + prefix + 'DatasetHoverStyle'](); - } - }, - - /** - * @private - */ - eventHandler: function(e) { - var me = this; - var tooltip = me.tooltip; - - if (core_plugins.notify(me, 'beforeEvent', [e]) === false) { - return; - } - - // Buffer any update calls so that renders do not occur - me._bufferedRender = true; - me._bufferedRequest = null; - - var changed = me.handleEvent(e); - // for smooth tooltip animations issue #4989 - // the tooltip should be the source of change - // Animation check workaround: - // tooltip._start will be null when tooltip isn't animating - if (tooltip) { - changed = tooltip._start - ? tooltip.handleEvent(e) - : changed | tooltip.handleEvent(e); - } - - core_plugins.notify(me, 'afterEvent', [e]); - - var bufferedRequest = me._bufferedRequest; - if (bufferedRequest) { - // If we have an update that was triggered, we need to do a normal render - me.render(bufferedRequest); - } else if (changed && !me.animating) { - // If entering, leaving, or changing elements, animate the change via pivot - me.stop(); - - // We only need to render at this point. Updating will cause scales to be - // recomputed generating flicker & using more memory than necessary. - me.render({ - duration: me.options.hover.animationDuration, - lazy: true - }); - } - - me._bufferedRender = false; - me._bufferedRequest = null; - - return me; - }, - - /** - * Handle an event - * @private - * @param {IEvent} event the event to handle - * @return {boolean} true if the chart needs to re-render - */ - handleEvent: function(e) { - var me = this; - var options = me.options || {}; - var hoverOptions = options.hover; - var changed = false; - - me.lastActive = me.lastActive || []; - - // Find Active Elements for hover and tooltips - if (e.type === 'mouseout') { - me.active = []; - } else { - me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions); - } - - // Invoke onHover hook - // Need to call with native event here to not break backwards compatibility - helpers$1.callback(options.onHover || options.hover.onHover, [e.native, me.active], me); - - if (e.type === 'mouseup' || e.type === 'click') { - if (options.onClick) { - // Use e.native here for backwards compatibility - options.onClick.call(me, e.native, me.active); - } - } - - // Remove styling for last active (even if it may still be active) - if (me.lastActive.length) { - me.updateHoverStyle(me.lastActive, hoverOptions.mode, false); - } - - // Built in hover styling - if (me.active.length && hoverOptions.mode) { - me.updateHoverStyle(me.active, hoverOptions.mode, true); - } - - changed = !helpers$1.arrayEquals(me.active, me.lastActive); - - // Remember Last Actives - me.lastActive = me.active; - - return changed; - } -}); - -/** - * NOTE(SB) We actually don't use this container anymore but we need to keep it - * for backward compatibility. Though, it can still be useful for plugins that - * would need to work on multiple charts?! - */ -Chart.instances = {}; - -var core_controller = Chart; - -// DEPRECATIONS - -/** - * Provided for backward compatibility, use Chart instead. - * @class Chart.Controller - * @deprecated since version 2.6 - * @todo remove at version 3 - * @private - */ -Chart.Controller = Chart; - -/** - * Provided for backward compatibility, not available anymore. - * @namespace Chart - * @deprecated since version 2.8 - * @todo remove at version 3 - * @private - */ -Chart.types = {}; - -/** - * Provided for backward compatibility, not available anymore. - * @namespace Chart.helpers.configMerge - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ -helpers$1.configMerge = mergeConfig; - -/** - * Provided for backward compatibility, not available anymore. - * @namespace Chart.helpers.scaleMerge - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ -helpers$1.scaleMerge = mergeScaleConfig; - -var core_helpers = function() { - - // -- Basic js utility methods - - helpers$1.where = function(collection, filterCallback) { - if (helpers$1.isArray(collection) && Array.prototype.filter) { - return collection.filter(filterCallback); - } - var filtered = []; - - helpers$1.each(collection, function(item) { - if (filterCallback(item)) { - filtered.push(item); - } - }); - - return filtered; - }; - helpers$1.findIndex = Array.prototype.findIndex ? - function(array, callback, scope) { - return array.findIndex(callback, scope); - } : - function(array, callback, scope) { - scope = scope === undefined ? array : scope; - for (var i = 0, ilen = array.length; i < ilen; ++i) { - if (callback.call(scope, array[i], i, array)) { - return i; - } - } - return -1; - }; - helpers$1.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { - // Default to start of the array - if (helpers$1.isNullOrUndef(startIndex)) { - startIndex = -1; - } - for (var i = startIndex + 1; i < arrayToSearch.length; i++) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)) { - return currentItem; - } - } - }; - helpers$1.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { - // Default to end of the array - if (helpers$1.isNullOrUndef(startIndex)) { - startIndex = arrayToSearch.length; - } - for (var i = startIndex - 1; i >= 0; i--) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)) { - return currentItem; - } - } - }; - - // -- Math methods - helpers$1.isNumber = function(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - }; - helpers$1.almostEquals = function(x, y, epsilon) { - return Math.abs(x - y) < epsilon; - }; - helpers$1.almostWhole = function(x, epsilon) { - var rounded = Math.round(x); - return ((rounded - epsilon) <= x) && ((rounded + epsilon) >= x); - }; - helpers$1.max = function(array) { - return array.reduce(function(max, value) { - if (!isNaN(value)) { - return Math.max(max, value); - } - return max; - }, Number.NEGATIVE_INFINITY); - }; - helpers$1.min = function(array) { - return array.reduce(function(min, value) { - if (!isNaN(value)) { - return Math.min(min, value); - } - return min; - }, Number.POSITIVE_INFINITY); - }; - helpers$1.sign = Math.sign ? - function(x) { - return Math.sign(x); - } : - function(x) { - x = +x; // convert to a number - if (x === 0 || isNaN(x)) { - return x; - } - return x > 0 ? 1 : -1; - }; - helpers$1.toRadians = function(degrees) { - return degrees * (Math.PI / 180); - }; - helpers$1.toDegrees = function(radians) { - return radians * (180 / Math.PI); - }; - - /** - * Returns the number of decimal places - * i.e. the number of digits after the decimal point, of the value of this Number. - * @param {number} x - A number. - * @returns {number} The number of decimal places. - * @private - */ - helpers$1._decimalPlaces = function(x) { - if (!helpers$1.isFinite(x)) { - return; - } - var e = 1; - var p = 0; - while (Math.round(x * e) / e !== x) { - e *= 10; - p++; - } - return p; - }; - - // Gets the angle from vertical upright to the point about a centre. - helpers$1.getAngleFromPoint = function(centrePoint, anglePoint) { - var distanceFromXCenter = anglePoint.x - centrePoint.x; - var distanceFromYCenter = anglePoint.y - centrePoint.y; - var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); - - var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); - - if (angle < (-0.5 * Math.PI)) { - angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] - } - - return { - angle: angle, - distance: radialDistanceFromCenter - }; - }; - helpers$1.distanceBetweenPoints = function(pt1, pt2) { - return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2)); - }; - - /** - * Provided for backward compatibility, not available anymore - * @function Chart.helpers.aliasPixel - * @deprecated since version 2.8.0 - * @todo remove at version 3 - */ - helpers$1.aliasPixel = function(pixelWidth) { - return (pixelWidth % 2 === 0) ? 0 : 0.5; - }; - - /** - * Returns the aligned pixel value to avoid anti-aliasing blur - * @param {Chart} chart - The chart instance. - * @param {number} pixel - A pixel value. - * @param {number} width - The width of the element. - * @returns {number} The aligned pixel value. - * @private - */ - helpers$1._alignPixel = function(chart, pixel, width) { - var devicePixelRatio = chart.currentDevicePixelRatio; - var halfWidth = width / 2; - return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth; - }; - - helpers$1.splineCurve = function(firstPoint, middlePoint, afterPoint, t) { - // Props to Rob Spencer at scaled innovation for his post on splining between points - // http://scaledinnovation.com/analytics/splines/aboutSplines.html - - // This function must also respect "skipped" points - - var previous = firstPoint.skip ? middlePoint : firstPoint; - var current = middlePoint; - var next = afterPoint.skip ? middlePoint : afterPoint; - - var d01 = Math.sqrt(Math.pow(current.x - previous.x, 2) + Math.pow(current.y - previous.y, 2)); - var d12 = Math.sqrt(Math.pow(next.x - current.x, 2) + Math.pow(next.y - current.y, 2)); - - var s01 = d01 / (d01 + d12); - var s12 = d12 / (d01 + d12); - - // If all points are the same, s01 & s02 will be inf - s01 = isNaN(s01) ? 0 : s01; - s12 = isNaN(s12) ? 0 : s12; - - var fa = t * s01; // scaling factor for triangle Ta - var fb = t * s12; - - return { - previous: { - x: current.x - fa * (next.x - previous.x), - y: current.y - fa * (next.y - previous.y) - }, - next: { - x: current.x + fb * (next.x - previous.x), - y: current.y + fb * (next.y - previous.y) - } - }; - }; - helpers$1.EPSILON = Number.EPSILON || 1e-14; - helpers$1.splineCurveMonotone = function(points) { - // This function calculates Bézier control points in a similar way than |splineCurve|, - // but preserves monotonicity of the provided data and ensures no local extremums are added - // between the dataset discrete points due to the interpolation. - // See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation - - var pointsWithTangents = (points || []).map(function(point) { - return { - model: point._model, - deltaK: 0, - mK: 0 - }; - }); - - // Calculate slopes (deltaK) and initialize tangents (mK) - var pointsLen = pointsWithTangents.length; - var i, pointBefore, pointCurrent, pointAfter; - for (i = 0; i < pointsLen; ++i) { - pointCurrent = pointsWithTangents[i]; - if (pointCurrent.model.skip) { - continue; - } - - pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; - pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; - if (pointAfter && !pointAfter.model.skip) { - var slopeDeltaX = (pointAfter.model.x - pointCurrent.model.x); - - // In the case of two points that appear at the same x pixel, slopeDeltaX is 0 - pointCurrent.deltaK = slopeDeltaX !== 0 ? (pointAfter.model.y - pointCurrent.model.y) / slopeDeltaX : 0; - } - - if (!pointBefore || pointBefore.model.skip) { - pointCurrent.mK = pointCurrent.deltaK; - } else if (!pointAfter || pointAfter.model.skip) { - pointCurrent.mK = pointBefore.deltaK; - } else if (this.sign(pointBefore.deltaK) !== this.sign(pointCurrent.deltaK)) { - pointCurrent.mK = 0; - } else { - pointCurrent.mK = (pointBefore.deltaK + pointCurrent.deltaK) / 2; - } - } - - // Adjust tangents to ensure monotonic properties - var alphaK, betaK, tauK, squaredMagnitude; - for (i = 0; i < pointsLen - 1; ++i) { - pointCurrent = pointsWithTangents[i]; - pointAfter = pointsWithTangents[i + 1]; - if (pointCurrent.model.skip || pointAfter.model.skip) { - continue; - } - - if (helpers$1.almostEquals(pointCurrent.deltaK, 0, this.EPSILON)) { - pointCurrent.mK = pointAfter.mK = 0; - continue; - } - - alphaK = pointCurrent.mK / pointCurrent.deltaK; - betaK = pointAfter.mK / pointCurrent.deltaK; - squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2); - if (squaredMagnitude <= 9) { - continue; - } - - tauK = 3 / Math.sqrt(squaredMagnitude); - pointCurrent.mK = alphaK * tauK * pointCurrent.deltaK; - pointAfter.mK = betaK * tauK * pointCurrent.deltaK; - } - - // Compute control points - var deltaX; - for (i = 0; i < pointsLen; ++i) { - pointCurrent = pointsWithTangents[i]; - if (pointCurrent.model.skip) { - continue; - } - - pointBefore = i > 0 ? pointsWithTangents[i - 1] : null; - pointAfter = i < pointsLen - 1 ? pointsWithTangents[i + 1] : null; - if (pointBefore && !pointBefore.model.skip) { - deltaX = (pointCurrent.model.x - pointBefore.model.x) / 3; - pointCurrent.model.controlPointPreviousX = pointCurrent.model.x - deltaX; - pointCurrent.model.controlPointPreviousY = pointCurrent.model.y - deltaX * pointCurrent.mK; - } - if (pointAfter && !pointAfter.model.skip) { - deltaX = (pointAfter.model.x - pointCurrent.model.x) / 3; - pointCurrent.model.controlPointNextX = pointCurrent.model.x + deltaX; - pointCurrent.model.controlPointNextY = pointCurrent.model.y + deltaX * pointCurrent.mK; - } - } - }; - helpers$1.nextItem = function(collection, index, loop) { - if (loop) { - return index >= collection.length - 1 ? collection[0] : collection[index + 1]; - } - return index >= collection.length - 1 ? collection[collection.length - 1] : collection[index + 1]; - }; - helpers$1.previousItem = function(collection, index, loop) { - if (loop) { - return index <= 0 ? collection[collection.length - 1] : collection[index - 1]; - } - return index <= 0 ? collection[0] : collection[index - 1]; - }; - // Implementation of the nice number algorithm used in determining where axis labels will go - helpers$1.niceNum = function(range, round) { - var exponent = Math.floor(helpers$1.log10(range)); - var fraction = range / Math.pow(10, exponent); - var niceFraction; - - if (round) { - if (fraction < 1.5) { - niceFraction = 1; - } else if (fraction < 3) { - niceFraction = 2; - } else if (fraction < 7) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } else if (fraction <= 1.0) { - niceFraction = 1; - } else if (fraction <= 2) { - niceFraction = 2; - } else if (fraction <= 5) { - niceFraction = 5; - } else { - niceFraction = 10; - } - - return niceFraction * Math.pow(10, exponent); - }; - // Request animation polyfill - https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ - helpers$1.requestAnimFrame = (function() { - if (typeof window === 'undefined') { - return function(callback) { - callback(); - }; - } - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, 1000 / 60); - }; - }()); - // -- DOM methods - helpers$1.getRelativePosition = function(evt, chart) { - var mouseX, mouseY; - var e = evt.originalEvent || evt; - var canvas = evt.target || evt.srcElement; - var boundingRect = canvas.getBoundingClientRect(); - - var touches = e.touches; - if (touches && touches.length > 0) { - mouseX = touches[0].clientX; - mouseY = touches[0].clientY; - - } else { - mouseX = e.clientX; - mouseY = e.clientY; - } - - // Scale mouse coordinates into canvas coordinates - // by following the pattern laid out by 'jerryj' in the comments of - // https://www.html5canvastutorials.com/advanced/html5-canvas-mouse-coordinates/ - var paddingLeft = parseFloat(helpers$1.getStyle(canvas, 'padding-left')); - var paddingTop = parseFloat(helpers$1.getStyle(canvas, 'padding-top')); - var paddingRight = parseFloat(helpers$1.getStyle(canvas, 'padding-right')); - var paddingBottom = parseFloat(helpers$1.getStyle(canvas, 'padding-bottom')); - var width = boundingRect.right - boundingRect.left - paddingLeft - paddingRight; - var height = boundingRect.bottom - boundingRect.top - paddingTop - paddingBottom; - - // We divide by the current device pixel ratio, because the canvas is scaled up by that amount in each direction. However - // the backend model is in unscaled coordinates. Since we are going to deal with our model coordinates, we go back here - mouseX = Math.round((mouseX - boundingRect.left - paddingLeft) / (width) * canvas.width / chart.currentDevicePixelRatio); - mouseY = Math.round((mouseY - boundingRect.top - paddingTop) / (height) * canvas.height / chart.currentDevicePixelRatio); - - return { - x: mouseX, - y: mouseY - }; - - }; - - // Private helper function to convert max-width/max-height values that may be percentages into a number - function parseMaxStyle(styleValue, node, parentProperty) { - var valueInPixels; - if (typeof styleValue === 'string') { - valueInPixels = parseInt(styleValue, 10); - - if (styleValue.indexOf('%') !== -1) { - // percentage * size in dimension - valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty]; - } - } else { - valueInPixels = styleValue; - } - - return valueInPixels; - } - - /** - * Returns if the given value contains an effective constraint. - * @private - */ - function isConstrainedValue(value) { - return value !== undefined && value !== null && value !== 'none'; - } - - /** - * Returns the max width or height of the given DOM node in a cross-browser compatible fashion - * @param {HTMLElement} domNode - the node to check the constraint on - * @param {string} maxStyle - the style that defines the maximum for the direction we are using ('max-width' / 'max-height') - * @param {string} percentageProperty - property of parent to use when calculating width as a percentage - * @see {@link https://www.nathanaeljones.com/blog/2013/reading-max-width-cross-browser} - */ - function getConstraintDimension(domNode, maxStyle, percentageProperty) { - var view = document.defaultView; - var parentNode = helpers$1._getParentNode(domNode); - var constrainedNode = view.getComputedStyle(domNode)[maxStyle]; - var constrainedContainer = view.getComputedStyle(parentNode)[maxStyle]; - var hasCNode = isConstrainedValue(constrainedNode); - var hasCContainer = isConstrainedValue(constrainedContainer); - var infinity = Number.POSITIVE_INFINITY; - - if (hasCNode || hasCContainer) { - return Math.min( - hasCNode ? parseMaxStyle(constrainedNode, domNode, percentageProperty) : infinity, - hasCContainer ? parseMaxStyle(constrainedContainer, parentNode, percentageProperty) : infinity); - } - - return 'none'; - } - // returns Number or undefined if no constraint - helpers$1.getConstraintWidth = function(domNode) { - return getConstraintDimension(domNode, 'max-width', 'clientWidth'); - }; - // returns Number or undefined if no constraint - helpers$1.getConstraintHeight = function(domNode) { - return getConstraintDimension(domNode, 'max-height', 'clientHeight'); - }; - /** - * @private - */ - helpers$1._calculatePadding = function(container, padding, parentDimension) { - padding = helpers$1.getStyle(container, padding); - - return padding.indexOf('%') > -1 ? parentDimension * parseInt(padding, 10) / 100 : parseInt(padding, 10); - }; - /** - * @private - */ - helpers$1._getParentNode = function(domNode) { - var parent = domNode.parentNode; - if (parent && parent.toString() === '[object ShadowRoot]') { - parent = parent.host; - } - return parent; - }; - helpers$1.getMaximumWidth = function(domNode) { - var container = helpers$1._getParentNode(domNode); - if (!container) { - return domNode.clientWidth; - } - - var clientWidth = container.clientWidth; - var paddingLeft = helpers$1._calculatePadding(container, 'padding-left', clientWidth); - var paddingRight = helpers$1._calculatePadding(container, 'padding-right', clientWidth); - - var w = clientWidth - paddingLeft - paddingRight; - var cw = helpers$1.getConstraintWidth(domNode); - return isNaN(cw) ? w : Math.min(w, cw); - }; - helpers$1.getMaximumHeight = function(domNode) { - var container = helpers$1._getParentNode(domNode); - if (!container) { - return domNode.clientHeight; - } - - var clientHeight = container.clientHeight; - var paddingTop = helpers$1._calculatePadding(container, 'padding-top', clientHeight); - var paddingBottom = helpers$1._calculatePadding(container, 'padding-bottom', clientHeight); - - var h = clientHeight - paddingTop - paddingBottom; - var ch = helpers$1.getConstraintHeight(domNode); - return isNaN(ch) ? h : Math.min(h, ch); - }; - helpers$1.getStyle = function(el, property) { - return el.currentStyle ? - el.currentStyle[property] : - document.defaultView.getComputedStyle(el, null).getPropertyValue(property); - }; - helpers$1.retinaScale = function(chart, forceRatio) { - var pixelRatio = chart.currentDevicePixelRatio = forceRatio || (typeof window !== 'undefined' && window.devicePixelRatio) || 1; - if (pixelRatio === 1) { - return; - } - - var canvas = chart.canvas; - var height = chart.height; - var width = chart.width; - - canvas.height = height * pixelRatio; - canvas.width = width * pixelRatio; - chart.ctx.scale(pixelRatio, pixelRatio); - - // If no style has been set on the canvas, the render size is used as display size, - // making the chart visually bigger, so let's enforce it to the "correct" values. - // See https://github.com/chartjs/Chart.js/issues/3575 - if (!canvas.style.height && !canvas.style.width) { - canvas.style.height = height + 'px'; - canvas.style.width = width + 'px'; - } - }; - // -- Canvas methods - helpers$1.fontString = function(pixelSize, fontStyle, fontFamily) { - return fontStyle + ' ' + pixelSize + 'px ' + fontFamily; - }; - helpers$1.longestText = function(ctx, font, arrayOfThings, cache) { - cache = cache || {}; - var data = cache.data = cache.data || {}; - var gc = cache.garbageCollect = cache.garbageCollect || []; - - if (cache.font !== font) { - data = cache.data = {}; - gc = cache.garbageCollect = []; - cache.font = font; - } - - ctx.font = font; - var longest = 0; - var ilen = arrayOfThings.length; - var i, j, jlen, thing, nestedThing; - for (i = 0; i < ilen; i++) { - thing = arrayOfThings[i]; - - // Undefined strings and arrays should not be measured - if (thing !== undefined && thing !== null && helpers$1.isArray(thing) !== true) { - longest = helpers$1.measureText(ctx, data, gc, longest, thing); - } else if (helpers$1.isArray(thing)) { - // if it is an array lets measure each element - // to do maybe simplify this function a bit so we can do this more recursively? - for (j = 0, jlen = thing.length; j < jlen; j++) { - nestedThing = thing[j]; - // Undefined strings and arrays should not be measured - if (nestedThing !== undefined && nestedThing !== null && !helpers$1.isArray(nestedThing)) { - longest = helpers$1.measureText(ctx, data, gc, longest, nestedThing); - } - } - } - } - - var gcLen = gc.length / 2; - if (gcLen > arrayOfThings.length) { - for (i = 0; i < gcLen; i++) { - delete data[gc[i]]; - } - gc.splice(0, gcLen); - } - return longest; - }; - helpers$1.measureText = function(ctx, data, gc, longest, string) { - var textWidth = data[string]; - if (!textWidth) { - textWidth = data[string] = ctx.measureText(string).width; - gc.push(string); - } - if (textWidth > longest) { - longest = textWidth; - } - return longest; - }; - - /** - * @deprecated - */ - helpers$1.numberOfLabelLines = function(arrayOfThings) { - var numberOfLines = 1; - helpers$1.each(arrayOfThings, function(thing) { - if (helpers$1.isArray(thing)) { - if (thing.length > numberOfLines) { - numberOfLines = thing.length; - } - } - }); - return numberOfLines; - }; - - helpers$1.color = !chartjsColor ? - function(value) { - console.error('Color.js not found!'); - return value; - } : - function(value) { - /* global CanvasGradient */ - if (value instanceof CanvasGradient) { - value = core_defaults.global.defaultColor; - } - - return chartjsColor(value); - }; - - helpers$1.getHoverColor = function(colorValue) { - /* global CanvasPattern */ - return (colorValue instanceof CanvasPattern || colorValue instanceof CanvasGradient) ? - colorValue : - helpers$1.color(colorValue).saturate(0.5).darken(0.1).rgbString(); - }; -}; - -function abstract() { - throw new Error( - 'This method is not implemented: either no adapter can ' + - 'be found or an incomplete integration was provided.' - ); -} - -/** - * Date adapter (current used by the time scale) - * @namespace Chart._adapters._date - * @memberof Chart._adapters - * @private - */ - -/** - * Currently supported unit string values. - * @typedef {('millisecond'|'second'|'minute'|'hour'|'day'|'week'|'month'|'quarter'|'year')} - * @memberof Chart._adapters._date - * @name Unit - */ - -/** - * @class - */ -function DateAdapter(options) { - this.options = options || {}; -} - -helpers$1.extend(DateAdapter.prototype, /** @lends DateAdapter */ { - /** - * Returns a map of time formats for the supported formatting units defined - * in Unit as well as 'datetime' representing a detailed date/time string. - * @returns {{string: string}} - */ - formats: abstract, - - /** - * Parses the given `value` and return the associated timestamp. - * @param {any} value - the value to parse (usually comes from the data) - * @param {string} [format] - the expected data format - * @returns {(number|null)} - * @function - */ - parse: abstract, - - /** - * Returns the formatted date in the specified `format` for a given `timestamp`. - * @param {number} timestamp - the timestamp to format - * @param {string} format - the date/time token - * @return {string} - * @function - */ - format: abstract, - - /** - * Adds the specified `amount` of `unit` to the given `timestamp`. - * @param {number} timestamp - the input timestamp - * @param {number} amount - the amount to add - * @param {Unit} unit - the unit as string - * @return {number} - * @function - */ - add: abstract, - - /** - * Returns the number of `unit` between the given timestamps. - * @param {number} max - the input timestamp (reference) - * @param {number} min - the timestamp to substract - * @param {Unit} unit - the unit as string - * @return {number} - * @function - */ - diff: abstract, - - /** - * Returns start of `unit` for the given `timestamp`. - * @param {number} timestamp - the input timestamp - * @param {Unit} unit - the unit as string - * @param {number} [weekday] - the ISO day of the week with 1 being Monday - * and 7 being Sunday (only needed if param *unit* is `isoWeek`). - * @function - */ - startOf: abstract, - - /** - * Returns end of `unit` for the given `timestamp`. - * @param {number} timestamp - the input timestamp - * @param {Unit} unit - the unit as string - * @function - */ - endOf: abstract, - - // DEPRECATIONS - - /** - * Provided for backward compatibility for scale.getValueForPixel(), - * this method should be overridden only by the moment adapter. - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ - _create: function(value) { - return value; - } -}); - -DateAdapter.override = function(members) { - helpers$1.extend(DateAdapter.prototype, members); -}; - -var _date = DateAdapter; - -var core_adapters = { - _date: _date -}; - -/** - * Namespace to hold static tick generation functions - * @namespace Chart.Ticks - */ -var core_ticks = { - /** - * Namespace to hold formatters for different types of ticks - * @namespace Chart.Ticks.formatters - */ - formatters: { - /** - * Formatter for value labels - * @method Chart.Ticks.formatters.values - * @param value the value to display - * @return {string|string[]} the label to display - */ - values: function(value) { - return helpers$1.isArray(value) ? value : '' + value; - }, - - /** - * Formatter for linear numeric ticks - * @method Chart.Ticks.formatters.linear - * @param tickValue {number} the value to be formatted - * @param index {number} the position of the tickValue parameter in the ticks array - * @param ticks {number[]} the list of ticks being converted - * @return {string} string representation of the tickValue parameter - */ - linear: function(tickValue, index, ticks) { - // If we have lots of ticks, don't use the ones - var delta = ticks.length > 3 ? ticks[2] - ticks[1] : ticks[1] - ticks[0]; - - // If we have a number like 2.5 as the delta, figure out how many decimal places we need - if (Math.abs(delta) > 1) { - if (tickValue !== Math.floor(tickValue)) { - // not an integer - delta = tickValue - Math.floor(tickValue); - } - } - - var logDelta = helpers$1.log10(Math.abs(delta)); - var tickString = ''; - - if (tickValue !== 0) { - var maxTick = Math.max(Math.abs(ticks[0]), Math.abs(ticks[ticks.length - 1])); - if (maxTick < 1e-4) { // all ticks are small numbers; use scientific notation - var logTick = helpers$1.log10(Math.abs(tickValue)); - var numExponential = Math.floor(logTick) - Math.floor(logDelta); - numExponential = Math.max(Math.min(numExponential, 20), 0); - tickString = tickValue.toExponential(numExponential); - } else { - var numDecimal = -1 * Math.floor(logDelta); - numDecimal = Math.max(Math.min(numDecimal, 20), 0); // toFixed has a max of 20 decimal places - tickString = tickValue.toFixed(numDecimal); - } - } else { - tickString = '0'; // never show decimal places for 0 - } - - return tickString; - }, - - logarithmic: function(tickValue, index, ticks) { - var remain = tickValue / (Math.pow(10, Math.floor(helpers$1.log10(tickValue)))); - - if (tickValue === 0) { - return '0'; - } else if (remain === 1 || remain === 2 || remain === 5 || index === 0 || index === ticks.length - 1) { - return tickValue.toExponential(); - } - return ''; - } - } -}; - -var isArray = helpers$1.isArray; -var isNullOrUndef = helpers$1.isNullOrUndef; -var valueOrDefault$a = helpers$1.valueOrDefault; -var valueAtIndexOrDefault = helpers$1.valueAtIndexOrDefault; - -core_defaults._set('scale', { - display: true, - position: 'left', - offset: false, - - // grid line settings - gridLines: { - display: true, - color: 'rgba(0,0,0,0.1)', - lineWidth: 1, - drawBorder: true, - drawOnChartArea: true, - drawTicks: true, - tickMarkLength: 10, - zeroLineWidth: 1, - zeroLineColor: 'rgba(0,0,0,0.25)', - zeroLineBorderDash: [], - zeroLineBorderDashOffset: 0.0, - offsetGridLines: false, - borderDash: [], - borderDashOffset: 0.0 - }, - - // scale label - scaleLabel: { - // display property - display: false, - - // actual label - labelString: '', - - // top/bottom padding - padding: { - top: 4, - bottom: 4 - } - }, - - // label settings - ticks: { - beginAtZero: false, - minRotation: 0, - maxRotation: 50, - mirror: false, - padding: 0, - reverse: false, - display: true, - autoSkip: true, - autoSkipPadding: 0, - labelOffset: 0, - // We pass through arrays to be rendered as multiline labels, we convert Others to strings here. - callback: core_ticks.formatters.values, - minor: {}, - major: {} - } -}); - -/** Returns a new array containing numItems from arr */ -function sample(arr, numItems) { - var result = []; - var increment = arr.length / numItems; - var i = 0; - var len = arr.length; - - for (; i < len; i += increment) { - result.push(arr[Math.floor(i)]); - } - return result; -} - -function getPixelForGridLine(scale, index, offsetGridLines) { - var length = scale.getTicks().length; - var validIndex = Math.min(index, length - 1); - var lineValue = scale.getPixelForTick(validIndex); - var start = scale._startPixel; - var end = scale._endPixel; - var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error. - var offset; - - if (offsetGridLines) { - if (length === 1) { - offset = Math.max(lineValue - start, end - lineValue); - } else if (index === 0) { - offset = (scale.getPixelForTick(1) - lineValue) / 2; - } else { - offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2; - } - lineValue += validIndex < index ? offset : -offset; - - // Return undefined if the pixel is out of the range - if (lineValue < start - epsilon || lineValue > end + epsilon) { - return; - } - } - return lineValue; -} - -function garbageCollect(caches, length) { - helpers$1.each(caches, function(cache) { - var gc = cache.gc; - var gcLen = gc.length / 2; - var i; - if (gcLen > length) { - for (i = 0; i < gcLen; ++i) { - delete cache.data[gc[i]]; - } - gc.splice(0, gcLen); - } - }); -} - -/** - * Returns {width, height, offset} objects for the first, last, widest, highest tick - * labels where offset indicates the anchor point offset from the top in pixels. - */ -function computeLabelSizes(ctx, tickFonts, ticks, caches) { - var length = ticks.length; - var widths = []; - var heights = []; - var offsets = []; - var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel, widest, highest; - - for (i = 0; i < length; ++i) { - label = ticks[i].label; - tickFont = ticks[i].major ? tickFonts.major : tickFonts.minor; - ctx.font = fontString = tickFont.string; - cache = caches[fontString] = caches[fontString] || {data: {}, gc: []}; - lineHeight = tickFont.lineHeight; - width = height = 0; - // Undefined labels and arrays should not be measured - if (!isNullOrUndef(label) && !isArray(label)) { - width = helpers$1.measureText(ctx, cache.data, cache.gc, width, label); - height = lineHeight; - } else if (isArray(label)) { - // if it is an array let's measure each element - for (j = 0, jlen = label.length; j < jlen; ++j) { - nestedLabel = label[j]; - // Undefined labels and arrays should not be measured - if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) { - width = helpers$1.measureText(ctx, cache.data, cache.gc, width, nestedLabel); - height += lineHeight; - } - } - } - widths.push(width); - heights.push(height); - offsets.push(lineHeight / 2); - } - garbageCollect(caches, length); - - widest = widths.indexOf(Math.max.apply(null, widths)); - highest = heights.indexOf(Math.max.apply(null, heights)); - - function valueAt(idx) { - return { - width: widths[idx] || 0, - height: heights[idx] || 0, - offset: offsets[idx] || 0 - }; - } - - return { - first: valueAt(0), - last: valueAt(length - 1), - widest: valueAt(widest), - highest: valueAt(highest) - }; -} - -function getTickMarkLength(options) { - return options.drawTicks ? options.tickMarkLength : 0; -} - -function getScaleLabelHeight(options) { - var font, padding; - - if (!options.display) { - return 0; - } - - font = helpers$1.options._parseFont(options); - padding = helpers$1.options.toPadding(options.padding); - - return font.lineHeight + padding.height; -} - -function parseFontOptions(options, nestedOpts) { - return helpers$1.extend(helpers$1.options._parseFont({ - fontFamily: valueOrDefault$a(nestedOpts.fontFamily, options.fontFamily), - fontSize: valueOrDefault$a(nestedOpts.fontSize, options.fontSize), - fontStyle: valueOrDefault$a(nestedOpts.fontStyle, options.fontStyle), - lineHeight: valueOrDefault$a(nestedOpts.lineHeight, options.lineHeight) - }), { - color: helpers$1.options.resolve([nestedOpts.fontColor, options.fontColor, core_defaults.global.defaultFontColor]) - }); -} - -function parseTickFontOptions(options) { - var minor = parseFontOptions(options, options.minor); - var major = options.major.enabled ? parseFontOptions(options, options.major) : minor; - - return {minor: minor, major: major}; -} - -function nonSkipped(ticksToFilter) { - var filtered = []; - var item, index, len; - for (index = 0, len = ticksToFilter.length; index < len; ++index) { - item = ticksToFilter[index]; - if (typeof item._index !== 'undefined') { - filtered.push(item); - } - } - return filtered; -} - -function getEvenSpacing(arr) { - var len = arr.length; - var i, diff; - - if (len < 2) { - return false; - } - - for (diff = arr[0], i = 1; i < len; ++i) { - if (arr[i] - arr[i - 1] !== diff) { - return false; - } - } - return diff; -} - -function calculateSpacing(majorIndices, ticks, axisLength, ticksLimit) { - var evenMajorSpacing = getEvenSpacing(majorIndices); - var spacing = (ticks.length - 1) / ticksLimit; - var factors, factor, i, ilen; - - // If the major ticks are evenly spaced apart, place the minor ticks - // so that they divide the major ticks into even chunks - if (!evenMajorSpacing) { - return Math.max(spacing, 1); - } - - factors = helpers$1.math._factorize(evenMajorSpacing); - for (i = 0, ilen = factors.length - 1; i < ilen; i++) { - factor = factors[i]; - if (factor > spacing) { - return factor; - } - } - return Math.max(spacing, 1); -} - -function getMajorIndices(ticks) { - var result = []; - var i, ilen; - for (i = 0, ilen = ticks.length; i < ilen; i++) { - if (ticks[i].major) { - result.push(i); - } - } - return result; -} - -function skipMajors(ticks, majorIndices, spacing) { - var count = 0; - var next = majorIndices[0]; - var i, tick; - - spacing = Math.ceil(spacing); - for (i = 0; i < ticks.length; i++) { - tick = ticks[i]; - if (i === next) { - tick._index = i; - count++; - next = majorIndices[count * spacing]; - } else { - delete tick.label; - } - } -} - -function skip(ticks, spacing, majorStart, majorEnd) { - var start = valueOrDefault$a(majorStart, 0); - var end = Math.min(valueOrDefault$a(majorEnd, ticks.length), ticks.length); - var count = 0; - var length, i, tick, next; - - spacing = Math.ceil(spacing); - if (majorEnd) { - length = majorEnd - majorStart; - spacing = length / Math.floor(length / spacing); - } - - next = start; - - while (next < 0) { - count++; - next = Math.round(start + count * spacing); - } - - for (i = Math.max(start, 0); i < end; i++) { - tick = ticks[i]; - if (i === next) { - tick._index = i; - count++; - next = Math.round(start + count * spacing); - } else { - delete tick.label; - } - } -} - -var Scale = core_element.extend({ - - zeroLineIndex: 0, - - /** - * Get the padding needed for the scale - * @method getPadding - * @private - * @returns {Padding} the necessary padding - */ - getPadding: function() { - var me = this; - return { - left: me.paddingLeft || 0, - top: me.paddingTop || 0, - right: me.paddingRight || 0, - bottom: me.paddingBottom || 0 - }; - }, - - /** - * Returns the scale tick objects ({label, major}) - * @since 2.7 - */ - getTicks: function() { - return this._ticks; - }, - - /** - * @private - */ - _getLabels: function() { - var data = this.chart.data; - return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || []; - }, - - // These methods are ordered by lifecyle. Utilities then follow. - // Any function defined here is inherited by all scale types. - // Any function can be extended by the scale type - - /** - * Provided for backward compatibility, not available anymore - * @function Chart.Scale.mergeTicksOptions - * @deprecated since version 2.8.0 - * @todo remove at version 3 - */ - mergeTicksOptions: function() { - // noop - }, - - beforeUpdate: function() { - helpers$1.callback(this.options.beforeUpdate, [this]); - }, - - /** - * @param {number} maxWidth - the max width in pixels - * @param {number} maxHeight - the max height in pixels - * @param {object} margins - the space between the edge of the other scales and edge of the chart - * This space comes from two sources: - * - padding - space that's required to show the labels at the edges of the scale - * - thickness of scales or legends in another orientation - */ - update: function(maxWidth, maxHeight, margins) { - var me = this; - var tickOpts = me.options.ticks; - var sampleSize = tickOpts.sampleSize; - var i, ilen, labels, ticks, samplingEnabled; - - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); - - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = helpers$1.extend({ - left: 0, - right: 0, - top: 0, - bottom: 0 - }, margins); - - me._ticks = null; - me.ticks = null; - me._labelSizes = null; - me._maxLabelLines = 0; - me.longestLabelWidth = 0; - me.longestTextCache = me.longestTextCache || {}; - me._gridLineItems = null; - me._labelItems = null; - - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - - // Data min/max - me.beforeDataLimits(); - me.determineDataLimits(); - me.afterDataLimits(); - - // Ticks - `this.ticks` is now DEPRECATED! - // Internal ticks are now stored as objects in the PRIVATE `this._ticks` member - // and must not be accessed directly from outside this class. `this.ticks` being - // around for long time and not marked as private, we can't change its structure - // without unexpected breaking changes. If you need to access the scale ticks, - // use scale.getTicks() instead. - - me.beforeBuildTicks(); - - // New implementations should return an array of objects but for BACKWARD COMPAT, - // we still support no return (`this.ticks` internally set by calling this method). - ticks = me.buildTicks() || []; - - // Allow modification of ticks in callback. - ticks = me.afterBuildTicks(ticks) || ticks; - - // Ensure ticks contains ticks in new tick format - if ((!ticks || !ticks.length) && me.ticks) { - ticks = []; - for (i = 0, ilen = me.ticks.length; i < ilen; ++i) { - ticks.push({ - value: me.ticks[i], - major: false - }); - } - } - - me._ticks = ticks; - - // Compute tick rotation and fit using a sampled subset of labels - // We generally don't need to compute the size of every single label for determining scale size - samplingEnabled = sampleSize < ticks.length; - labels = me._convertTicksToLabels(samplingEnabled ? sample(ticks, sampleSize) : ticks); - - // _configure is called twice, once here, once from core.controller.updateLayout. - // Here we haven't been positioned yet, but dimensions are correct. - // Variables set in _configure are needed for calculateTickRotation, and - // it's ok that coordinates are not correct there, only dimensions matter. - me._configure(); - - // Tick Rotation - me.beforeCalculateTickRotation(); - me.calculateTickRotation(); - me.afterCalculateTickRotation(); - - me.beforeFit(); - me.fit(); - me.afterFit(); - - // Auto-skip - me._ticksToDraw = tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto') ? me._autoSkip(ticks) : ticks; - - if (samplingEnabled) { - // Generate labels using all non-skipped ticks - labels = me._convertTicksToLabels(me._ticksToDraw); - } - - me.ticks = labels; // BACKWARD COMPATIBILITY - - // IMPORTANT: after this point, we consider that `this.ticks` will NEVER change! - - me.afterUpdate(); - - // TODO(v3): remove minSize as a public property and return value from all layout boxes. It is unused - // make maxWidth and maxHeight private - return me.minSize; - }, - - /** - * @private - */ - _configure: function() { - var me = this; - var reversePixels = me.options.ticks.reverse; - var startPixel, endPixel; - - if (me.isHorizontal()) { - startPixel = me.left; - endPixel = me.right; - } else { - startPixel = me.top; - endPixel = me.bottom; - // by default vertical scales are from bottom to top, so pixels are reversed - reversePixels = !reversePixels; - } - me._startPixel = startPixel; - me._endPixel = endPixel; - me._reversePixels = reversePixels; - me._length = endPixel - startPixel; - }, - - afterUpdate: function() { - helpers$1.callback(this.options.afterUpdate, [this]); - }, - - // - - beforeSetDimensions: function() { - helpers$1.callback(this.options.beforeSetDimensions, [this]); - }, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; - - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } - - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; - }, - afterSetDimensions: function() { - helpers$1.callback(this.options.afterSetDimensions, [this]); - }, - - // Data limits - beforeDataLimits: function() { - helpers$1.callback(this.options.beforeDataLimits, [this]); - }, - determineDataLimits: helpers$1.noop, - afterDataLimits: function() { - helpers$1.callback(this.options.afterDataLimits, [this]); - }, - - // - beforeBuildTicks: function() { - helpers$1.callback(this.options.beforeBuildTicks, [this]); - }, - buildTicks: helpers$1.noop, - afterBuildTicks: function(ticks) { - var me = this; - // ticks is empty for old axis implementations here - if (isArray(ticks) && ticks.length) { - return helpers$1.callback(me.options.afterBuildTicks, [me, ticks]); - } - // Support old implementations (that modified `this.ticks` directly in buildTicks) - me.ticks = helpers$1.callback(me.options.afterBuildTicks, [me, me.ticks]) || me.ticks; - return ticks; - }, - - beforeTickToLabelConversion: function() { - helpers$1.callback(this.options.beforeTickToLabelConversion, [this]); - }, - convertTicksToLabels: function() { - var me = this; - // Convert ticks to strings - var tickOpts = me.options.ticks; - me.ticks = me.ticks.map(tickOpts.userCallback || tickOpts.callback, this); - }, - afterTickToLabelConversion: function() { - helpers$1.callback(this.options.afterTickToLabelConversion, [this]); - }, - - // - - beforeCalculateTickRotation: function() { - helpers$1.callback(this.options.beforeCalculateTickRotation, [this]); - }, - calculateTickRotation: function() { - var me = this; - var options = me.options; - var tickOpts = options.ticks; - var numTicks = me.getTicks().length; - var minRotation = tickOpts.minRotation || 0; - var maxRotation = tickOpts.maxRotation; - var labelRotation = minRotation; - var labelSizes, maxLabelWidth, maxLabelHeight, maxWidth, tickWidth, maxHeight, maxLabelDiagonal; - - if (!me._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !me.isHorizontal()) { - me.labelRotation = minRotation; - return; - } - - labelSizes = me._getLabelSizes(); - maxLabelWidth = labelSizes.widest.width; - maxLabelHeight = labelSizes.highest.height - labelSizes.highest.offset; - - // Estimate the width of each grid based on the canvas width, the maximum - // label width and the number of tick intervals - maxWidth = Math.min(me.maxWidth, me.chart.width - maxLabelWidth); - tickWidth = options.offset ? me.maxWidth / numTicks : maxWidth / (numTicks - 1); - - // Allow 3 pixels x2 padding either side for label readability - if (maxLabelWidth + 6 > tickWidth) { - tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1)); - maxHeight = me.maxHeight - getTickMarkLength(options.gridLines) - - tickOpts.padding - getScaleLabelHeight(options.scaleLabel); - maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight); - labelRotation = helpers$1.toDegrees(Math.min( - Math.asin(Math.min((labelSizes.highest.height + 6) / tickWidth, 1)), - Math.asin(Math.min(maxHeight / maxLabelDiagonal, 1)) - Math.asin(maxLabelHeight / maxLabelDiagonal) - )); - labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation)); - } - - me.labelRotation = labelRotation; - }, - afterCalculateTickRotation: function() { - helpers$1.callback(this.options.afterCalculateTickRotation, [this]); - }, - - // - - beforeFit: function() { - helpers$1.callback(this.options.beforeFit, [this]); - }, - fit: function() { - var me = this; - // Reset - var minSize = me.minSize = { - width: 0, - height: 0 - }; - - var chart = me.chart; - var opts = me.options; - var tickOpts = opts.ticks; - var scaleLabelOpts = opts.scaleLabel; - var gridLineOpts = opts.gridLines; - var display = me._isVisible(); - var isBottom = opts.position === 'bottom'; - var isHorizontal = me.isHorizontal(); - - // Width - if (isHorizontal) { - minSize.width = me.maxWidth; - } else if (display) { - minSize.width = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); - } - - // height - if (!isHorizontal) { - minSize.height = me.maxHeight; // fill all the height - } else if (display) { - minSize.height = getTickMarkLength(gridLineOpts) + getScaleLabelHeight(scaleLabelOpts); - } - - // Don't bother fitting the ticks if we are not showing the labels - if (tickOpts.display && display) { - var tickFonts = parseTickFontOptions(tickOpts); - var labelSizes = me._getLabelSizes(); - var firstLabelSize = labelSizes.first; - var lastLabelSize = labelSizes.last; - var widestLabelSize = labelSizes.widest; - var highestLabelSize = labelSizes.highest; - var lineSpace = tickFonts.minor.lineHeight * 0.4; - var tickPadding = tickOpts.padding; - - if (isHorizontal) { - // A horizontal axis is more constrained by the height. - var isRotated = me.labelRotation !== 0; - var angleRadians = helpers$1.toRadians(me.labelRotation); - var cosRotation = Math.cos(angleRadians); - var sinRotation = Math.sin(angleRadians); - - var labelHeight = sinRotation * widestLabelSize.width - + cosRotation * (highestLabelSize.height - (isRotated ? highestLabelSize.offset : 0)) - + (isRotated ? 0 : lineSpace); // padding - - minSize.height = Math.min(me.maxHeight, minSize.height + labelHeight + tickPadding); - - var offsetLeft = me.getPixelForTick(0) - me.left; - var offsetRight = me.right - me.getPixelForTick(me.getTicks().length - 1); - var paddingLeft, paddingRight; - - // Ensure that our ticks are always inside the canvas. When rotated, ticks are right aligned - // which means that the right padding is dominated by the font height - if (isRotated) { - paddingLeft = isBottom ? - cosRotation * firstLabelSize.width + sinRotation * firstLabelSize.offset : - sinRotation * (firstLabelSize.height - firstLabelSize.offset); - paddingRight = isBottom ? - sinRotation * (lastLabelSize.height - lastLabelSize.offset) : - cosRotation * lastLabelSize.width + sinRotation * lastLabelSize.offset; - } else { - paddingLeft = firstLabelSize.width / 2; - paddingRight = lastLabelSize.width / 2; - } - - // Adjust padding taking into account changes in offsets - // and add 3 px to move away from canvas edges - me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3; - me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3; - } else { - // A vertical axis is more constrained by the width. Labels are the - // dominant factor here, so get that length first and account for padding - var labelWidth = tickOpts.mirror ? 0 : - // use lineSpace for consistency with horizontal axis - // tickPadding is not implemented for horizontal - widestLabelSize.width + tickPadding + lineSpace; - - minSize.width = Math.min(me.maxWidth, minSize.width + labelWidth); - - me.paddingTop = firstLabelSize.height / 2; - me.paddingBottom = lastLabelSize.height / 2; - } - } - - me.handleMargins(); - - if (isHorizontal) { - me.width = me._length = chart.width - me.margins.left - me.margins.right; - me.height = minSize.height; - } else { - me.width = minSize.width; - me.height = me._length = chart.height - me.margins.top - me.margins.bottom; - } - }, - - /** - * Handle margins and padding interactions - * @private - */ - handleMargins: function() { - var me = this; - if (me.margins) { - me.margins.left = Math.max(me.paddingLeft, me.margins.left); - me.margins.top = Math.max(me.paddingTop, me.margins.top); - me.margins.right = Math.max(me.paddingRight, me.margins.right); - me.margins.bottom = Math.max(me.paddingBottom, me.margins.bottom); - } - }, - - afterFit: function() { - helpers$1.callback(this.options.afterFit, [this]); - }, - - // Shared Methods - isHorizontal: function() { - var pos = this.options.position; - return pos === 'top' || pos === 'bottom'; - }, - isFullWidth: function() { - return this.options.fullWidth; - }, - - // Get the correct value. NaN bad inputs, If the value type is object get the x or y based on whether we are horizontal or not - getRightValue: function(rawValue) { - // Null and undefined values first - if (isNullOrUndef(rawValue)) { - return NaN; - } - // isNaN(object) returns true, so make sure NaN is checking for a number; Discard Infinite values - if ((typeof rawValue === 'number' || rawValue instanceof Number) && !isFinite(rawValue)) { - return NaN; - } - - // If it is in fact an object, dive in one more level - if (rawValue) { - if (this.isHorizontal()) { - if (rawValue.x !== undefined) { - return this.getRightValue(rawValue.x); - } - } else if (rawValue.y !== undefined) { - return this.getRightValue(rawValue.y); - } - } - - // Value is good, return it - return rawValue; - }, - - _convertTicksToLabels: function(ticks) { - var me = this; - var labels, i, ilen; - - me.ticks = ticks.map(function(tick) { - return tick.value; - }); - - me.beforeTickToLabelConversion(); - - // New implementations should return the formatted tick labels but for BACKWARD - // COMPAT, we still support no return (`this.ticks` internally changed by calling - // this method and supposed to contain only string values). - labels = me.convertTicksToLabels(ticks) || me.ticks; - - me.afterTickToLabelConversion(); - - // BACKWARD COMPAT: synchronize `_ticks` with labels (so potentially `this.ticks`) - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - ticks[i].label = labels[i]; - } - - return labels; - }, - - /** - * @private - */ - _getLabelSizes: function() { - var me = this; - var labelSizes = me._labelSizes; - - if (!labelSizes) { - me._labelSizes = labelSizes = computeLabelSizes(me.ctx, parseTickFontOptions(me.options.ticks), me.getTicks(), me.longestTextCache); - me.longestLabelWidth = labelSizes.widest.width; - } - - return labelSizes; - }, - - /** - * @private - */ - _parseValue: function(value) { - var start, end, min, max; - - if (isArray(value)) { - start = +this.getRightValue(value[0]); - end = +this.getRightValue(value[1]); - min = Math.min(start, end); - max = Math.max(start, end); - } else { - value = +this.getRightValue(value); - start = undefined; - end = value; - min = value; - max = value; - } - - return { - min: min, - max: max, - start: start, - end: end - }; - }, - - /** - * @private - */ - _getScaleLabel: function(rawValue) { - var v = this._parseValue(rawValue); - if (v.start !== undefined) { - return '[' + v.start + ', ' + v.end + ']'; - } - - return +this.getRightValue(rawValue); - }, - - /** - * Used to get the value to display in the tooltip for the data at the given index - * @param index - * @param datasetIndex - */ - getLabelForIndex: helpers$1.noop, - - /** - * Returns the location of the given data point. Value can either be an index or a numerical value - * The coordinate (0, 0) is at the upper-left corner of the canvas - * @param value - * @param index - * @param datasetIndex - */ - getPixelForValue: helpers$1.noop, - - /** - * Used to get the data value from a given pixel. This is the inverse of getPixelForValue - * The coordinate (0, 0) is at the upper-left corner of the canvas - * @param pixel - */ - getValueForPixel: helpers$1.noop, - - /** - * Returns the location of the tick at the given index - * The coordinate (0, 0) is at the upper-left corner of the canvas - */ - getPixelForTick: function(index) { - var me = this; - var offset = me.options.offset; - var numTicks = me._ticks.length; - var tickWidth = 1 / Math.max(numTicks - (offset ? 0 : 1), 1); - - return index < 0 || index > numTicks - 1 - ? null - : me.getPixelForDecimal(index * tickWidth + (offset ? tickWidth / 2 : 0)); - }, - - /** - * Utility for getting the pixel location of a percentage of scale - * The coordinate (0, 0) is at the upper-left corner of the canvas - */ - getPixelForDecimal: function(decimal) { - var me = this; - - if (me._reversePixels) { - decimal = 1 - decimal; - } - - return me._startPixel + decimal * me._length; - }, - - getDecimalForPixel: function(pixel) { - var decimal = (pixel - this._startPixel) / this._length; - return this._reversePixels ? 1 - decimal : decimal; - }, - - /** - * Returns the pixel for the minimum chart value - * The coordinate (0, 0) is at the upper-left corner of the canvas - */ - getBasePixel: function() { - return this.getPixelForValue(this.getBaseValue()); - }, - - getBaseValue: function() { - var me = this; - var min = me.min; - var max = me.max; - - return me.beginAtZero ? 0 : - min < 0 && max < 0 ? max : - min > 0 && max > 0 ? min : - 0; - }, - - /** - * Returns a subset of ticks to be plotted to avoid overlapping labels. - * @private - */ - _autoSkip: function(ticks) { - var me = this; - var tickOpts = me.options.ticks; - var axisLength = me._length; - var ticksLimit = tickOpts.maxTicksLimit || axisLength / me._tickSize() + 1; - var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : []; - var numMajorIndices = majorIndices.length; - var first = majorIndices[0]; - var last = majorIndices[numMajorIndices - 1]; - var i, ilen, spacing, avgMajorSpacing; - - // If there are too many major ticks to display them all - if (numMajorIndices > ticksLimit) { - skipMajors(ticks, majorIndices, numMajorIndices / ticksLimit); - return nonSkipped(ticks); - } - - spacing = calculateSpacing(majorIndices, ticks, axisLength, ticksLimit); - - if (numMajorIndices > 0) { - for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) { - skip(ticks, spacing, majorIndices[i], majorIndices[i + 1]); - } - avgMajorSpacing = numMajorIndices > 1 ? (last - first) / (numMajorIndices - 1) : null; - skip(ticks, spacing, helpers$1.isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first); - skip(ticks, spacing, last, helpers$1.isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing); - return nonSkipped(ticks); - } - skip(ticks, spacing); - return nonSkipped(ticks); - }, - - /** - * @private - */ - _tickSize: function() { - var me = this; - var optionTicks = me.options.ticks; - - // Calculate space needed by label in axis direction. - var rot = helpers$1.toRadians(me.labelRotation); - var cos = Math.abs(Math.cos(rot)); - var sin = Math.abs(Math.sin(rot)); - - var labelSizes = me._getLabelSizes(); - var padding = optionTicks.autoSkipPadding || 0; - var w = labelSizes ? labelSizes.widest.width + padding : 0; - var h = labelSizes ? labelSizes.highest.height + padding : 0; - - // Calculate space needed for 1 tick in axis direction. - return me.isHorizontal() - ? h * cos > w * sin ? w / cos : h / sin - : h * sin < w * cos ? h / cos : w / sin; - }, - - /** - * @private - */ - _isVisible: function() { - var me = this; - var chart = me.chart; - var display = me.options.display; - var i, ilen, meta; - - if (display !== 'auto') { - return !!display; - } - - // When 'auto', the scale is visible if at least one associated dataset is visible. - for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) { - if (chart.isDatasetVisible(i)) { - meta = chart.getDatasetMeta(i); - if (meta.xAxisID === me.id || meta.yAxisID === me.id) { - return true; - } - } - } - - return false; - }, - - /** - * @private - */ - _computeGridLineItems: function(chartArea) { - var me = this; - var chart = me.chart; - var options = me.options; - var gridLines = options.gridLines; - var position = options.position; - var offsetGridLines = gridLines.offsetGridLines; - var isHorizontal = me.isHorizontal(); - var ticks = me._ticksToDraw; - var ticksLength = ticks.length + (offsetGridLines ? 1 : 0); - - var tl = getTickMarkLength(gridLines); - var items = []; - var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; - var axisHalfWidth = axisWidth / 2; - var alignPixel = helpers$1._alignPixel; - var alignBorderValue = function(pixel) { - return alignPixel(chart, pixel, axisWidth); - }; - var borderValue, i, tick, lineValue, alignedLineValue; - var tx1, ty1, tx2, ty2, x1, y1, x2, y2, lineWidth, lineColor, borderDash, borderDashOffset; - - if (position === 'top') { - borderValue = alignBorderValue(me.bottom); - ty1 = me.bottom - tl; - ty2 = borderValue - axisHalfWidth; - y1 = alignBorderValue(chartArea.top) + axisHalfWidth; - y2 = chartArea.bottom; - } else if (position === 'bottom') { - borderValue = alignBorderValue(me.top); - y1 = chartArea.top; - y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth; - ty1 = borderValue + axisHalfWidth; - ty2 = me.top + tl; - } else if (position === 'left') { - borderValue = alignBorderValue(me.right); - tx1 = me.right - tl; - tx2 = borderValue - axisHalfWidth; - x1 = alignBorderValue(chartArea.left) + axisHalfWidth; - x2 = chartArea.right; - } else { - borderValue = alignBorderValue(me.left); - x1 = chartArea.left; - x2 = alignBorderValue(chartArea.right) - axisHalfWidth; - tx1 = borderValue + axisHalfWidth; - tx2 = me.left + tl; - } - - for (i = 0; i < ticksLength; ++i) { - tick = ticks[i] || {}; - - // autoskipper skipped this tick (#4635) - if (isNullOrUndef(tick.label) && i < ticks.length) { - continue; - } - - if (i === me.zeroLineIndex && options.offset === offsetGridLines) { - // Draw the first index specially - lineWidth = gridLines.zeroLineWidth; - lineColor = gridLines.zeroLineColor; - borderDash = gridLines.zeroLineBorderDash || []; - borderDashOffset = gridLines.zeroLineBorderDashOffset || 0.0; - } else { - lineWidth = valueAtIndexOrDefault(gridLines.lineWidth, i, 1); - lineColor = valueAtIndexOrDefault(gridLines.color, i, 'rgba(0,0,0,0.1)'); - borderDash = gridLines.borderDash || []; - borderDashOffset = gridLines.borderDashOffset || 0.0; - } - - lineValue = getPixelForGridLine(me, tick._index || i, offsetGridLines); - - // Skip if the pixel is out of the range - if (lineValue === undefined) { - continue; - } - - alignedLineValue = alignPixel(chart, lineValue, lineWidth); - - if (isHorizontal) { - tx1 = tx2 = x1 = x2 = alignedLineValue; - } else { - ty1 = ty2 = y1 = y2 = alignedLineValue; - } - - items.push({ - tx1: tx1, - ty1: ty1, - tx2: tx2, - ty2: ty2, - x1: x1, - y1: y1, - x2: x2, - y2: y2, - width: lineWidth, - color: lineColor, - borderDash: borderDash, - borderDashOffset: borderDashOffset, - }); - } - - items.ticksLength = ticksLength; - items.borderValue = borderValue; - - return items; - }, - - /** - * @private - */ - _computeLabelItems: function() { - var me = this; - var options = me.options; - var optionTicks = options.ticks; - var position = options.position; - var isMirrored = optionTicks.mirror; - var isHorizontal = me.isHorizontal(); - var ticks = me._ticksToDraw; - var fonts = parseTickFontOptions(optionTicks); - var tickPadding = optionTicks.padding; - var tl = getTickMarkLength(options.gridLines); - var rotation = -helpers$1.toRadians(me.labelRotation); - var items = []; - var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset; - - if (position === 'top') { - y = me.bottom - tl - tickPadding; - textAlign = !rotation ? 'center' : 'left'; - } else if (position === 'bottom') { - y = me.top + tl + tickPadding; - textAlign = !rotation ? 'center' : 'right'; - } else if (position === 'left') { - x = me.right - (isMirrored ? 0 : tl) - tickPadding; - textAlign = isMirrored ? 'left' : 'right'; - } else { - x = me.left + (isMirrored ? 0 : tl) + tickPadding; - textAlign = isMirrored ? 'right' : 'left'; - } - - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - tick = ticks[i]; - label = tick.label; - - // autoskipper skipped this tick (#4635) - if (isNullOrUndef(label)) { - continue; - } - - pixel = me.getPixelForTick(tick._index || i) + optionTicks.labelOffset; - font = tick.major ? fonts.major : fonts.minor; - lineHeight = font.lineHeight; - lineCount = isArray(label) ? label.length : 1; - - if (isHorizontal) { - x = pixel; - textOffset = position === 'top' - ? ((!rotation ? 0.5 : 1) - lineCount) * lineHeight - : (!rotation ? 0.5 : 0) * lineHeight; - } else { - y = pixel; - textOffset = (1 - lineCount) * lineHeight / 2; - } - - items.push({ - x: x, - y: y, - rotation: rotation, - label: label, - font: font, - textOffset: textOffset, - textAlign: textAlign - }); - } - - return items; - }, - - /** - * @private - */ - _drawGrid: function(chartArea) { - var me = this; - var gridLines = me.options.gridLines; - - if (!gridLines.display) { - return; - } - - var ctx = me.ctx; - var chart = me.chart; - var alignPixel = helpers$1._alignPixel; - var axisWidth = gridLines.drawBorder ? valueAtIndexOrDefault(gridLines.lineWidth, 0, 0) : 0; - var items = me._gridLineItems || (me._gridLineItems = me._computeGridLineItems(chartArea)); - var width, color, i, ilen, item; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - item = items[i]; - width = item.width; - color = item.color; - - if (width && color) { - ctx.save(); - ctx.lineWidth = width; - ctx.strokeStyle = color; - if (ctx.setLineDash) { - ctx.setLineDash(item.borderDash); - ctx.lineDashOffset = item.borderDashOffset; - } - - ctx.beginPath(); - - if (gridLines.drawTicks) { - ctx.moveTo(item.tx1, item.ty1); - ctx.lineTo(item.tx2, item.ty2); - } - - if (gridLines.drawOnChartArea) { - ctx.moveTo(item.x1, item.y1); - ctx.lineTo(item.x2, item.y2); - } - - ctx.stroke(); - ctx.restore(); - } - } - - if (axisWidth) { - // Draw the line at the edge of the axis - var firstLineWidth = axisWidth; - var lastLineWidth = valueAtIndexOrDefault(gridLines.lineWidth, items.ticksLength - 1, 1); - var borderValue = items.borderValue; - var x1, x2, y1, y2; - - if (me.isHorizontal()) { - x1 = alignPixel(chart, me.left, firstLineWidth) - firstLineWidth / 2; - x2 = alignPixel(chart, me.right, lastLineWidth) + lastLineWidth / 2; - y1 = y2 = borderValue; - } else { - y1 = alignPixel(chart, me.top, firstLineWidth) - firstLineWidth / 2; - y2 = alignPixel(chart, me.bottom, lastLineWidth) + lastLineWidth / 2; - x1 = x2 = borderValue; - } - - ctx.lineWidth = axisWidth; - ctx.strokeStyle = valueAtIndexOrDefault(gridLines.color, 0); - ctx.beginPath(); - ctx.moveTo(x1, y1); - ctx.lineTo(x2, y2); - ctx.stroke(); - } - }, - - /** - * @private - */ - _drawLabels: function() { - var me = this; - var optionTicks = me.options.ticks; - - if (!optionTicks.display) { - return; - } - - var ctx = me.ctx; - var items = me._labelItems || (me._labelItems = me._computeLabelItems()); - var i, j, ilen, jlen, item, tickFont, label, y; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - item = items[i]; - tickFont = item.font; - - // Make sure we draw text in the correct color and font - ctx.save(); - ctx.translate(item.x, item.y); - ctx.rotate(item.rotation); - ctx.font = tickFont.string; - ctx.fillStyle = tickFont.color; - ctx.textBaseline = 'middle'; - ctx.textAlign = item.textAlign; - - label = item.label; - y = item.textOffset; - if (isArray(label)) { - for (j = 0, jlen = label.length; j < jlen; ++j) { - // We just make sure the multiline element is a string here.. - ctx.fillText('' + label[j], 0, y); - y += tickFont.lineHeight; - } - } else { - ctx.fillText(label, 0, y); - } - ctx.restore(); - } - }, - - /** - * @private - */ - _drawTitle: function() { - var me = this; - var ctx = me.ctx; - var options = me.options; - var scaleLabel = options.scaleLabel; - - if (!scaleLabel.display) { - return; - } - - var scaleLabelFontColor = valueOrDefault$a(scaleLabel.fontColor, core_defaults.global.defaultFontColor); - var scaleLabelFont = helpers$1.options._parseFont(scaleLabel); - var scaleLabelPadding = helpers$1.options.toPadding(scaleLabel.padding); - var halfLineHeight = scaleLabelFont.lineHeight / 2; - var position = options.position; - var rotation = 0; - var scaleLabelX, scaleLabelY; - - if (me.isHorizontal()) { - scaleLabelX = me.left + me.width / 2; // midpoint of the width - scaleLabelY = position === 'bottom' - ? me.bottom - halfLineHeight - scaleLabelPadding.bottom - : me.top + halfLineHeight + scaleLabelPadding.top; - } else { - var isLeft = position === 'left'; - scaleLabelX = isLeft - ? me.left + halfLineHeight + scaleLabelPadding.top - : me.right - halfLineHeight - scaleLabelPadding.top; - scaleLabelY = me.top + me.height / 2; - rotation = isLeft ? -0.5 * Math.PI : 0.5 * Math.PI; - } - - ctx.save(); - ctx.translate(scaleLabelX, scaleLabelY); - ctx.rotate(rotation); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillStyle = scaleLabelFontColor; // render in correct colour - ctx.font = scaleLabelFont.string; - ctx.fillText(scaleLabel.labelString, 0, 0); - ctx.restore(); - }, - - draw: function(chartArea) { - var me = this; - - if (!me._isVisible()) { - return; - } - - me._drawGrid(chartArea); - me._drawTitle(); - me._drawLabels(); - }, - - /** - * @private - */ - _layers: function() { - var me = this; - var opts = me.options; - var tz = opts.ticks && opts.ticks.z || 0; - var gz = opts.gridLines && opts.gridLines.z || 0; - - if (!me._isVisible() || tz === gz || me.draw !== me._draw) { - // backward compatibility: draw has been overridden by custom scale - return [{ - z: tz, - draw: function() { - me.draw.apply(me, arguments); - } - }]; - } - - return [{ - z: gz, - draw: function() { - me._drawGrid.apply(me, arguments); - me._drawTitle.apply(me, arguments); - } - }, { - z: tz, - draw: function() { - me._drawLabels.apply(me, arguments); - } - }]; - }, - - /** - * @private - */ - _getMatchingVisibleMetas: function(type) { - var me = this; - var isHorizontal = me.isHorizontal(); - return me.chart._getSortedVisibleDatasetMetas() - .filter(function(meta) { - return (!type || meta.type === type) - && (isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id); - }); - } -}); - -Scale.prototype._draw = Scale.prototype.draw; - -var core_scale = Scale; - -var isNullOrUndef$1 = helpers$1.isNullOrUndef; - -var defaultConfig = { - position: 'bottom' -}; - -var scale_category = core_scale.extend({ - determineDataLimits: function() { - var me = this; - var labels = me._getLabels(); - var ticksOpts = me.options.ticks; - var min = ticksOpts.min; - var max = ticksOpts.max; - var minIndex = 0; - var maxIndex = labels.length - 1; - var findIndex; - - if (min !== undefined) { - // user specified min value - findIndex = labels.indexOf(min); - if (findIndex >= 0) { - minIndex = findIndex; - } - } - - if (max !== undefined) { - // user specified max value - findIndex = labels.indexOf(max); - if (findIndex >= 0) { - maxIndex = findIndex; - } - } - - me.minIndex = minIndex; - me.maxIndex = maxIndex; - me.min = labels[minIndex]; - me.max = labels[maxIndex]; - }, - - buildTicks: function() { - var me = this; - var labels = me._getLabels(); - var minIndex = me.minIndex; - var maxIndex = me.maxIndex; - - // If we are viewing some subset of labels, slice the original array - me.ticks = (minIndex === 0 && maxIndex === labels.length - 1) ? labels : labels.slice(minIndex, maxIndex + 1); - }, - - getLabelForIndex: function(index, datasetIndex) { - var me = this; - var chart = me.chart; - - if (chart.getDatasetMeta(datasetIndex).controller._getValueScaleId() === me.id) { - return me.getRightValue(chart.data.datasets[datasetIndex].data[index]); - } - - return me._getLabels()[index]; - }, - - _configure: function() { - var me = this; - var offset = me.options.offset; - var ticks = me.ticks; - - core_scale.prototype._configure.call(me); - - if (!me.isHorizontal()) { - // For backward compatibility, vertical category scale reverse is inverted. - me._reversePixels = !me._reversePixels; - } - - if (!ticks) { - return; - } - - me._startValue = me.minIndex - (offset ? 0.5 : 0); - me._valueRange = Math.max(ticks.length - (offset ? 0 : 1), 1); - }, - - // Used to get data value locations. Value can either be an index or a numerical value - getPixelForValue: function(value, index, datasetIndex) { - var me = this; - var valueCategory, labels, idx; - - if (!isNullOrUndef$1(index) && !isNullOrUndef$1(datasetIndex)) { - value = me.chart.data.datasets[datasetIndex].data[index]; - } - - // If value is a data object, then index is the index in the data array, - // not the index of the scale. We need to change that. - if (!isNullOrUndef$1(value)) { - valueCategory = me.isHorizontal() ? value.x : value.y; - } - if (valueCategory !== undefined || (value !== undefined && isNaN(index))) { - labels = me._getLabels(); - value = helpers$1.valueOrDefault(valueCategory, value); - idx = labels.indexOf(value); - index = idx !== -1 ? idx : index; - if (isNaN(index)) { - index = value; - } - } - return me.getPixelForDecimal((index - me._startValue) / me._valueRange); - }, - - getPixelForTick: function(index) { - var ticks = this.ticks; - return index < 0 || index > ticks.length - 1 - ? null - : this.getPixelForValue(ticks[index], index + this.minIndex); - }, - - getValueForPixel: function(pixel) { - var me = this; - var value = Math.round(me._startValue + me.getDecimalForPixel(pixel) * me._valueRange); - return Math.min(Math.max(value, 0), me.ticks.length - 1); - }, - - getBasePixel: function() { - return this.bottom; - } -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults = defaultConfig; -scale_category._defaults = _defaults; - -var noop = helpers$1.noop; -var isNullOrUndef$2 = helpers$1.isNullOrUndef; - -/** - * Generate a set of linear ticks - * @param generationOptions the options used to generate the ticks - * @param dataRange the range of the data - * @returns {number[]} array of tick values - */ -function generateTicks(generationOptions, dataRange) { - var ticks = []; - // To get a "nice" value for the tick spacing, we will use the appropriately named - // "nice number" algorithm. See https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks - // for details. - - var MIN_SPACING = 1e-14; - var stepSize = generationOptions.stepSize; - var unit = stepSize || 1; - var maxNumSpaces = generationOptions.maxTicks - 1; - var min = generationOptions.min; - var max = generationOptions.max; - var precision = generationOptions.precision; - var rmin = dataRange.min; - var rmax = dataRange.max; - var spacing = helpers$1.niceNum((rmax - rmin) / maxNumSpaces / unit) * unit; - var factor, niceMin, niceMax, numSpaces; - - // Beyond MIN_SPACING floating point numbers being to lose precision - // such that we can't do the math necessary to generate ticks - if (spacing < MIN_SPACING && isNullOrUndef$2(min) && isNullOrUndef$2(max)) { - return [rmin, rmax]; - } - - numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing); - if (numSpaces > maxNumSpaces) { - // If the calculated num of spaces exceeds maxNumSpaces, recalculate it - spacing = helpers$1.niceNum(numSpaces * spacing / maxNumSpaces / unit) * unit; - } - - if (stepSize || isNullOrUndef$2(precision)) { - // If a precision is not specified, calculate factor based on spacing - factor = Math.pow(10, helpers$1._decimalPlaces(spacing)); - } else { - // If the user specified a precision, round to that number of decimal places - factor = Math.pow(10, precision); - spacing = Math.ceil(spacing * factor) / factor; - } - - niceMin = Math.floor(rmin / spacing) * spacing; - niceMax = Math.ceil(rmax / spacing) * spacing; - - // If min, max and stepSize is set and they make an evenly spaced scale use it. - if (stepSize) { - // If very close to our whole number, use it. - if (!isNullOrUndef$2(min) && helpers$1.almostWhole(min / spacing, spacing / 1000)) { - niceMin = min; - } - if (!isNullOrUndef$2(max) && helpers$1.almostWhole(max / spacing, spacing / 1000)) { - niceMax = max; - } - } - - numSpaces = (niceMax - niceMin) / spacing; - // If very close to our rounded value, use it. - if (helpers$1.almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) { - numSpaces = Math.round(numSpaces); - } else { - numSpaces = Math.ceil(numSpaces); - } - - niceMin = Math.round(niceMin * factor) / factor; - niceMax = Math.round(niceMax * factor) / factor; - ticks.push(isNullOrUndef$2(min) ? niceMin : min); - for (var j = 1; j < numSpaces; ++j) { - ticks.push(Math.round((niceMin + j * spacing) * factor) / factor); - } - ticks.push(isNullOrUndef$2(max) ? niceMax : max); - - return ticks; -} - -var scale_linearbase = core_scale.extend({ - getRightValue: function(value) { - if (typeof value === 'string') { - return +value; - } - return core_scale.prototype.getRightValue.call(this, value); - }, - - handleTickRangeOptions: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - - // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, - // do nothing since that would make the chart weird. If the user really wants a weird chart - // axis, they can manually override it - if (tickOpts.beginAtZero) { - var minSign = helpers$1.sign(me.min); - var maxSign = helpers$1.sign(me.max); - - if (minSign < 0 && maxSign < 0) { - // move the top up to 0 - me.max = 0; - } else if (minSign > 0 && maxSign > 0) { - // move the bottom down to 0 - me.min = 0; - } - } - - var setMin = tickOpts.min !== undefined || tickOpts.suggestedMin !== undefined; - var setMax = tickOpts.max !== undefined || tickOpts.suggestedMax !== undefined; - - if (tickOpts.min !== undefined) { - me.min = tickOpts.min; - } else if (tickOpts.suggestedMin !== undefined) { - if (me.min === null) { - me.min = tickOpts.suggestedMin; - } else { - me.min = Math.min(me.min, tickOpts.suggestedMin); - } - } - - if (tickOpts.max !== undefined) { - me.max = tickOpts.max; - } else if (tickOpts.suggestedMax !== undefined) { - if (me.max === null) { - me.max = tickOpts.suggestedMax; - } else { - me.max = Math.max(me.max, tickOpts.suggestedMax); - } - } - - if (setMin !== setMax) { - // We set the min or the max but not both. - // So ensure that our range is good - // Inverted or 0 length range can happen when - // ticks.min is set, and no datasets are visible - if (me.min >= me.max) { - if (setMin) { - me.max = me.min + 1; - } else { - me.min = me.max - 1; - } - } - } - - if (me.min === me.max) { - me.max++; - - if (!tickOpts.beginAtZero) { - me.min--; - } - } - }, - - getTickLimit: function() { - var me = this; - var tickOpts = me.options.ticks; - var stepSize = tickOpts.stepSize; - var maxTicksLimit = tickOpts.maxTicksLimit; - var maxTicks; - - if (stepSize) { - maxTicks = Math.ceil(me.max / stepSize) - Math.floor(me.min / stepSize) + 1; - } else { - maxTicks = me._computeTickLimit(); - maxTicksLimit = maxTicksLimit || 11; - } - - if (maxTicksLimit) { - maxTicks = Math.min(maxTicksLimit, maxTicks); - } - - return maxTicks; - }, - - _computeTickLimit: function() { - return Number.POSITIVE_INFINITY; - }, - - handleDirectionalChanges: noop, - - buildTicks: function() { - var me = this; - var opts = me.options; - var tickOpts = opts.ticks; - - // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 40 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph. Make sure we always have at least 2 ticks - var maxTicks = me.getTickLimit(); - maxTicks = Math.max(2, maxTicks); - - var numericGeneratorOptions = { - maxTicks: maxTicks, - min: tickOpts.min, - max: tickOpts.max, - precision: tickOpts.precision, - stepSize: helpers$1.valueOrDefault(tickOpts.fixedStepSize, tickOpts.stepSize) - }; - var ticks = me.ticks = generateTicks(numericGeneratorOptions, me); - - me.handleDirectionalChanges(); - - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers$1.max(ticks); - me.min = helpers$1.min(ticks); - - if (tickOpts.reverse) { - ticks.reverse(); - - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - }, - - convertTicksToLabels: function() { - var me = this; - me.ticksAsNumbers = me.ticks.slice(); - me.zeroLineIndex = me.ticks.indexOf(0); - - core_scale.prototype.convertTicksToLabels.call(me); - }, - - _configure: function() { - var me = this; - var ticks = me.getTicks(); - var start = me.min; - var end = me.max; - var offset; - - core_scale.prototype._configure.call(me); - - if (me.options.offset && ticks.length) { - offset = (end - start) / Math.max(ticks.length - 1, 1) / 2; - start -= offset; - end += offset; - } - me._startValue = start; - me._endValue = end; - me._valueRange = end - start; - } -}); - -var defaultConfig$1 = { - position: 'left', - ticks: { - callback: core_ticks.formatters.linear - } -}; - -var DEFAULT_MIN = 0; -var DEFAULT_MAX = 1; - -function getOrCreateStack(stacks, stacked, meta) { - var key = [ - meta.type, - // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined - stacked === undefined && meta.stack === undefined ? meta.index : '', - meta.stack - ].join('.'); - - if (stacks[key] === undefined) { - stacks[key] = { - pos: [], - neg: [] - }; - } - - return stacks[key]; -} - -function stackData(scale, stacks, meta, data) { - var opts = scale.options; - var stacked = opts.stacked; - var stack = getOrCreateStack(stacks, stacked, meta); - var pos = stack.pos; - var neg = stack.neg; - var ilen = data.length; - var i, value; - - for (i = 0; i < ilen; ++i) { - value = scale._parseValue(data[i]); - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { - continue; - } - - pos[i] = pos[i] || 0; - neg[i] = neg[i] || 0; - - if (opts.relativePoints) { - pos[i] = 100; - } else if (value.min < 0 || value.max < 0) { - neg[i] += value.min; - } else { - pos[i] += value.max; - } - } -} - -function updateMinMax(scale, meta, data) { - var ilen = data.length; - var i, value; - - for (i = 0; i < ilen; ++i) { - value = scale._parseValue(data[i]); - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden) { - continue; - } - - scale.min = Math.min(scale.min, value.min); - scale.max = Math.max(scale.max, value.max); - } -} - -var scale_linear = scale_linearbase.extend({ - determineDataLimits: function() { - var me = this; - var opts = me.options; - var chart = me.chart; - var datasets = chart.data.datasets; - var metasets = me._getMatchingVisibleMetas(); - var hasStacks = opts.stacked; - var stacks = {}; - var ilen = metasets.length; - var i, meta, data, values; - - me.min = Number.POSITIVE_INFINITY; - me.max = Number.NEGATIVE_INFINITY; - - if (hasStacks === undefined) { - for (i = 0; !hasStacks && i < ilen; ++i) { - meta = metasets[i]; - hasStacks = meta.stack !== undefined; - } - } - - for (i = 0; i < ilen; ++i) { - meta = metasets[i]; - data = datasets[meta.index].data; - if (hasStacks) { - stackData(me, stacks, meta, data); - } else { - updateMinMax(me, meta, data); - } - } - - helpers$1.each(stacks, function(stackValues) { - values = stackValues.pos.concat(stackValues.neg); - me.min = Math.min(me.min, helpers$1.min(values)); - me.max = Math.max(me.max, helpers$1.max(values)); - }); - - me.min = helpers$1.isFinite(me.min) && !isNaN(me.min) ? me.min : DEFAULT_MIN; - me.max = helpers$1.isFinite(me.max) && !isNaN(me.max) ? me.max : DEFAULT_MAX; - - // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - me.handleTickRangeOptions(); - }, - - // Returns the maximum number of ticks based on the scale dimension - _computeTickLimit: function() { - var me = this; - var tickFont; - - if (me.isHorizontal()) { - return Math.ceil(me.width / 40); - } - tickFont = helpers$1.options._parseFont(me.options.ticks); - return Math.ceil(me.height / tickFont.lineHeight); - }, - - // Called after the ticks are built. We need - handleDirectionalChanges: function() { - if (!this.isHorizontal()) { - // We are in a vertical orientation. The top value is the highest. So reverse the array - this.ticks.reverse(); - } - }, - - getLabelForIndex: function(index, datasetIndex) { - return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); - }, - - // Utils - getPixelForValue: function(value) { - var me = this; - return me.getPixelForDecimal((+me.getRightValue(value) - me._startValue) / me._valueRange); - }, - - getValueForPixel: function(pixel) { - return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange; - }, - - getPixelForTick: function(index) { - var ticks = this.ticksAsNumbers; - if (index < 0 || index > ticks.length - 1) { - return null; - } - return this.getPixelForValue(ticks[index]); - } -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults$1 = defaultConfig$1; -scale_linear._defaults = _defaults$1; - -var valueOrDefault$b = helpers$1.valueOrDefault; -var log10 = helpers$1.math.log10; - -/** - * Generate a set of logarithmic ticks - * @param generationOptions the options used to generate the ticks - * @param dataRange the range of the data - * @returns {number[]} array of tick values - */ -function generateTicks$1(generationOptions, dataRange) { - var ticks = []; - - var tickVal = valueOrDefault$b(generationOptions.min, Math.pow(10, Math.floor(log10(dataRange.min)))); - - var endExp = Math.floor(log10(dataRange.max)); - var endSignificand = Math.ceil(dataRange.max / Math.pow(10, endExp)); - var exp, significand; - - if (tickVal === 0) { - exp = Math.floor(log10(dataRange.minNotZero)); - significand = Math.floor(dataRange.minNotZero / Math.pow(10, exp)); - - ticks.push(tickVal); - tickVal = significand * Math.pow(10, exp); - } else { - exp = Math.floor(log10(tickVal)); - significand = Math.floor(tickVal / Math.pow(10, exp)); - } - var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1; - - do { - ticks.push(tickVal); - - ++significand; - if (significand === 10) { - significand = 1; - ++exp; - precision = exp >= 0 ? 1 : precision; - } - - tickVal = Math.round(significand * Math.pow(10, exp) * precision) / precision; - } while (exp < endExp || (exp === endExp && significand < endSignificand)); - - var lastTick = valueOrDefault$b(generationOptions.max, tickVal); - ticks.push(lastTick); - - return ticks; -} - -var defaultConfig$2 = { - position: 'left', - - // label settings - ticks: { - callback: core_ticks.formatters.logarithmic - } -}; - -// TODO(v3): change this to positiveOrDefault -function nonNegativeOrDefault(value, defaultValue) { - return helpers$1.isFinite(value) && value >= 0 ? value : defaultValue; -} - -var scale_logarithmic = core_scale.extend({ - determineDataLimits: function() { - var me = this; - var opts = me.options; - var chart = me.chart; - var datasets = chart.data.datasets; - var isHorizontal = me.isHorizontal(); - function IDMatches(meta) { - return isHorizontal ? meta.xAxisID === me.id : meta.yAxisID === me.id; - } - var datasetIndex, meta, value, data, i, ilen; - - // Calculate Range - me.min = Number.POSITIVE_INFINITY; - me.max = Number.NEGATIVE_INFINITY; - me.minNotZero = Number.POSITIVE_INFINITY; - - var hasStacks = opts.stacked; - if (hasStacks === undefined) { - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta) && - meta.stack !== undefined) { - hasStacks = true; - break; - } - } - } - - if (opts.stacked || hasStacks) { - var valuesPerStack = {}; - - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - meta = chart.getDatasetMeta(datasetIndex); - var key = [ - meta.type, - // we have a separate stack for stack=undefined datasets when the opts.stacked is undefined - ((opts.stacked === undefined && meta.stack === undefined) ? datasetIndex : ''), - meta.stack - ].join('.'); - - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - if (valuesPerStack[key] === undefined) { - valuesPerStack[key] = []; - } - - data = datasets[datasetIndex].data; - for (i = 0, ilen = data.length; i < ilen; i++) { - var values = valuesPerStack[key]; - value = me._parseValue(data[i]); - // invalid, hidden and negative values are ignored - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { - continue; - } - values[i] = values[i] || 0; - values[i] += value.max; - } - } - } - - helpers$1.each(valuesPerStack, function(valuesForType) { - if (valuesForType.length > 0) { - var minVal = helpers$1.min(valuesForType); - var maxVal = helpers$1.max(valuesForType); - me.min = Math.min(me.min, minVal); - me.max = Math.max(me.max, maxVal); - } - }); - - } else { - for (datasetIndex = 0; datasetIndex < datasets.length; datasetIndex++) { - meta = chart.getDatasetMeta(datasetIndex); - if (chart.isDatasetVisible(datasetIndex) && IDMatches(meta)) { - data = datasets[datasetIndex].data; - for (i = 0, ilen = data.length; i < ilen; i++) { - value = me._parseValue(data[i]); - // invalid, hidden and negative values are ignored - if (isNaN(value.min) || isNaN(value.max) || meta.data[i].hidden || value.min < 0 || value.max < 0) { - continue; - } - - me.min = Math.min(value.min, me.min); - me.max = Math.max(value.max, me.max); - - if (value.min !== 0) { - me.minNotZero = Math.min(value.min, me.minNotZero); - } - } - } - } - } - - me.min = helpers$1.isFinite(me.min) ? me.min : null; - me.max = helpers$1.isFinite(me.max) ? me.max : null; - me.minNotZero = helpers$1.isFinite(me.minNotZero) ? me.minNotZero : null; - - // Common base implementation to handle ticks.min, ticks.max - this.handleTickRangeOptions(); - }, - - handleTickRangeOptions: function() { - var me = this; - var tickOpts = me.options.ticks; - var DEFAULT_MIN = 1; - var DEFAULT_MAX = 10; - - me.min = nonNegativeOrDefault(tickOpts.min, me.min); - me.max = nonNegativeOrDefault(tickOpts.max, me.max); - - if (me.min === me.max) { - if (me.min !== 0 && me.min !== null) { - me.min = Math.pow(10, Math.floor(log10(me.min)) - 1); - me.max = Math.pow(10, Math.floor(log10(me.max)) + 1); - } else { - me.min = DEFAULT_MIN; - me.max = DEFAULT_MAX; - } - } - if (me.min === null) { - me.min = Math.pow(10, Math.floor(log10(me.max)) - 1); - } - if (me.max === null) { - me.max = me.min !== 0 - ? Math.pow(10, Math.floor(log10(me.min)) + 1) - : DEFAULT_MAX; - } - if (me.minNotZero === null) { - if (me.min > 0) { - me.minNotZero = me.min; - } else if (me.max < 1) { - me.minNotZero = Math.pow(10, Math.floor(log10(me.max))); - } else { - me.minNotZero = DEFAULT_MIN; - } - } - }, - - buildTicks: function() { - var me = this; - var tickOpts = me.options.ticks; - var reverse = !me.isHorizontal(); - - var generationOptions = { - min: nonNegativeOrDefault(tickOpts.min), - max: nonNegativeOrDefault(tickOpts.max) - }; - var ticks = me.ticks = generateTicks$1(generationOptions, me); - - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - me.max = helpers$1.max(ticks); - me.min = helpers$1.min(ticks); - - if (tickOpts.reverse) { - reverse = !reverse; - me.start = me.max; - me.end = me.min; - } else { - me.start = me.min; - me.end = me.max; - } - if (reverse) { - ticks.reverse(); - } - }, - - convertTicksToLabels: function() { - this.tickValues = this.ticks.slice(); - - core_scale.prototype.convertTicksToLabels.call(this); - }, - - // Get the correct tooltip label - getLabelForIndex: function(index, datasetIndex) { - return this._getScaleLabel(this.chart.data.datasets[datasetIndex].data[index]); - }, - - getPixelForTick: function(index) { - var ticks = this.tickValues; - if (index < 0 || index > ticks.length - 1) { - return null; - } - return this.getPixelForValue(ticks[index]); - }, - - /** - * Returns the value of the first tick. - * @param {number} value - The minimum not zero value. - * @return {number} The first tick value. - * @private - */ - _getFirstTickValue: function(value) { - var exp = Math.floor(log10(value)); - var significand = Math.floor(value / Math.pow(10, exp)); - - return significand * Math.pow(10, exp); - }, - - _configure: function() { - var me = this; - var start = me.min; - var offset = 0; - - core_scale.prototype._configure.call(me); - - if (start === 0) { - start = me._getFirstTickValue(me.minNotZero); - offset = valueOrDefault$b(me.options.ticks.fontSize, core_defaults.global.defaultFontSize) / me._length; - } - - me._startValue = log10(start); - me._valueOffset = offset; - me._valueRange = (log10(me.max) - log10(start)) / (1 - offset); - }, - - getPixelForValue: function(value) { - var me = this; - var decimal = 0; - - value = +me.getRightValue(value); - - if (value > me.min && value > 0) { - decimal = (log10(value) - me._startValue) / me._valueRange + me._valueOffset; - } - return me.getPixelForDecimal(decimal); - }, - - getValueForPixel: function(pixel) { - var me = this; - var decimal = me.getDecimalForPixel(pixel); - return decimal === 0 && me.min === 0 - ? 0 - : Math.pow(10, me._startValue + (decimal - me._valueOffset) * me._valueRange); - } -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults$2 = defaultConfig$2; -scale_logarithmic._defaults = _defaults$2; - -var valueOrDefault$c = helpers$1.valueOrDefault; -var valueAtIndexOrDefault$1 = helpers$1.valueAtIndexOrDefault; -var resolve$4 = helpers$1.options.resolve; - -var defaultConfig$3 = { - display: true, - - // Boolean - Whether to animate scaling the chart from the centre - animate: true, - position: 'chartArea', - - angleLines: { - display: true, - color: 'rgba(0,0,0,0.1)', - lineWidth: 1, - borderDash: [], - borderDashOffset: 0.0 - }, - - gridLines: { - circular: false - }, - - // label settings - ticks: { - // Boolean - Show a backdrop to the scale label - showLabelBackdrop: true, - - // String - The colour of the label backdrop - backdropColor: 'rgba(255,255,255,0.75)', - - // Number - The backdrop padding above & below the label in pixels - backdropPaddingY: 2, - - // Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2, - - callback: core_ticks.formatters.linear - }, - - pointLabels: { - // Boolean - if true, show point labels - display: true, - - // Number - Point label font size in pixels - fontSize: 10, - - // Function - Used to convert point labels - callback: function(label) { - return label; - } - } -}; - -function getTickBackdropHeight(opts) { - var tickOpts = opts.ticks; - - if (tickOpts.display && opts.display) { - return valueOrDefault$c(tickOpts.fontSize, core_defaults.global.defaultFontSize) + tickOpts.backdropPaddingY * 2; - } - return 0; -} - -function measureLabelSize(ctx, lineHeight, label) { - if (helpers$1.isArray(label)) { - return { - w: helpers$1.longestText(ctx, ctx.font, label), - h: label.length * lineHeight - }; - } - - return { - w: ctx.measureText(label).width, - h: lineHeight - }; -} - -function determineLimits(angle, pos, size, min, max) { - if (angle === min || angle === max) { - return { - start: pos - (size / 2), - end: pos + (size / 2) - }; - } else if (angle < min || angle > max) { - return { - start: pos - size, - end: pos - }; - } - - return { - start: pos, - end: pos + size - }; -} - -/** - * Helper function to fit a radial linear scale with point labels - */ -function fitWithPointLabels(scale) { - - // Right, this is really confusing and there is a lot of maths going on here - // The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 - // - // Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif - // - // Solution: - // - // We assume the radius of the polygon is half the size of the canvas at first - // at each index we check if the text overlaps. - // - // Where it does, we store that angle and that index. - // - // After finding the largest index and angle we calculate how much we need to remove - // from the shape radius to move the point inwards by that x. - // - // We average the left and right distances to get the maximum shape radius that can fit in the box - // along with labels. - // - // Once we have that, we can find the centre point for the chart, by taking the x text protrusion - // on each side, removing that from the size, halving it and adding the left x protrusion width. - // - // This will mean we have a shape fitted to the canvas, as large as it can be with the labels - // and position it in the most space efficient manner - // - // https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif - - var plFont = helpers$1.options._parseFont(scale.options.pointLabels); - - // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. - // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points - var furthestLimits = { - l: 0, - r: scale.width, - t: 0, - b: scale.height - scale.paddingTop - }; - var furthestAngles = {}; - var i, textSize, pointPosition; - - scale.ctx.font = plFont.string; - scale._pointLabelSizes = []; - - var valueCount = scale.chart.data.labels.length; - for (i = 0; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, scale.drawingArea + 5); - textSize = measureLabelSize(scale.ctx, plFont.lineHeight, scale.pointLabels[i]); - scale._pointLabelSizes[i] = textSize; - - // Add quarter circle to make degree 0 mean top of circle - var angleRadians = scale.getIndexAngle(i); - var angle = helpers$1.toDegrees(angleRadians) % 360; - var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180); - var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270); - - if (hLimits.start < furthestLimits.l) { - furthestLimits.l = hLimits.start; - furthestAngles.l = angleRadians; - } - - if (hLimits.end > furthestLimits.r) { - furthestLimits.r = hLimits.end; - furthestAngles.r = angleRadians; - } - - if (vLimits.start < furthestLimits.t) { - furthestLimits.t = vLimits.start; - furthestAngles.t = angleRadians; - } - - if (vLimits.end > furthestLimits.b) { - furthestLimits.b = vLimits.end; - furthestAngles.b = angleRadians; - } - } - - scale.setReductions(scale.drawingArea, furthestLimits, furthestAngles); -} - -function getTextAlignForAngle(angle) { - if (angle === 0 || angle === 180) { - return 'center'; - } else if (angle < 180) { - return 'left'; - } - - return 'right'; -} - -function fillText(ctx, text, position, lineHeight) { - var y = position.y + lineHeight / 2; - var i, ilen; - - if (helpers$1.isArray(text)) { - for (i = 0, ilen = text.length; i < ilen; ++i) { - ctx.fillText(text[i], position.x, y); - y += lineHeight; - } - } else { - ctx.fillText(text, position.x, y); - } -} - -function adjustPointPositionForLabelHeight(angle, textSize, position) { - if (angle === 90 || angle === 270) { - position.y -= (textSize.h / 2); - } else if (angle > 270 || angle < 90) { - position.y -= textSize.h; - } -} - -function drawPointLabels(scale) { - var ctx = scale.ctx; - var opts = scale.options; - var pointLabelOpts = opts.pointLabels; - var tickBackdropHeight = getTickBackdropHeight(opts); - var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max); - var plFont = helpers$1.options._parseFont(pointLabelOpts); - - ctx.save(); - - ctx.font = plFont.string; - ctx.textBaseline = 'middle'; - - for (var i = scale.chart.data.labels.length - 1; i >= 0; i--) { - // Extra pixels out for some label spacing - var extra = (i === 0 ? tickBackdropHeight / 2 : 0); - var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5); - - // Keep this in loop since we may support array properties here - var pointLabelFontColor = valueAtIndexOrDefault$1(pointLabelOpts.fontColor, i, core_defaults.global.defaultFontColor); - ctx.fillStyle = pointLabelFontColor; - - var angleRadians = scale.getIndexAngle(i); - var angle = helpers$1.toDegrees(angleRadians); - ctx.textAlign = getTextAlignForAngle(angle); - adjustPointPositionForLabelHeight(angle, scale._pointLabelSizes[i], pointLabelPosition); - fillText(ctx, scale.pointLabels[i], pointLabelPosition, plFont.lineHeight); - } - ctx.restore(); -} - -function drawRadiusLine(scale, gridLineOpts, radius, index) { - var ctx = scale.ctx; - var circular = gridLineOpts.circular; - var valueCount = scale.chart.data.labels.length; - var lineColor = valueAtIndexOrDefault$1(gridLineOpts.color, index - 1); - var lineWidth = valueAtIndexOrDefault$1(gridLineOpts.lineWidth, index - 1); - var pointPosition; - - if ((!circular && !valueCount) || !lineColor || !lineWidth) { - return; - } - - ctx.save(); - ctx.strokeStyle = lineColor; - ctx.lineWidth = lineWidth; - if (ctx.setLineDash) { - ctx.setLineDash(gridLineOpts.borderDash || []); - ctx.lineDashOffset = gridLineOpts.borderDashOffset || 0.0; - } - - ctx.beginPath(); - if (circular) { - // Draw circular arcs between the points - ctx.arc(scale.xCenter, scale.yCenter, radius, 0, Math.PI * 2); - } else { - // Draw straight lines connecting each index - pointPosition = scale.getPointPosition(0, radius); - ctx.moveTo(pointPosition.x, pointPosition.y); - - for (var i = 1; i < valueCount; i++) { - pointPosition = scale.getPointPosition(i, radius); - ctx.lineTo(pointPosition.x, pointPosition.y); - } - } - ctx.closePath(); - ctx.stroke(); - ctx.restore(); -} - -function numberOrZero(param) { - return helpers$1.isNumber(param) ? param : 0; -} - -var scale_radialLinear = scale_linearbase.extend({ - setDimensions: function() { - var me = this; - - // Set the unconstrained dimension before label rotation - me.width = me.maxWidth; - me.height = me.maxHeight; - me.paddingTop = getTickBackdropHeight(me.options) / 2; - me.xCenter = Math.floor(me.width / 2); - me.yCenter = Math.floor((me.height - me.paddingTop) / 2); - me.drawingArea = Math.min(me.height - me.paddingTop, me.width) / 2; - }, - - determineDataLimits: function() { - var me = this; - var chart = me.chart; - var min = Number.POSITIVE_INFINITY; - var max = Number.NEGATIVE_INFINITY; - - helpers$1.each(chart.data.datasets, function(dataset, datasetIndex) { - if (chart.isDatasetVisible(datasetIndex)) { - var meta = chart.getDatasetMeta(datasetIndex); - - helpers$1.each(dataset.data, function(rawValue, index) { - var value = +me.getRightValue(rawValue); - if (isNaN(value) || meta.data[index].hidden) { - return; - } - - min = Math.min(value, min); - max = Math.max(value, max); - }); - } - }); - - me.min = (min === Number.POSITIVE_INFINITY ? 0 : min); - me.max = (max === Number.NEGATIVE_INFINITY ? 0 : max); - - // Common base implementation to handle ticks.min, ticks.max, ticks.beginAtZero - me.handleTickRangeOptions(); - }, - - // Returns the maximum number of ticks based on the scale dimension - _computeTickLimit: function() { - return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options)); - }, - - convertTicksToLabels: function() { - var me = this; - - scale_linearbase.prototype.convertTicksToLabels.call(me); - - // Point labels - me.pointLabels = me.chart.data.labels.map(function() { - var label = helpers$1.callback(me.options.pointLabels.callback, arguments, me); - return label || label === 0 ? label : ''; - }); - }, - - getLabelForIndex: function(index, datasetIndex) { - return +this.getRightValue(this.chart.data.datasets[datasetIndex].data[index]); - }, - - fit: function() { - var me = this; - var opts = me.options; - - if (opts.display && opts.pointLabels.display) { - fitWithPointLabels(me); - } else { - me.setCenterPoint(0, 0, 0, 0); - } - }, - - /** - * Set radius reductions and determine new radius and center point - * @private - */ - setReductions: function(largestPossibleRadius, furthestLimits, furthestAngles) { - var me = this; - var radiusReductionLeft = furthestLimits.l / Math.sin(furthestAngles.l); - var radiusReductionRight = Math.max(furthestLimits.r - me.width, 0) / Math.sin(furthestAngles.r); - var radiusReductionTop = -furthestLimits.t / Math.cos(furthestAngles.t); - var radiusReductionBottom = -Math.max(furthestLimits.b - (me.height - me.paddingTop), 0) / Math.cos(furthestAngles.b); - - radiusReductionLeft = numberOrZero(radiusReductionLeft); - radiusReductionRight = numberOrZero(radiusReductionRight); - radiusReductionTop = numberOrZero(radiusReductionTop); - radiusReductionBottom = numberOrZero(radiusReductionBottom); - - me.drawingArea = Math.min( - Math.floor(largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2), - Math.floor(largestPossibleRadius - (radiusReductionTop + radiusReductionBottom) / 2)); - me.setCenterPoint(radiusReductionLeft, radiusReductionRight, radiusReductionTop, radiusReductionBottom); - }, - - setCenterPoint: function(leftMovement, rightMovement, topMovement, bottomMovement) { - var me = this; - var maxRight = me.width - rightMovement - me.drawingArea; - var maxLeft = leftMovement + me.drawingArea; - var maxTop = topMovement + me.drawingArea; - var maxBottom = (me.height - me.paddingTop) - bottomMovement - me.drawingArea; - - me.xCenter = Math.floor(((maxLeft + maxRight) / 2) + me.left); - me.yCenter = Math.floor(((maxTop + maxBottom) / 2) + me.top + me.paddingTop); - }, - - getIndexAngle: function(index) { - var chart = this.chart; - var angleMultiplier = 360 / chart.data.labels.length; - var options = chart.options || {}; - var startAngle = options.startAngle || 0; - - // Start from the top instead of right, so remove a quarter of the circle - var angle = (index * angleMultiplier + startAngle) % 360; - - return (angle < 0 ? angle + 360 : angle) * Math.PI * 2 / 360; - }, - - getDistanceFromCenterForValue: function(value) { - var me = this; - - if (helpers$1.isNullOrUndef(value)) { - return NaN; - } - - // Take into account half font size + the yPadding of the top value - var scalingFactor = me.drawingArea / (me.max - me.min); - if (me.options.ticks.reverse) { - return (me.max - value) * scalingFactor; - } - return (value - me.min) * scalingFactor; - }, - - getPointPosition: function(index, distanceFromCenter) { - var me = this; - var thisAngle = me.getIndexAngle(index) - (Math.PI / 2); - return { - x: Math.cos(thisAngle) * distanceFromCenter + me.xCenter, - y: Math.sin(thisAngle) * distanceFromCenter + me.yCenter - }; - }, - - getPointPositionForValue: function(index, value) { - return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); - }, - - getBasePosition: function(index) { - var me = this; - var min = me.min; - var max = me.max; - - return me.getPointPositionForValue(index || 0, - me.beginAtZero ? 0 : - min < 0 && max < 0 ? max : - min > 0 && max > 0 ? min : - 0); - }, - - /** - * @private - */ - _drawGrid: function() { - var me = this; - var ctx = me.ctx; - var opts = me.options; - var gridLineOpts = opts.gridLines; - var angleLineOpts = opts.angleLines; - var lineWidth = valueOrDefault$c(angleLineOpts.lineWidth, gridLineOpts.lineWidth); - var lineColor = valueOrDefault$c(angleLineOpts.color, gridLineOpts.color); - var i, offset, position; - - if (opts.pointLabels.display) { - drawPointLabels(me); - } - - if (gridLineOpts.display) { - helpers$1.each(me.ticks, function(label, index) { - if (index !== 0) { - offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); - drawRadiusLine(me, gridLineOpts, offset, index); - } - }); - } - - if (angleLineOpts.display && lineWidth && lineColor) { - ctx.save(); - ctx.lineWidth = lineWidth; - ctx.strokeStyle = lineColor; - if (ctx.setLineDash) { - ctx.setLineDash(resolve$4([angleLineOpts.borderDash, gridLineOpts.borderDash, []])); - ctx.lineDashOffset = resolve$4([angleLineOpts.borderDashOffset, gridLineOpts.borderDashOffset, 0.0]); - } - - for (i = me.chart.data.labels.length - 1; i >= 0; i--) { - offset = me.getDistanceFromCenterForValue(opts.ticks.reverse ? me.min : me.max); - position = me.getPointPosition(i, offset); - ctx.beginPath(); - ctx.moveTo(me.xCenter, me.yCenter); - ctx.lineTo(position.x, position.y); - ctx.stroke(); - } - - ctx.restore(); - } - }, - - /** - * @private - */ - _drawLabels: function() { - var me = this; - var ctx = me.ctx; - var opts = me.options; - var tickOpts = opts.ticks; - - if (!tickOpts.display) { - return; - } - - var startAngle = me.getIndexAngle(0); - var tickFont = helpers$1.options._parseFont(tickOpts); - var tickFontColor = valueOrDefault$c(tickOpts.fontColor, core_defaults.global.defaultFontColor); - var offset, width; - - ctx.save(); - ctx.font = tickFont.string; - ctx.translate(me.xCenter, me.yCenter); - ctx.rotate(startAngle); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - - helpers$1.each(me.ticks, function(label, index) { - if (index === 0 && !tickOpts.reverse) { - return; - } - - offset = me.getDistanceFromCenterForValue(me.ticksAsNumbers[index]); - - if (tickOpts.showLabelBackdrop) { - width = ctx.measureText(label).width; - ctx.fillStyle = tickOpts.backdropColor; - - ctx.fillRect( - -width / 2 - tickOpts.backdropPaddingX, - -offset - tickFont.size / 2 - tickOpts.backdropPaddingY, - width + tickOpts.backdropPaddingX * 2, - tickFont.size + tickOpts.backdropPaddingY * 2 - ); - } - - ctx.fillStyle = tickFontColor; - ctx.fillText(label, 0, -offset); - }); - - ctx.restore(); - }, - - /** - * @private - */ - _drawTitle: helpers$1.noop -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults$3 = defaultConfig$3; -scale_radialLinear._defaults = _defaults$3; - -var deprecated$1 = helpers$1._deprecated; -var resolve$5 = helpers$1.options.resolve; -var valueOrDefault$d = helpers$1.valueOrDefault; - -// Integer constants are from the ES6 spec. -var MIN_INTEGER = Number.MIN_SAFE_INTEGER || -9007199254740991; -var MAX_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; - -var INTERVALS = { - millisecond: { - common: true, - size: 1, - steps: 1000 - }, - second: { - common: true, - size: 1000, - steps: 60 - }, - minute: { - common: true, - size: 60000, - steps: 60 - }, - hour: { - common: true, - size: 3600000, - steps: 24 - }, - day: { - common: true, - size: 86400000, - steps: 30 - }, - week: { - common: false, - size: 604800000, - steps: 4 - }, - month: { - common: true, - size: 2.628e9, - steps: 12 - }, - quarter: { - common: false, - size: 7.884e9, - steps: 4 - }, - year: { - common: true, - size: 3.154e10 - } -}; - -var UNITS = Object.keys(INTERVALS); - -function sorter(a, b) { - return a - b; -} - -function arrayUnique(items) { - var hash = {}; - var out = []; - var i, ilen, item; - - for (i = 0, ilen = items.length; i < ilen; ++i) { - item = items[i]; - if (!hash[item]) { - hash[item] = true; - out.push(item); - } - } - - return out; -} - -function getMin(options) { - return helpers$1.valueOrDefault(options.time.min, options.ticks.min); -} - -function getMax(options) { - return helpers$1.valueOrDefault(options.time.max, options.ticks.max); -} - -/** - * Returns an array of {time, pos} objects used to interpolate a specific `time` or position - * (`pos`) on the scale, by searching entries before and after the requested value. `pos` is - * a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other - * extremity (left + width or top + height). Note that it would be more optimized to directly - * store pre-computed pixels, but the scale dimensions are not guaranteed at the time we need - * to create the lookup table. The table ALWAYS contains at least two items: min and max. - * - * @param {number[]} timestamps - timestamps sorted from lowest to highest. - * @param {string} distribution - If 'linear', timestamps will be spread linearly along the min - * and max range, so basically, the table will contains only two items: {min, 0} and {max, 1}. - * If 'series', timestamps will be positioned at the same distance from each other. In this - * case, only timestamps that break the time linearity are registered, meaning that in the - * best case, all timestamps are linear, the table contains only min and max. - */ -function buildLookupTable(timestamps, min, max, distribution) { - if (distribution === 'linear' || !timestamps.length) { - return [ - {time: min, pos: 0}, - {time: max, pos: 1} - ]; - } - - var table = []; - var items = [min]; - var i, ilen, prev, curr, next; - - for (i = 0, ilen = timestamps.length; i < ilen; ++i) { - curr = timestamps[i]; - if (curr > min && curr < max) { - items.push(curr); - } - } - - items.push(max); - - for (i = 0, ilen = items.length; i < ilen; ++i) { - next = items[i + 1]; - prev = items[i - 1]; - curr = items[i]; - - // only add points that breaks the scale linearity - if (prev === undefined || next === undefined || Math.round((next + prev) / 2) !== curr) { - table.push({time: curr, pos: i / (ilen - 1)}); - } - } - - return table; -} - -// @see adapted from https://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ -function lookup(table, key, value) { - var lo = 0; - var hi = table.length - 1; - var mid, i0, i1; - - while (lo >= 0 && lo <= hi) { - mid = (lo + hi) >> 1; - i0 = table[mid - 1] || null; - i1 = table[mid]; - - if (!i0) { - // given value is outside table (before first item) - return {lo: null, hi: i1}; - } else if (i1[key] < value) { - lo = mid + 1; - } else if (i0[key] > value) { - hi = mid - 1; - } else { - return {lo: i0, hi: i1}; - } - } - - // given value is outside table (after last item) - return {lo: i1, hi: null}; -} - -/** - * Linearly interpolates the given source `value` using the table items `skey` values and - * returns the associated `tkey` value. For example, interpolate(table, 'time', 42, 'pos') - * returns the position for a timestamp equal to 42. If value is out of bounds, values at - * index [0, 1] or [n - 1, n] are used for the interpolation. - */ -function interpolate$1(table, skey, sval, tkey) { - var range = lookup(table, skey, sval); - - // Note: the lookup table ALWAYS contains at least 2 items (min and max) - var prev = !range.lo ? table[0] : !range.hi ? table[table.length - 2] : range.lo; - var next = !range.lo ? table[1] : !range.hi ? table[table.length - 1] : range.hi; - - var span = next[skey] - prev[skey]; - var ratio = span ? (sval - prev[skey]) / span : 0; - var offset = (next[tkey] - prev[tkey]) * ratio; - - return prev[tkey] + offset; -} - -function toTimestamp(scale, input) { - var adapter = scale._adapter; - var options = scale.options.time; - var parser = options.parser; - var format = parser || options.format; - var value = input; - - if (typeof parser === 'function') { - value = parser(value); - } - - // Only parse if its not a timestamp already - if (!helpers$1.isFinite(value)) { - value = typeof format === 'string' - ? adapter.parse(value, format) - : adapter.parse(value); - } - - if (value !== null) { - return +value; - } - - // Labels are in an incompatible format and no `parser` has been provided. - // The user might still use the deprecated `format` option for parsing. - if (!parser && typeof format === 'function') { - value = format(input); - - // `format` could return something else than a timestamp, if so, parse it - if (!helpers$1.isFinite(value)) { - value = adapter.parse(value); - } - } - - return value; -} - -function parse(scale, input) { - if (helpers$1.isNullOrUndef(input)) { - return null; - } - - var options = scale.options.time; - var value = toTimestamp(scale, scale.getRightValue(input)); - if (value === null) { - return value; - } - - if (options.round) { - value = +scale._adapter.startOf(value, options.round); - } - - return value; -} - -/** - * Figures out what unit results in an appropriate number of auto-generated ticks - */ -function determineUnitForAutoTicks(minUnit, min, max, capacity) { - var ilen = UNITS.length; - var i, interval, factor; - - for (i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) { - interval = INTERVALS[UNITS[i]]; - factor = interval.steps ? interval.steps : MAX_INTEGER; - - if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) { - return UNITS[i]; - } - } - - return UNITS[ilen - 1]; -} - -/** - * Figures out what unit to format a set of ticks with - */ -function determineUnitForFormatting(scale, numTicks, minUnit, min, max) { - var i, unit; - - for (i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) { - unit = UNITS[i]; - if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) { - return unit; - } - } - - return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0]; -} - -function determineMajorUnit(unit) { - for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) { - if (INTERVALS[UNITS[i]].common) { - return UNITS[i]; - } - } -} - -/** - * Generates a maximum of `capacity` timestamps between min and max, rounded to the - * `minor` unit using the given scale time `options`. - * Important: this method can return ticks outside the min and max range, it's the - * responsibility of the calling code to clamp values if needed. - */ -function generate(scale, min, max, capacity) { - var adapter = scale._adapter; - var options = scale.options; - var timeOpts = options.time; - var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, capacity); - var stepSize = resolve$5([timeOpts.stepSize, timeOpts.unitStepSize, 1]); - var weekday = minor === 'week' ? timeOpts.isoWeekday : false; - var first = min; - var ticks = []; - var time; - - // For 'week' unit, handle the first day of week option - if (weekday) { - first = +adapter.startOf(first, 'isoWeek', weekday); - } - - // Align first ticks on unit - first = +adapter.startOf(first, weekday ? 'day' : minor); - - // Prevent browser from freezing in case user options request millions of milliseconds - if (adapter.diff(max, min, minor) > 100000 * stepSize) { - throw min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor; - } - - for (time = first; time < max; time = +adapter.add(time, stepSize, minor)) { - ticks.push(time); - } - - if (time === max || options.bounds === 'ticks') { - ticks.push(time); - } - - return ticks; -} - -/** - * Returns the start and end offsets from edges in the form of {start, end} - * where each value is a relative width to the scale and ranges between 0 and 1. - * They add extra margins on the both sides by scaling down the original scale. - * Offsets are added when the `offset` option is true. - */ -function computeOffsets(table, ticks, min, max, options) { - var start = 0; - var end = 0; - var first, last; - - if (options.offset && ticks.length) { - first = interpolate$1(table, 'time', ticks[0], 'pos'); - if (ticks.length === 1) { - start = 1 - first; - } else { - start = (interpolate$1(table, 'time', ticks[1], 'pos') - first) / 2; - } - last = interpolate$1(table, 'time', ticks[ticks.length - 1], 'pos'); - if (ticks.length === 1) { - end = last; - } else { - end = (last - interpolate$1(table, 'time', ticks[ticks.length - 2], 'pos')) / 2; - } - } - - return {start: start, end: end, factor: 1 / (start + 1 + end)}; -} - -function setMajorTicks(scale, ticks, map, majorUnit) { - var adapter = scale._adapter; - var first = +adapter.startOf(ticks[0].value, majorUnit); - var last = ticks[ticks.length - 1].value; - var major, index; - - for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) { - index = map[major]; - if (index >= 0) { - ticks[index].major = true; - } - } - return ticks; -} - -function ticksFromTimestamps(scale, values, majorUnit) { - var ticks = []; - var map = {}; - var ilen = values.length; - var i, value; - - for (i = 0; i < ilen; ++i) { - value = values[i]; - map[value] = i; - - ticks.push({ - value: value, - major: false - }); - } - - // We set the major ticks separately from the above loop because calling startOf for every tick - // is expensive when there is a large number of ticks - return (ilen === 0 || !majorUnit) ? ticks : setMajorTicks(scale, ticks, map, majorUnit); -} - -var defaultConfig$4 = { - position: 'bottom', - - /** - * Data distribution along the scale: - * - 'linear': data are spread according to their time (distances can vary), - * - 'series': data are spread at the same distance from each other. - * @see https://github.com/chartjs/Chart.js/pull/4507 - * @since 2.7.0 - */ - distribution: 'linear', - - /** - * Scale boundary strategy (bypassed by min/max time options) - * - `data`: make sure data are fully visible, ticks outside are removed - * - `ticks`: make sure ticks are fully visible, data outside are truncated - * @see https://github.com/chartjs/Chart.js/pull/4556 - * @since 2.7.0 - */ - bounds: 'data', - - adapters: {}, - time: { - parser: false, // false == a pattern string from https://momentjs.com/docs/#/parsing/string-format/ or a custom callback that converts its argument to a moment - unit: false, // false == automatic or override with week, month, year, etc. - round: false, // none, or override with week, month, year, etc. - displayFormat: false, // DEPRECATED - isoWeekday: false, // override week start day - see https://momentjs.com/docs/#/get-set/iso-weekday/ - minUnit: 'millisecond', - displayFormats: {} - }, - ticks: { - autoSkip: false, - - /** - * Ticks generation input values: - * - 'auto': generates "optimal" ticks based on scale size and time options. - * - 'data': generates ticks from data (including labels from data {t|x|y} objects). - * - 'labels': generates ticks from user given `data.labels` values ONLY. - * @see https://github.com/chartjs/Chart.js/pull/4507 - * @since 2.7.0 - */ - source: 'auto', - - major: { - enabled: false - } - } -}; - -var scale_time = core_scale.extend({ - initialize: function() { - this.mergeTicksOptions(); - core_scale.prototype.initialize.call(this); - }, - - update: function() { - var me = this; - var options = me.options; - var time = options.time || (options.time = {}); - var adapter = me._adapter = new core_adapters._date(options.adapters.date); - - // DEPRECATIONS: output a message only one time per update - deprecated$1('time scale', time.format, 'time.format', 'time.parser'); - deprecated$1('time scale', time.min, 'time.min', 'ticks.min'); - deprecated$1('time scale', time.max, 'time.max', 'ticks.max'); - - // Backward compatibility: before introducing adapter, `displayFormats` was - // supposed to contain *all* unit/string pairs but this can't be resolved - // when loading the scale (adapters are loaded afterward), so let's populate - // missing formats on update - helpers$1.mergeIf(time.displayFormats, adapter.formats()); - - return core_scale.prototype.update.apply(me, arguments); - }, - - /** - * Allows data to be referenced via 't' attribute - */ - getRightValue: function(rawValue) { - if (rawValue && rawValue.t !== undefined) { - rawValue = rawValue.t; - } - return core_scale.prototype.getRightValue.call(this, rawValue); - }, - - determineDataLimits: function() { - var me = this; - var chart = me.chart; - var adapter = me._adapter; - var options = me.options; - var unit = options.time.unit || 'day'; - var min = MAX_INTEGER; - var max = MIN_INTEGER; - var timestamps = []; - var datasets = []; - var labels = []; - var i, j, ilen, jlen, data, timestamp, labelsAdded; - var dataLabels = me._getLabels(); - - for (i = 0, ilen = dataLabels.length; i < ilen; ++i) { - labels.push(parse(me, dataLabels[i])); - } - - for (i = 0, ilen = (chart.data.datasets || []).length; i < ilen; ++i) { - if (chart.isDatasetVisible(i)) { - data = chart.data.datasets[i].data; - - // Let's consider that all data have the same format. - if (helpers$1.isObject(data[0])) { - datasets[i] = []; - - for (j = 0, jlen = data.length; j < jlen; ++j) { - timestamp = parse(me, data[j]); - timestamps.push(timestamp); - datasets[i][j] = timestamp; - } - } else { - datasets[i] = labels.slice(0); - if (!labelsAdded) { - timestamps = timestamps.concat(labels); - labelsAdded = true; - } - } - } else { - datasets[i] = []; - } - } - - if (labels.length) { - min = Math.min(min, labels[0]); - max = Math.max(max, labels[labels.length - 1]); - } - - if (timestamps.length) { - timestamps = ilen > 1 ? arrayUnique(timestamps).sort(sorter) : timestamps.sort(sorter); - min = Math.min(min, timestamps[0]); - max = Math.max(max, timestamps[timestamps.length - 1]); - } - - min = parse(me, getMin(options)) || min; - max = parse(me, getMax(options)) || max; - - // In case there is no valid min/max, set limits based on unit time option - min = min === MAX_INTEGER ? +adapter.startOf(Date.now(), unit) : min; - max = max === MIN_INTEGER ? +adapter.endOf(Date.now(), unit) + 1 : max; - - // Make sure that max is strictly higher than min (required by the lookup table) - me.min = Math.min(min, max); - me.max = Math.max(min + 1, max); - - // PRIVATE - me._table = []; - me._timestamps = { - data: timestamps, - datasets: datasets, - labels: labels - }; - }, - - buildTicks: function() { - var me = this; - var min = me.min; - var max = me.max; - var options = me.options; - var tickOpts = options.ticks; - var timeOpts = options.time; - var timestamps = me._timestamps; - var ticks = []; - var capacity = me.getLabelCapacity(min); - var source = tickOpts.source; - var distribution = options.distribution; - var i, ilen, timestamp; - - if (source === 'data' || (source === 'auto' && distribution === 'series')) { - timestamps = timestamps.data; - } else if (source === 'labels') { - timestamps = timestamps.labels; - } else { - timestamps = generate(me, min, max, capacity); - } - - if (options.bounds === 'ticks' && timestamps.length) { - min = timestamps[0]; - max = timestamps[timestamps.length - 1]; - } - - // Enforce limits with user min/max options - min = parse(me, getMin(options)) || min; - max = parse(me, getMax(options)) || max; - - // Remove ticks outside the min/max range - for (i = 0, ilen = timestamps.length; i < ilen; ++i) { - timestamp = timestamps[i]; - if (timestamp >= min && timestamp <= max) { - ticks.push(timestamp); - } - } - - me.min = min; - me.max = max; - - // PRIVATE - // determineUnitForFormatting relies on the number of ticks so we don't use it when - // autoSkip is enabled because we don't yet know what the final number of ticks will be - me._unit = timeOpts.unit || (tickOpts.autoSkip - ? determineUnitForAutoTicks(timeOpts.minUnit, me.min, me.max, capacity) - : determineUnitForFormatting(me, ticks.length, timeOpts.minUnit, me.min, me.max)); - me._majorUnit = !tickOpts.major.enabled || me._unit === 'year' ? undefined - : determineMajorUnit(me._unit); - me._table = buildLookupTable(me._timestamps.data, min, max, distribution); - me._offsets = computeOffsets(me._table, ticks, min, max, options); - - if (tickOpts.reverse) { - ticks.reverse(); - } - - return ticksFromTimestamps(me, ticks, me._majorUnit); - }, - - getLabelForIndex: function(index, datasetIndex) { - var me = this; - var adapter = me._adapter; - var data = me.chart.data; - var timeOpts = me.options.time; - var label = data.labels && index < data.labels.length ? data.labels[index] : ''; - var value = data.datasets[datasetIndex].data[index]; - - if (helpers$1.isObject(value)) { - label = me.getRightValue(value); - } - if (timeOpts.tooltipFormat) { - return adapter.format(toTimestamp(me, label), timeOpts.tooltipFormat); - } - if (typeof label === 'string') { - return label; - } - return adapter.format(toTimestamp(me, label), timeOpts.displayFormats.datetime); - }, - - /** - * Function to format an individual tick mark - * @private - */ - tickFormatFunction: function(time, index, ticks, format) { - var me = this; - var adapter = me._adapter; - var options = me.options; - var formats = options.time.displayFormats; - var minorFormat = formats[me._unit]; - var majorUnit = me._majorUnit; - var majorFormat = formats[majorUnit]; - var tick = ticks[index]; - var tickOpts = options.ticks; - var major = majorUnit && majorFormat && tick && tick.major; - var label = adapter.format(time, format ? format : major ? majorFormat : minorFormat); - var nestedTickOpts = major ? tickOpts.major : tickOpts.minor; - var formatter = resolve$5([ - nestedTickOpts.callback, - nestedTickOpts.userCallback, - tickOpts.callback, - tickOpts.userCallback - ]); - - return formatter ? formatter(label, index, ticks) : label; - }, - - convertTicksToLabels: function(ticks) { - var labels = []; - var i, ilen; - - for (i = 0, ilen = ticks.length; i < ilen; ++i) { - labels.push(this.tickFormatFunction(ticks[i].value, i, ticks)); - } - - return labels; - }, - - /** - * @private - */ - getPixelForOffset: function(time) { - var me = this; - var offsets = me._offsets; - var pos = interpolate$1(me._table, 'time', time, 'pos'); - return me.getPixelForDecimal((offsets.start + pos) * offsets.factor); - }, - - getPixelForValue: function(value, index, datasetIndex) { - var me = this; - var time = null; - - if (index !== undefined && datasetIndex !== undefined) { - time = me._timestamps.datasets[datasetIndex][index]; - } - - if (time === null) { - time = parse(me, value); - } - - if (time !== null) { - return me.getPixelForOffset(time); - } - }, - - getPixelForTick: function(index) { - var ticks = this.getTicks(); - return index >= 0 && index < ticks.length ? - this.getPixelForOffset(ticks[index].value) : - null; - }, - - getValueForPixel: function(pixel) { - var me = this; - var offsets = me._offsets; - var pos = me.getDecimalForPixel(pixel) / offsets.factor - offsets.end; - var time = interpolate$1(me._table, 'pos', pos, 'time'); - - // DEPRECATION, we should return time directly - return me._adapter._create(time); - }, - - /** - * @private - */ - _getLabelSize: function(label) { - var me = this; - var ticksOpts = me.options.ticks; - var tickLabelWidth = me.ctx.measureText(label).width; - var angle = helpers$1.toRadians(me.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation); - var cosRotation = Math.cos(angle); - var sinRotation = Math.sin(angle); - var tickFontSize = valueOrDefault$d(ticksOpts.fontSize, core_defaults.global.defaultFontSize); - - return { - w: (tickLabelWidth * cosRotation) + (tickFontSize * sinRotation), - h: (tickLabelWidth * sinRotation) + (tickFontSize * cosRotation) - }; - }, - - /** - * Crude approximation of what the label width might be - * @private - */ - getLabelWidth: function(label) { - return this._getLabelSize(label).w; - }, - - /** - * @private - */ - getLabelCapacity: function(exampleTime) { - var me = this; - var timeOpts = me.options.time; - var displayFormats = timeOpts.displayFormats; - - // pick the longest format (milliseconds) for guestimation - var format = displayFormats[timeOpts.unit] || displayFormats.millisecond; - var exampleLabel = me.tickFormatFunction(exampleTime, 0, ticksFromTimestamps(me, [exampleTime], me._majorUnit), format); - var size = me._getLabelSize(exampleLabel); - var capacity = Math.floor(me.isHorizontal() ? me.width / size.w : me.height / size.h); - - if (me.options.offset) { - capacity--; - } - - return capacity > 0 ? capacity : 1; - } -}); - -// INTERNAL: static default options, registered in src/index.js -var _defaults$4 = defaultConfig$4; -scale_time._defaults = _defaults$4; - -var scales = { - category: scale_category, - linear: scale_linear, - logarithmic: scale_logarithmic, - radialLinear: scale_radialLinear, - time: scale_time -}; - -var FORMATS = { - datetime: 'MMM D, YYYY, h:mm:ss a', - millisecond: 'h:mm:ss.SSS a', - second: 'h:mm:ss a', - minute: 'h:mm a', - hour: 'hA', - day: 'MMM D', - week: 'll', - month: 'MMM YYYY', - quarter: '[Q]Q - YYYY', - year: 'YYYY' -}; - -core_adapters._date.override(typeof moment === 'function' ? { - _id: 'moment', // DEBUG ONLY - - formats: function() { - return FORMATS; - }, - - parse: function(value, format) { - if (typeof value === 'string' && typeof format === 'string') { - value = moment(value, format); - } else if (!(value instanceof moment)) { - value = moment(value); - } - return value.isValid() ? value.valueOf() : null; - }, - - format: function(time, format) { - return moment(time).format(format); - }, - - add: function(time, amount, unit) { - return moment(time).add(amount, unit).valueOf(); - }, - - diff: function(max, min, unit) { - return moment(max).diff(moment(min), unit); - }, - - startOf: function(time, unit, weekday) { - time = moment(time); - if (unit === 'isoWeek') { - return time.isoWeekday(weekday).valueOf(); - } - return time.startOf(unit).valueOf(); - }, - - endOf: function(time, unit) { - return moment(time).endOf(unit).valueOf(); - }, - - // DEPRECATIONS - - /** - * Provided for backward compatibility with scale.getValueForPixel(). - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ - _create: function(time) { - return moment(time); - }, -} : {}); - -core_defaults._set('global', { - plugins: { - filler: { - propagate: true - } - } -}); - -var mappers = { - dataset: function(source) { - var index = source.fill; - var chart = source.chart; - var meta = chart.getDatasetMeta(index); - var visible = meta && chart.isDatasetVisible(index); - var points = (visible && meta.dataset._children) || []; - var length = points.length || 0; - - return !length ? null : function(point, i) { - return (i < length && points[i]._view) || null; - }; - }, - - boundary: function(source) { - var boundary = source.boundary; - var x = boundary ? boundary.x : null; - var y = boundary ? boundary.y : null; - - if (helpers$1.isArray(boundary)) { - return function(point, i) { - return boundary[i]; - }; - } - - return function(point) { - return { - x: x === null ? point.x : x, - y: y === null ? point.y : y, - }; - }; - } -}; - -// @todo if (fill[0] === '#') -function decodeFill(el, index, count) { - var model = el._model || {}; - var fill = model.fill; - var target; - - if (fill === undefined) { - fill = !!model.backgroundColor; - } - - if (fill === false || fill === null) { - return false; - } - - if (fill === true) { - return 'origin'; - } - - target = parseFloat(fill, 10); - if (isFinite(target) && Math.floor(target) === target) { - if (fill[0] === '-' || fill[0] === '+') { - target = index + target; - } - - if (target === index || target < 0 || target >= count) { - return false; - } - - return target; - } - - switch (fill) { - // compatibility - case 'bottom': - return 'start'; - case 'top': - return 'end'; - case 'zero': - return 'origin'; - // supported boundaries - case 'origin': - case 'start': - case 'end': - return fill; - // invalid fill values - default: - return false; - } -} - -function computeLinearBoundary(source) { - var model = source.el._model || {}; - var scale = source.el._scale || {}; - var fill = source.fill; - var target = null; - var horizontal; - - if (isFinite(fill)) { - return null; - } - - // Backward compatibility: until v3, we still need to support boundary values set on - // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and - // controllers might still use it (e.g. the Smith chart). - - if (fill === 'start') { - target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom; - } else if (fill === 'end') { - target = model.scaleTop === undefined ? scale.top : model.scaleTop; - } else if (model.scaleZero !== undefined) { - target = model.scaleZero; - } else if (scale.getBasePixel) { - target = scale.getBasePixel(); - } - - if (target !== undefined && target !== null) { - if (target.x !== undefined && target.y !== undefined) { - return target; - } - - if (helpers$1.isFinite(target)) { - horizontal = scale.isHorizontal(); - return { - x: horizontal ? target : null, - y: horizontal ? null : target - }; - } - } - - return null; -} - -function computeCircularBoundary(source) { - var scale = source.el._scale; - var options = scale.options; - var length = scale.chart.data.labels.length; - var fill = source.fill; - var target = []; - var start, end, center, i, point; - - if (!length) { - return null; - } - - start = options.ticks.reverse ? scale.max : scale.min; - end = options.ticks.reverse ? scale.min : scale.max; - center = scale.getPointPositionForValue(0, start); - for (i = 0; i < length; ++i) { - point = fill === 'start' || fill === 'end' - ? scale.getPointPositionForValue(i, fill === 'start' ? start : end) - : scale.getBasePosition(i); - if (options.gridLines.circular) { - point.cx = center.x; - point.cy = center.y; - point.angle = scale.getIndexAngle(i) - Math.PI / 2; - } - target.push(point); - } - return target; -} - -function computeBoundary(source) { - var scale = source.el._scale || {}; - - if (scale.getPointPositionForValue) { - return computeCircularBoundary(source); - } - return computeLinearBoundary(source); -} - -function resolveTarget(sources, index, propagate) { - var source = sources[index]; - var fill = source.fill; - var visited = [index]; - var target; - - if (!propagate) { - return fill; - } - - while (fill !== false && visited.indexOf(fill) === -1) { - if (!isFinite(fill)) { - return fill; - } - - target = sources[fill]; - if (!target) { - return false; - } - - if (target.visible) { - return fill; - } - - visited.push(fill); - fill = target.fill; - } - - return false; -} - -function createMapper(source) { - var fill = source.fill; - var type = 'dataset'; - - if (fill === false) { - return null; - } - - if (!isFinite(fill)) { - type = 'boundary'; - } - - return mappers[type](source); -} - -function isDrawable(point) { - return point && !point.skip; -} - -function drawArea(ctx, curve0, curve1, len0, len1) { - var i, cx, cy, r; - - if (!len0 || !len1) { - return; - } - - // building first area curve (normal) - ctx.moveTo(curve0[0].x, curve0[0].y); - for (i = 1; i < len0; ++i) { - helpers$1.canvas.lineTo(ctx, curve0[i - 1], curve0[i]); - } - - if (curve1[0].angle !== undefined) { - cx = curve1[0].cx; - cy = curve1[0].cy; - r = Math.sqrt(Math.pow(curve1[0].x - cx, 2) + Math.pow(curve1[0].y - cy, 2)); - for (i = len1 - 1; i > 0; --i) { - ctx.arc(cx, cy, r, curve1[i].angle, curve1[i - 1].angle, true); - } - return; - } - - // joining the two area curves - ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y); - - // building opposite area curve (reverse) - for (i = len1 - 1; i > 0; --i) { - helpers$1.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true); - } -} - -function doFill(ctx, points, mapper, view, color, loop) { - var count = points.length; - var span = view.spanGaps; - var curve0 = []; - var curve1 = []; - var len0 = 0; - var len1 = 0; - var i, ilen, index, p0, p1, d0, d1, loopOffset; - - ctx.beginPath(); - - for (i = 0, ilen = count; i < ilen; ++i) { - index = i % count; - p0 = points[index]._view; - p1 = mapper(p0, index, view); - d0 = isDrawable(p0); - d1 = isDrawable(p1); - - if (loop && loopOffset === undefined && d0) { - loopOffset = i + 1; - ilen = count + loopOffset; - } - - if (d0 && d1) { - len0 = curve0.push(p0); - len1 = curve1.push(p1); - } else if (len0 && len1) { - if (!span) { - drawArea(ctx, curve0, curve1, len0, len1); - len0 = len1 = 0; - curve0 = []; - curve1 = []; - } else { - if (d0) { - curve0.push(p0); - } - if (d1) { - curve1.push(p1); - } - } - } - } - - drawArea(ctx, curve0, curve1, len0, len1); - - ctx.closePath(); - ctx.fillStyle = color; - ctx.fill(); -} - -var plugin_filler = { - id: 'filler', - - afterDatasetsUpdate: function(chart, options) { - var count = (chart.data.datasets || []).length; - var propagate = options.propagate; - var sources = []; - var meta, i, el, source; - - for (i = 0; i < count; ++i) { - meta = chart.getDatasetMeta(i); - el = meta.dataset; - source = null; - - if (el && el._model && el instanceof elements.Line) { - source = { - visible: chart.isDatasetVisible(i), - fill: decodeFill(el, i, count), - chart: chart, - el: el - }; - } - - meta.$filler = source; - sources.push(source); - } - - for (i = 0; i < count; ++i) { - source = sources[i]; - if (!source) { - continue; - } - - source.fill = resolveTarget(sources, i, propagate); - source.boundary = computeBoundary(source); - source.mapper = createMapper(source); - } - }, - - beforeDatasetsDraw: function(chart) { - var metasets = chart._getSortedVisibleDatasetMetas(); - var ctx = chart.ctx; - var meta, i, el, view, points, mapper, color; - - for (i = metasets.length - 1; i >= 0; --i) { - meta = metasets[i].$filler; - - if (!meta || !meta.visible) { - continue; - } - - el = meta.el; - view = el._view; - points = el._children || []; - mapper = meta.mapper; - color = view.backgroundColor || core_defaults.global.defaultColor; - - if (mapper && color && points.length) { - helpers$1.canvas.clipArea(ctx, chart.chartArea); - doFill(ctx, points, mapper, view, color, el._loop); - helpers$1.canvas.unclipArea(ctx); - } - } - } -}; - -var getRtlHelper$1 = helpers$1.rtl.getRtlAdapter; -var noop$1 = helpers$1.noop; -var valueOrDefault$e = helpers$1.valueOrDefault; - -core_defaults._set('global', { - legend: { - display: true, - position: 'top', - align: 'center', - fullWidth: true, - reverse: false, - weight: 1000, - - // a callback that will handle - onClick: function(e, legendItem) { - var index = legendItem.datasetIndex; - var ci = this.chart; - var meta = ci.getDatasetMeta(index); - - // See controller.isDatasetVisible comment - meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null; - - // We hid a dataset ... rerender the chart - ci.update(); - }, - - onHover: null, - onLeave: null, - - labels: { - boxWidth: 40, - padding: 10, - // Generates labels shown in the legend - // Valid properties to return: - // text : text to display - // fillStyle : fill of coloured box - // strokeStyle: stroke of coloured box - // hidden : if this legend item refers to a hidden item - // lineCap : cap style for line - // lineDash - // lineDashOffset : - // lineJoin : - // lineWidth : - generateLabels: function(chart) { - var datasets = chart.data.datasets; - var options = chart.options.legend || {}; - var usePointStyle = options.labels && options.labels.usePointStyle; - - return chart._getSortedDatasetMetas().map(function(meta) { - var style = meta.controller.getStyle(usePointStyle ? 0 : undefined); - - return { - text: datasets[meta.index].label, - fillStyle: style.backgroundColor, - hidden: !chart.isDatasetVisible(meta.index), - lineCap: style.borderCapStyle, - lineDash: style.borderDash, - lineDashOffset: style.borderDashOffset, - lineJoin: style.borderJoinStyle, - lineWidth: style.borderWidth, - strokeStyle: style.borderColor, - pointStyle: style.pointStyle, - rotation: style.rotation, - - // Below is extra data used for toggling the datasets - datasetIndex: meta.index - }; - }, this); - } - } - }, - - legendCallback: function(chart) { - var list = document.createElement('ul'); - var datasets = chart.data.datasets; - var i, ilen, listItem, listItemSpan; - - list.setAttribute('class', chart.id + '-legend'); - - for (i = 0, ilen = datasets.length; i < ilen; i++) { - listItem = list.appendChild(document.createElement('li')); - listItemSpan = listItem.appendChild(document.createElement('span')); - listItemSpan.style.backgroundColor = datasets[i].backgroundColor; - if (datasets[i].label) { - listItem.appendChild(document.createTextNode(datasets[i].label)); - } - } - - return list.outerHTML; - } -}); - -/** - * Helper function to get the box width based on the usePointStyle option - * @param {object} labelopts - the label options on the legend - * @param {number} fontSize - the label font size - * @return {number} width of the color box area - */ -function getBoxWidth(labelOpts, fontSize) { - return labelOpts.usePointStyle && labelOpts.boxWidth > fontSize ? - fontSize : - labelOpts.boxWidth; -} - -/** - * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! - */ -var Legend = core_element.extend({ - - initialize: function(config) { - var me = this; - helpers$1.extend(me, config); - - // Contains hit boxes for each dataset (in dataset order) - me.legendHitBoxes = []; - - /** - * @private - */ - me._hoveredItem = null; - - // Are we in doughnut mode which has a different data type - me.doughnutMode = false; - }, - - // These methods are ordered by lifecycle. Utilities then follow. - // Any function defined here is inherited by all legend types. - // Any function can be extended by the legend type - - beforeUpdate: noop$1, - update: function(maxWidth, maxHeight, margins) { - var me = this; - - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); - - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = margins; - - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - // Labels - me.beforeBuildLabels(); - me.buildLabels(); - me.afterBuildLabels(); - - // Fit - me.beforeFit(); - me.fit(); - me.afterFit(); - // - me.afterUpdate(); - - return me.minSize; - }, - afterUpdate: noop$1, - - // - - beforeSetDimensions: noop$1, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; - - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } - - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; - - // Reset minSize - me.minSize = { - width: 0, - height: 0 - }; - }, - afterSetDimensions: noop$1, - - // - - beforeBuildLabels: noop$1, - buildLabels: function() { - var me = this; - var labelOpts = me.options.labels || {}; - var legendItems = helpers$1.callback(labelOpts.generateLabels, [me.chart], me) || []; - - if (labelOpts.filter) { - legendItems = legendItems.filter(function(item) { - return labelOpts.filter(item, me.chart.data); - }); - } - - if (me.options.reverse) { - legendItems.reverse(); - } - - me.legendItems = legendItems; - }, - afterBuildLabels: noop$1, - - // - - beforeFit: noop$1, - fit: function() { - var me = this; - var opts = me.options; - var labelOpts = opts.labels; - var display = opts.display; - - var ctx = me.ctx; - - var labelFont = helpers$1.options._parseFont(labelOpts); - var fontSize = labelFont.size; - - // Reset hit boxes - var hitboxes = me.legendHitBoxes = []; - - var minSize = me.minSize; - var isHorizontal = me.isHorizontal(); - - if (isHorizontal) { - minSize.width = me.maxWidth; // fill all the width - minSize.height = display ? 10 : 0; - } else { - minSize.width = display ? 10 : 0; - minSize.height = me.maxHeight; // fill all the height - } - - // Increase sizes here - if (!display) { - me.width = minSize.width = me.height = minSize.height = 0; - return; - } - ctx.font = labelFont.string; - - if (isHorizontal) { - // Labels - - // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one - var lineWidths = me.lineWidths = [0]; - var totalHeight = 0; - - ctx.textAlign = 'left'; - ctx.textBaseline = 'middle'; - - helpers$1.each(me.legendItems, function(legendItem, i) { - var boxWidth = getBoxWidth(labelOpts, fontSize); - var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; - - if (i === 0 || lineWidths[lineWidths.length - 1] + width + 2 * labelOpts.padding > minSize.width) { - totalHeight += fontSize + labelOpts.padding; - lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0; - } - - // Store the hitbox width and height here. Final position will be updated in `draw` - hitboxes[i] = { - left: 0, - top: 0, - width: width, - height: fontSize - }; - - lineWidths[lineWidths.length - 1] += width + labelOpts.padding; - }); - - minSize.height += totalHeight; - - } else { - var vPadding = labelOpts.padding; - var columnWidths = me.columnWidths = []; - var columnHeights = me.columnHeights = []; - var totalWidth = labelOpts.padding; - var currentColWidth = 0; - var currentColHeight = 0; - - helpers$1.each(me.legendItems, function(legendItem, i) { - var boxWidth = getBoxWidth(labelOpts, fontSize); - var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width; - - // If too tall, go to new column - if (i > 0 && currentColHeight + fontSize + 2 * vPadding > minSize.height) { - totalWidth += currentColWidth + labelOpts.padding; - columnWidths.push(currentColWidth); // previous column width - columnHeights.push(currentColHeight); - currentColWidth = 0; - currentColHeight = 0; - } - - // Get max width - currentColWidth = Math.max(currentColWidth, itemWidth); - currentColHeight += fontSize + vPadding; - - // Store the hitbox width and height here. Final position will be updated in `draw` - hitboxes[i] = { - left: 0, - top: 0, - width: itemWidth, - height: fontSize - }; - }); - - totalWidth += currentColWidth; - columnWidths.push(currentColWidth); - columnHeights.push(currentColHeight); - minSize.width += totalWidth; - } - - me.width = minSize.width; - me.height = minSize.height; - }, - afterFit: noop$1, - - // Shared Methods - isHorizontal: function() { - return this.options.position === 'top' || this.options.position === 'bottom'; - }, - - // Actually draw the legend on the canvas - draw: function() { - var me = this; - var opts = me.options; - var labelOpts = opts.labels; - var globalDefaults = core_defaults.global; - var defaultColor = globalDefaults.defaultColor; - var lineDefault = globalDefaults.elements.line; - var legendHeight = me.height; - var columnHeights = me.columnHeights; - var legendWidth = me.width; - var lineWidths = me.lineWidths; - - if (!opts.display) { - return; - } - - var rtlHelper = getRtlHelper$1(opts.rtl, me.left, me.minSize.width); - var ctx = me.ctx; - var fontColor = valueOrDefault$e(labelOpts.fontColor, globalDefaults.defaultFontColor); - var labelFont = helpers$1.options._parseFont(labelOpts); - var fontSize = labelFont.size; - var cursor; - - // Canvas setup - ctx.textAlign = rtlHelper.textAlign('left'); - ctx.textBaseline = 'middle'; - ctx.lineWidth = 0.5; - ctx.strokeStyle = fontColor; // for strikethrough effect - ctx.fillStyle = fontColor; // render in correct colour - ctx.font = labelFont.string; - - var boxWidth = getBoxWidth(labelOpts, fontSize); - var hitboxes = me.legendHitBoxes; - - // current position - var drawLegendBox = function(x, y, legendItem) { - if (isNaN(boxWidth) || boxWidth <= 0) { - return; - } - - // Set the ctx for the box - ctx.save(); - - var lineWidth = valueOrDefault$e(legendItem.lineWidth, lineDefault.borderWidth); - ctx.fillStyle = valueOrDefault$e(legendItem.fillStyle, defaultColor); - ctx.lineCap = valueOrDefault$e(legendItem.lineCap, lineDefault.borderCapStyle); - ctx.lineDashOffset = valueOrDefault$e(legendItem.lineDashOffset, lineDefault.borderDashOffset); - ctx.lineJoin = valueOrDefault$e(legendItem.lineJoin, lineDefault.borderJoinStyle); - ctx.lineWidth = lineWidth; - ctx.strokeStyle = valueOrDefault$e(legendItem.strokeStyle, defaultColor); - - if (ctx.setLineDash) { - // IE 9 and 10 do not support line dash - ctx.setLineDash(valueOrDefault$e(legendItem.lineDash, lineDefault.borderDash)); - } - - if (labelOpts && labelOpts.usePointStyle) { - // Recalculate x and y for drawPoint() because its expecting - // x and y to be center of figure (instead of top left) - var radius = boxWidth * Math.SQRT2 / 2; - var centerX = rtlHelper.xPlus(x, boxWidth / 2); - var centerY = y + fontSize / 2; - - // Draw pointStyle as legend symbol - helpers$1.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation); - } else { - // Draw box as legend symbol - ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); - if (lineWidth !== 0) { - ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize); - } - } - - ctx.restore(); - }; - - var fillText = function(x, y, legendItem, textWidth) { - var halfFontSize = fontSize / 2; - var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize); - var yMiddle = y + halfFontSize; - - ctx.fillText(legendItem.text, xLeft, yMiddle); - - if (legendItem.hidden) { - // Strikethrough the text if hidden - ctx.beginPath(); - ctx.lineWidth = 2; - ctx.moveTo(xLeft, yMiddle); - ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle); - ctx.stroke(); - } - }; - - var alignmentOffset = function(dimension, blockSize) { - switch (opts.align) { - case 'start': - return labelOpts.padding; - case 'end': - return dimension - blockSize; - default: // center - return (dimension - blockSize + labelOpts.padding) / 2; - } - }; - - // Horizontal - var isHorizontal = me.isHorizontal(); - if (isHorizontal) { - cursor = { - x: me.left + alignmentOffset(legendWidth, lineWidths[0]), - y: me.top + labelOpts.padding, - line: 0 - }; - } else { - cursor = { - x: me.left + labelOpts.padding, - y: me.top + alignmentOffset(legendHeight, columnHeights[0]), - line: 0 - }; - } - - helpers$1.rtl.overrideTextDirection(me.ctx, opts.textDirection); - - var itemHeight = fontSize + labelOpts.padding; - helpers$1.each(me.legendItems, function(legendItem, i) { - var textWidth = ctx.measureText(legendItem.text).width; - var width = boxWidth + (fontSize / 2) + textWidth; - var x = cursor.x; - var y = cursor.y; - - rtlHelper.setWidth(me.minSize.width); - - // Use (me.left + me.minSize.width) and (me.top + me.minSize.height) - // instead of me.right and me.bottom because me.width and me.height - // may have been changed since me.minSize was calculated - if (isHorizontal) { - if (i > 0 && x + width + labelOpts.padding > me.left + me.minSize.width) { - y = cursor.y += itemHeight; - cursor.line++; - x = cursor.x = me.left + alignmentOffset(legendWidth, lineWidths[cursor.line]); - } - } else if (i > 0 && y + itemHeight > me.top + me.minSize.height) { - x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding; - cursor.line++; - y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]); - } - - var realX = rtlHelper.x(x); - - drawLegendBox(realX, y, legendItem); - - hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width); - hitboxes[i].top = y; - - // Fill the actual label - fillText(realX, y, legendItem, textWidth); - - if (isHorizontal) { - cursor.x += width + labelOpts.padding; - } else { - cursor.y += itemHeight; - } - }); - - helpers$1.rtl.restoreTextDirection(me.ctx, opts.textDirection); - }, - - /** - * @private - */ - _getLegendItemAt: function(x, y) { - var me = this; - var i, hitBox, lh; - - if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) { - // See if we are touching one of the dataset boxes - lh = me.legendHitBoxes; - for (i = 0; i < lh.length; ++i) { - hitBox = lh[i]; - - if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) { - // Touching an element - return me.legendItems[i]; - } - } - } - - return null; - }, - - /** - * Handle an event - * @private - * @param {IEvent} event - The event to handle - */ - handleEvent: function(e) { - var me = this; - var opts = me.options; - var type = e.type === 'mouseup' ? 'click' : e.type; - var hoveredItem; - - if (type === 'mousemove') { - if (!opts.onHover && !opts.onLeave) { - return; - } - } else if (type === 'click') { - if (!opts.onClick) { - return; - } - } else { - return; - } - - // Chart event already has relative position in it - hoveredItem = me._getLegendItemAt(e.x, e.y); - - if (type === 'click') { - if (hoveredItem && opts.onClick) { - // use e.native for backwards compatibility - opts.onClick.call(me, e.native, hoveredItem); - } - } else { - if (opts.onLeave && hoveredItem !== me._hoveredItem) { - if (me._hoveredItem) { - opts.onLeave.call(me, e.native, me._hoveredItem); - } - me._hoveredItem = hoveredItem; - } - - if (opts.onHover && hoveredItem) { - // use e.native for backwards compatibility - opts.onHover.call(me, e.native, hoveredItem); - } - } - } -}); - -function createNewLegendAndAttach(chart, legendOpts) { - var legend = new Legend({ - ctx: chart.ctx, - options: legendOpts, - chart: chart - }); - - core_layouts.configure(chart, legend, legendOpts); - core_layouts.addBox(chart, legend); - chart.legend = legend; -} - -var plugin_legend = { - id: 'legend', - - /** - * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making - * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of - * the plugin, which one will be re-exposed in the chart.js file. - * https://github.com/chartjs/Chart.js/pull/2640 - * @private - */ - _element: Legend, - - beforeInit: function(chart) { - var legendOpts = chart.options.legend; - - if (legendOpts) { - createNewLegendAndAttach(chart, legendOpts); - } - }, - - beforeUpdate: function(chart) { - var legendOpts = chart.options.legend; - var legend = chart.legend; - - if (legendOpts) { - helpers$1.mergeIf(legendOpts, core_defaults.global.legend); - - if (legend) { - core_layouts.configure(chart, legend, legendOpts); - legend.options = legendOpts; - } else { - createNewLegendAndAttach(chart, legendOpts); - } - } else if (legend) { - core_layouts.removeBox(chart, legend); - delete chart.legend; - } - }, - - afterEvent: function(chart, e) { - var legend = chart.legend; - if (legend) { - legend.handleEvent(e); - } - } -}; - -var noop$2 = helpers$1.noop; - -core_defaults._set('global', { - title: { - display: false, - fontStyle: 'bold', - fullWidth: true, - padding: 10, - position: 'top', - text: '', - weight: 2000 // by default greater than legend (1000) to be above - } -}); - -/** - * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required! - */ -var Title = core_element.extend({ - initialize: function(config) { - var me = this; - helpers$1.extend(me, config); - - // Contains hit boxes for each dataset (in dataset order) - me.legendHitBoxes = []; - }, - - // These methods are ordered by lifecycle. Utilities then follow. - - beforeUpdate: noop$2, - update: function(maxWidth, maxHeight, margins) { - var me = this; - - // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;) - me.beforeUpdate(); - - // Absorb the master measurements - me.maxWidth = maxWidth; - me.maxHeight = maxHeight; - me.margins = margins; - - // Dimensions - me.beforeSetDimensions(); - me.setDimensions(); - me.afterSetDimensions(); - // Labels - me.beforeBuildLabels(); - me.buildLabels(); - me.afterBuildLabels(); - - // Fit - me.beforeFit(); - me.fit(); - me.afterFit(); - // - me.afterUpdate(); - - return me.minSize; - - }, - afterUpdate: noop$2, - - // - - beforeSetDimensions: noop$2, - setDimensions: function() { - var me = this; - // Set the unconstrained dimension before label rotation - if (me.isHorizontal()) { - // Reset position before calculating rotation - me.width = me.maxWidth; - me.left = 0; - me.right = me.width; - } else { - me.height = me.maxHeight; - - // Reset position before calculating rotation - me.top = 0; - me.bottom = me.height; - } - - // Reset padding - me.paddingLeft = 0; - me.paddingTop = 0; - me.paddingRight = 0; - me.paddingBottom = 0; - - // Reset minSize - me.minSize = { - width: 0, - height: 0 - }; - }, - afterSetDimensions: noop$2, - - // - - beforeBuildLabels: noop$2, - buildLabels: noop$2, - afterBuildLabels: noop$2, - - // - - beforeFit: noop$2, - fit: function() { - var me = this; - var opts = me.options; - var minSize = me.minSize = {}; - var isHorizontal = me.isHorizontal(); - var lineCount, textSize; - - if (!opts.display) { - me.width = minSize.width = me.height = minSize.height = 0; - return; - } - - lineCount = helpers$1.isArray(opts.text) ? opts.text.length : 1; - textSize = lineCount * helpers$1.options._parseFont(opts).lineHeight + opts.padding * 2; - - me.width = minSize.width = isHorizontal ? me.maxWidth : textSize; - me.height = minSize.height = isHorizontal ? textSize : me.maxHeight; - }, - afterFit: noop$2, - - // Shared Methods - isHorizontal: function() { - var pos = this.options.position; - return pos === 'top' || pos === 'bottom'; - }, - - // Actually draw the title block on the canvas - draw: function() { - var me = this; - var ctx = me.ctx; - var opts = me.options; - - if (!opts.display) { - return; - } - - var fontOpts = helpers$1.options._parseFont(opts); - var lineHeight = fontOpts.lineHeight; - var offset = lineHeight / 2 + opts.padding; - var rotation = 0; - var top = me.top; - var left = me.left; - var bottom = me.bottom; - var right = me.right; - var maxWidth, titleX, titleY; - - ctx.fillStyle = helpers$1.valueOrDefault(opts.fontColor, core_defaults.global.defaultFontColor); // render in correct colour - ctx.font = fontOpts.string; - - // Horizontal - if (me.isHorizontal()) { - titleX = left + ((right - left) / 2); // midpoint of the width - titleY = top + offset; - maxWidth = right - left; - } else { - titleX = opts.position === 'left' ? left + offset : right - offset; - titleY = top + ((bottom - top) / 2); - maxWidth = bottom - top; - rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5); - } - - ctx.save(); - ctx.translate(titleX, titleY); - ctx.rotate(rotation); - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - - var text = opts.text; - if (helpers$1.isArray(text)) { - var y = 0; - for (var i = 0; i < text.length; ++i) { - ctx.fillText(text[i], 0, y, maxWidth); - y += lineHeight; - } - } else { - ctx.fillText(text, 0, 0, maxWidth); - } - - ctx.restore(); - } -}); - -function createNewTitleBlockAndAttach(chart, titleOpts) { - var title = new Title({ - ctx: chart.ctx, - options: titleOpts, - chart: chart - }); - - core_layouts.configure(chart, title, titleOpts); - core_layouts.addBox(chart, title); - chart.titleBlock = title; -} - -var plugin_title = { - id: 'title', - - /** - * Backward compatibility: since 2.1.5, the title is registered as a plugin, making - * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of - * the plugin, which one will be re-exposed in the chart.js file. - * https://github.com/chartjs/Chart.js/pull/2640 - * @private - */ - _element: Title, - - beforeInit: function(chart) { - var titleOpts = chart.options.title; - - if (titleOpts) { - createNewTitleBlockAndAttach(chart, titleOpts); - } - }, - - beforeUpdate: function(chart) { - var titleOpts = chart.options.title; - var titleBlock = chart.titleBlock; - - if (titleOpts) { - helpers$1.mergeIf(titleOpts, core_defaults.global.title); - - if (titleBlock) { - core_layouts.configure(chart, titleBlock, titleOpts); - titleBlock.options = titleOpts; - } else { - createNewTitleBlockAndAttach(chart, titleOpts); - } - } else if (titleBlock) { - core_layouts.removeBox(chart, titleBlock); - delete chart.titleBlock; - } - } -}; - -var plugins = {}; -var filler = plugin_filler; -var legend = plugin_legend; -var title = plugin_title; -plugins.filler = filler; -plugins.legend = legend; -plugins.title = title; - -/** - * @namespace Chart - */ - - -core_controller.helpers = helpers$1; - -// @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests! -core_helpers(); - -core_controller._adapters = core_adapters; -core_controller.Animation = core_animation; -core_controller.animationService = core_animations; -core_controller.controllers = controllers; -core_controller.DatasetController = core_datasetController; -core_controller.defaults = core_defaults; -core_controller.Element = core_element; -core_controller.elements = elements; -core_controller.Interaction = core_interaction; -core_controller.layouts = core_layouts; -core_controller.platform = platform; -core_controller.plugins = core_plugins; -core_controller.Scale = core_scale; -core_controller.scaleService = core_scaleService; -core_controller.Ticks = core_ticks; -core_controller.Tooltip = core_tooltip; - -// Register built-in scales - -core_controller.helpers.each(scales, function(scale, type) { - core_controller.scaleService.registerScaleType(type, scale, scale._defaults); -}); - -// Load to register built-in adapters (as side effects) - - -// Loading built-in plugins - -for (var k in plugins) { - if (plugins.hasOwnProperty(k)) { - core_controller.plugins.register(plugins[k]); - } -} - -core_controller.platform.initialize(); - -var src = core_controller; -if (typeof window !== 'undefined') { - window.Chart = core_controller; -} - -// DEPRECATIONS - -/** - * Provided for backward compatibility, not available anymore - * @namespace Chart.Chart - * @deprecated since version 2.8.0 - * @todo remove at version 3 - * @private - */ -core_controller.Chart = core_controller; - -/** - * Provided for backward compatibility, not available anymore - * @namespace Chart.Legend - * @deprecated since version 2.1.5 - * @todo remove at version 3 - * @private - */ -core_controller.Legend = plugins.legend._element; - -/** - * Provided for backward compatibility, not available anymore - * @namespace Chart.Title - * @deprecated since version 2.1.5 - * @todo remove at version 3 - * @private - */ -core_controller.Title = plugins.title._element; - -/** - * Provided for backward compatibility, use Chart.plugins instead - * @namespace Chart.pluginService - * @deprecated since version 2.1.5 - * @todo remove at version 3 - * @private - */ -core_controller.pluginService = core_controller.plugins; - -/** - * Provided for backward compatibility, inheriting from Chart.PlugingBase has no - * effect, instead simply create/register plugins via plain JavaScript objects. - * @interface Chart.PluginBase - * @deprecated since version 2.5.0 - * @todo remove at version 3 - * @private - */ -core_controller.PluginBase = core_controller.Element.extend({}); - -/** - * Provided for backward compatibility, use Chart.helpers.canvas instead. - * @namespace Chart.canvasHelpers - * @deprecated since version 2.6.0 - * @todo remove at version 3 - * @private - */ -core_controller.canvasHelpers = core_controller.helpers.canvas; - -/** - * Provided for backward compatibility, use Chart.layouts instead. - * @namespace Chart.layoutService - * @deprecated since version 2.7.3 - * @todo remove at version 3 - * @private - */ -core_controller.layoutService = core_controller.layouts; - -/** - * Provided for backward compatibility, not available anymore. - * @namespace Chart.LinearScaleBase - * @deprecated since version 2.8 - * @todo remove at version 3 - * @private - */ -core_controller.LinearScaleBase = scale_linearbase; - -/** - * Provided for backward compatibility, instead we should create a new Chart - * by setting the type in the config (`new Chart(id, {type: '{chart-type}'}`). - * @deprecated since version 2.8.0 - * @todo remove at version 3 - */ -core_controller.helpers.each( - [ - 'Bar', - 'Bubble', - 'Doughnut', - 'Line', - 'PolarArea', - 'Radar', - 'Scatter' - ], - function(klass) { - core_controller[klass] = function(ctx, cfg) { - return new core_controller(ctx, core_controller.helpers.merge(cfg || {}, { - type: klass.charAt(0).toLowerCase() + klass.slice(1) - })); - }; - } -); - -return src; - -}))); diff --git a/lib/web/chartjs/Chart.min.css b/lib/web/chartjs/Chart.min.css deleted file mode 100644 index 9dc5ac2e5faca..0000000000000 --- a/lib/web/chartjs/Chart.min.css +++ /dev/null @@ -1 +0,0 @@ -@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0} \ No newline at end of file From 43402b06dd021ed725a2a1a574735e3d1e7c17c3 Mon Sep 17 00:00:00 2001 From: Vadim Malesh <51680850+engcom-Charlie@users.noreply.github.com> Date: Mon, 2 Mar 2020 13:33:34 +0200 Subject: [PATCH 154/369] Update Rows.php fix static --- .../Catalog/Model/Indexer/Product/Flat/Action/Rows.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php index 8cafc82bf77d6..6c7b69b884f8d 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Rows.php @@ -5,14 +5,14 @@ */ namespace Magento\Catalog\Model\Indexer\Product\Flat\Action; +use Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction; use Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder; use Magento\Catalog\Model\Indexer\Product\Flat\TableBuilder; /** * Class Rows reindex action for mass actions - * */ -class Rows extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction +class Rows extends AbstractAction { /** * @var Eraser From 7afe256432592f069e1b8d39e4e0abf804d709fc Mon Sep 17 00:00:00 2001 From: Vadim Malesh <51680850+engcom-Charlie@users.noreply.github.com> Date: Mon, 2 Mar 2020 13:37:27 +0200 Subject: [PATCH 155/369] Update RowsTest.php test coverage --- .../Indexer/Product/Flat/Action/RowsTest.php | 110 +++++++++++++----- 1 file changed, 82 insertions(+), 28 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RowsTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RowsTest.php index baf0a0c1a9c4b..1252245a259c2 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RowsTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RowsTest.php @@ -3,63 +3,117 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Catalog\Model\Indexer\Product\Flat\Action; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Block\Product\ListProduct; +use Magento\Catalog\Model\Indexer\Product\Flat\Processor; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Indexer\TestCase; + /** - * Class RowsTest + * Test for \Magento\Catalog\Model\Indexer\Product\Flat\Action\Rows. */ -class RowsTest extends \Magento\TestFramework\Indexer\TestCase +class RowsTest extends TestCase { /** - * @var \Magento\Catalog\Model\Product + * @var Processor */ - protected $_product; + private $processor; /** - * @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor + * @var ProductRepositoryInterface */ - protected $_processor; + private $productRepository; + /** + * @var CategoryRepositoryInterface + */ + private $categoryRepository; + + /** + * @var CollectionFactory + */ + private $productCollectionFactory; + + /** + * @var LayoutInterface + */ + private $layout; + + /** + * @inheritdoc + */ protected function setUp() { - $this->_product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\Product::class - ); - $this->_processor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Catalog\Model\Indexer\Product\Flat\Processor::class - ); + $objectManager = Bootstrap::getObjectManager(); + $this->processor = $objectManager->get(Processor::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->categoryRepository = $objectManager->get(CategoryRepositoryInterface::class); + $this->productCollectionFactory = $objectManager->get(CollectionFactory::class); + $this->layout = $objectManager->get(LayoutInterface::class); } /** + * Test update category products + * * @magentoDbIsolation disabled * @magentoAppIsolation enabled * @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1 * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * + * @return void */ - public function testProductsUpdate() + public function testProductsUpdate(): void { - $this->_product->load(1); - - $this->_processor->reindexList([$this->_product->getId()]); - - $categoryFactory = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Catalog\Model\CategoryFactory::class - ); - $listProduct = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Catalog\Block\Product\ListProduct::class - ); + $product = $this->productRepository->getById(1); + $this->processor->reindexList([$product->getId()]); - $category = $categoryFactory->create()->load(2); + $category = $this->categoryRepository->get(2); + $listProduct = $this->layout->createBlock(ListProduct::class); $layer = $listProduct->getLayer(); $layer->setCurrentCategory($category); $productCollection = $layer->getProductCollection(); $this->assertCount(1, $productCollection); - /** @var $product \Magento\Catalog\Model\Product */ - foreach ($productCollection as $product) { - $this->assertEquals($this->_product->getName(), $product->getName()); - $this->assertEquals($this->_product->getShortDescription(), $product->getShortDescription()); + /** @var $productItem Product */ + foreach ($productCollection as $productItem) { + $this->assertEquals($product->getName(), $productItem->getName()); + $this->assertEquals($product->getShortDescription(), $productItem->getShortDescription()); } } + + /** + * Products update with different statuses + * + * @magentoDbIsolation disabled + * @magentoAppArea frontend + * @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1 + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_attribute_in_flat.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_duplicated.php + * + * @return void + */ + public function testProductsDifferentStatusesUpdate(): void + { + $firstProduct = $this->productRepository->get('simple_with_custom_flat_attribute'); + $secondProduct = $this->productRepository->get('simple-1'); + + $this->processor->getIndexer()->setScheduled(true); + $this->productRepository->save($secondProduct->setStatus(Status::STATUS_DISABLED)); + $this->processor->reindexList([$firstProduct->getId(), $secondProduct->getId()], true); + $collection = $this->productCollectionFactory->create(); + + $this->assertCount(1, $collection); + $this->assertEquals($firstProduct->getId(), $collection->getFirstItem()->getId()); + } } From a2cceb2490814a065cbdf1e972143a123fce6971 Mon Sep 17 00:00:00 2001 From: Viktor Sevch <svitja@ukr.net> Date: Mon, 2 Mar 2020 14:25:37 +0200 Subject: [PATCH 156/369] MC-29052: Magento\FunctionalTestingFramework.functional.AdminTaxReportGridTest fails randomly --- .../Mftf/Page/AdminSalesTaxReportPage.xml | 11 + .../Section/AdminOrderItemsOrderedSection.xml | 1 + .../AdminSelectProductTaxClassActionGroup.xml | 23 ++ .../AdminSelectTaxRateActionGroup.xml | 24 ++ ...dminUnassignProductTaxClassActionGroup.xml | 23 ++ .../Tax/Test/Mftf/Data/TaxRateData.xml | 11 + .../Tax/Test/Mftf/Metadata/tax_rate-meta.xml | 4 +- .../Section/AdminOrderFormTotalSection.xml | 14 ++ .../Mftf/Section/AdminTaxReportsSection.xml | 1 + .../Mftf/Section/AdminTaxRuleFormSection.xml | 1 + .../Test/AdminCheckingTaxReportGridTest.xml | 207 ++++++++++++++++++ .../Test/Mftf/Test/AdminTaxReportGridTest.xml | 4 +- 12 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 app/code/Magento/Reports/Test/Mftf/Page/AdminSalesTaxReportPage.xml create mode 100644 app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminSelectProductTaxClassActionGroup.xml create mode 100644 app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminSelectTaxRateActionGroup.xml create mode 100644 app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminUnassignProductTaxClassActionGroup.xml create mode 100644 app/code/Magento/Tax/Test/Mftf/Section/AdminOrderFormTotalSection.xml create mode 100644 app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml diff --git a/app/code/Magento/Reports/Test/Mftf/Page/AdminSalesTaxReportPage.xml b/app/code/Magento/Reports/Test/Mftf/Page/AdminSalesTaxReportPage.xml new file mode 100644 index 0000000000000..db60433830001 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Page/AdminSalesTaxReportPage.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminSalesTaxReportPage" url="reports/report_sales/tax/" area="admin" module="Magento_Reports"> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml index a2c82de60a78e..94af3c79c8e02 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml @@ -20,6 +20,7 @@ <element name="itemTaxPercent" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-tax-percent" parameterized="true"/> <element name="itemDiscountAmount" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-discont .price" parameterized="true"/> <element name="itemTotal" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-total .price" parameterized="true"/> + <element name="itemTaxAmountByProductName" type="text" selector="//table[contains(@class,'edit-order-table')]//div[contains(text(),'{{productName}}')]/ancestor::tr//td[contains(@class, 'col-tax-amount')]//span" parameterized="true"/> <element name="productNameColumn" type="text" selector=".edit-order-table .col-product .product-title"/> <element name="productNameOptions" type="text" selector=".edit-order-table .col-product .item-options"/> diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminSelectProductTaxClassActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminSelectProductTaxClassActionGroup.xml new file mode 100644 index 0000000000000..c59c710a7500d --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminSelectProductTaxClassActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSelectProductTaxClassActionGroup"> + <annotations> + <description>Select "Product Tax Class" in tax rule edit form.</description> + </annotations> + <arguments> + <argument name="taxClass" type="string" defaultValue="{{productTaxClass.class_name}}"/> + </arguments> + + <conditionalClick selector="{{AdminTaxRuleFormSection.additionalSettings}}" dependentSelector="{{AdminTaxRuleFormSection.additionalSettingsOpened}}" visible="false" stepKey="openAdditionalSettings"/> + <waitForElementVisible selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxClass)}}" stepKey="waitForVisibleTaxClass"/> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxClass)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxClass)}}" visible="false" stepKey="assignProdTaxClass"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminSelectTaxRateActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminSelectTaxRateActionGroup.xml new file mode 100644 index 0000000000000..6fc6ecc6dbcdf --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminSelectTaxRateActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSelectTaxRateActionGroup"> + <annotations> + <description>Select "Tax Rate" in tax rule edit form.</description> + </annotations> + <arguments> + <argument name="taxRate" type="string" defaultValue="{{TaxRateTexas.code}}"/> + </arguments> + + <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="{{taxRate}}" stepKey="searchTaxRate"/> + <waitForPageLoad time="30" stepKey="waitForAjaxLoad"/> + <waitForElementVisible selector="{{AdminTaxRuleFormSection.taxRateOption(taxRate)}}" time="30" stepKey="waitForVisibleTaxRate" /> + <click selector="{{AdminTaxRuleFormSection.taxRateOption(taxRate)}}" stepKey="clickTaxRate"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminUnassignProductTaxClassActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminUnassignProductTaxClassActionGroup.xml new file mode 100644 index 0000000000000..bda604ffa5da0 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminUnassignProductTaxClassActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminUnassignProductTaxClassActionGroup"> + <annotations> + <description>Admin unassign "Product Tax Class" in tax rule edit form</description> + </annotations> + <arguments> + <argument name="taxClass" type="string" defaultValue="{{productTaxClass.class_name}}"/> + </arguments> + + <conditionalClick selector="{{AdminTaxRuleFormSection.additionalSettings}}" dependentSelector="{{AdminTaxRuleFormSection.additionalSettingsOpened}}" visible="false" stepKey="openAdditionalSettings"/> + <waitForElementVisible selector="{{AdminProductTaxClassSection.productTaxClass}}" stepKey="waitForAddProductTaxClassButton"/> + <conditionalClick selector="{{AdminTaxRuleFormSection.productTaxClassOption(taxClass)}}" dependentSelector="{{AdminTaxRuleFormSection.productTaxClassSelected(taxClass)}}" visible="true" stepKey="unSelectTaxClass"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml index 20d05e1f572c2..80b158d116aa7 100644 --- a/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml @@ -127,4 +127,15 @@ <data key="zip_is_range">0</data> <data key="rate">100.0000</data> </entity> + <entity name="TaxRateTexas" type="taxRate"> + <data key="code" unique="suffix">Tax Rate </data> + <data key="tax_region">Texas</data> + <data key="tax_country_id">US</data> + <data key="tax_country">United States</data> + <data key="tax_postcode">78729</data> + <data key="rate">7.25</data> + </entity> + <entity name="SecondTaxRateTexas" extends="TaxRateTexas"> + <data key="rate">0.125</data> + </entity> </entities> diff --git a/app/code/Magento/Tax/Test/Mftf/Metadata/tax_rate-meta.xml b/app/code/Magento/Tax/Test/Mftf/Metadata/tax_rate-meta.xml index 3f192920c5cc3..6236a6d6c627e 100644 --- a/app/code/Magento/Tax/Test/Mftf/Metadata/tax_rate-meta.xml +++ b/app/code/Magento/Tax/Test/Mftf/Metadata/tax_rate-meta.xml @@ -17,11 +17,11 @@ <field key="zip_is_range">integer</field> <field key="zip_from">integer</field> <field key="zip_to">integer</field> - <field key="rate">integer</field> + <field key="rate">number</field> <field key="code">string</field> </object> </operation> <operation name="DeleteTaxRate" dataType="taxRate" type="delete" auth="adminOauth" url="/V1/taxRates/{id}" method="DELETE"> <contentType>application/json</contentType> </operation> -</operations> \ No newline at end of file +</operations> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminOrderFormTotalSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminOrderFormTotalSection.xml new file mode 100644 index 0000000000000..9b033bb375eb2 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminOrderFormTotalSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminOrderFormTotalSection"> + <element name="totalTax" type="text" selector="//table[contains(@class, 'order-subtotal-table')]/tbody/tr/td/div[contains(text(), 'Tax')]/ancestor::tr/td/span[contains(@class, 'price')]"/> + </section> +</sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml index 71bc4cbceff83..3cfa8206a089c 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxReportsSection.xml @@ -11,6 +11,7 @@ <section name="AdminTaxReportsSection"> <element name="refreshStatistics" type="button" selector="//a[contains(text(),'here')]"/> <element name="fromDate" type="input" selector="#sales_report_from"/> + <element name="toDateInput" type="input" selector="#sales_report_to"/> <element name="toDate" type="input" selector="//*[@id='sales_report_to']/following-sibling::button"/> <element name="goTodayButton" type="input" selector="//button[contains(text(),'Go Today')]"/> <element name="showReportButton" type="button" selector="#filter_form_submit"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleFormSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleFormSection.xml index c77d3ad0d9444..a7e5826454ac6 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleFormSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRuleFormSection.xml @@ -19,6 +19,7 @@ <element name="deleteRule" type="button" selector="#delete" timeout="30"/> <element name="ok" type="button" selector="button.action-primary.action-accept" timeout="30"/> <element name="additionalSettings" type="button" selector="#details-summarybase_fieldset" timeout="30"/> + <element name="additionalSettingsOpened" type="button" selector="#details-summarybase_fieldset[aria-expanded=true]"/> <element name="customerTaxClassOption" type="checkbox" selector="//*[@id='tax_customer_class']/..//span[.='{{taxCustomerClass}}']" parameterized="true"/> <element name="productTaxClassOption" type="checkbox" selector="//*[@id='tax_product_class']/..//span[.='{{taxProductClass}}']" parameterized="true"/> <element name="customerTaxClassSelected" type="checkbox" selector="//*[@id='tax_customer_class']/..//span[.='{{taxCustomerClass}}' and preceding-sibling::input[contains(@class, 'mselect-checked')]]" parameterized="true"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml new file mode 100644 index 0000000000000..9b1a0ca2a9892 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml @@ -0,0 +1,207 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckingTaxReportGridTest"> + <annotations> + <features value="Tax"/> + <stories value="Tax Report Grid"/> + <title value="Checking Tax Report grid"/> + <description value="Tax Report Grid displays Tax amount in rows 'Total' and 'Subtotal' is a sum of all tax amounts"/> + <severity value="MAJOR"/> + <testCaseId value="MC-6230"/> + <useCaseId value="MAGETWO-91521"/> + <group value="Tax"/> + </annotations> + <before> + <!-- Create category and product --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createFirstProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="_defaultProduct" stepKey="createSecondProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create Tax Rule and Tax Rate --> + <createData entity="SimpleTaxRule" stepKey="createTaxRule"/> + <createData entity="SimpleTaxRule" stepKey="createSecondTaxRule"/> + <createData entity="TaxRateTexas" stepKey="createTaxRate"/> + <createData entity="SecondTaxRateTexas" stepKey="createSecondTaxRate"/> + + <!-- Create product tax class --> + <createData entity="productTaxClass" stepKey="createProductTaxClass"/> + <getData entity="productTaxClass" stepKey="productTaxClass"> + <requiredEntity createDataKey="createProductTaxClass"/> + </getData> + <createData entity="productTaxClass" stepKey="createSecondProductTaxClass"/> + <getData entity="productTaxClass" stepKey="productSecondTaxClass"> + <requiredEntity createDataKey="createSecondProductTaxClass"/> + </getData> + + <!-- Login to Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Go to Tax Rule page, add Tax Rate, unassign Default Tax Rate --> + <amOnPage url="{{AdminEditTaxRulePage.url($createTaxRule.id$)}}" stepKey="goToTaxRulePage"/> + <waitForPageLoad stepKey="waitForTaxRulePage"/> + <actionGroup ref="AdminSelectTaxRateActionGroup" stepKey="assignTaxRate"> + <argument name="taxRate" value="$createTaxRate.code$"/> + </actionGroup> + + <!-- Assign Product Tax Class and Unassign Default Product Tax Class --> + <actionGroup ref="AdminSelectProductTaxClassActionGroup" stepKey="assignProductTaxClass"> + <argument name="taxClass" value="$productTaxClass.class_name$"/> + </actionGroup> + <actionGroup ref="AdminUnassignProductTaxClassActionGroup" stepKey="unSelectTaxRuleDefaultProductTax"> + <argument name="taxClass" value="{{taxableGoodsTaxClass.class_name}}"/> + </actionGroup> + + <!-- Save Tax Rule --> + <actionGroup ref="ClickSaveButtonActionGroup" stepKey="saveTaxRule"> + <argument name="message" value="You saved the tax rule."/> + </actionGroup> + + <!-- Go to Tax Rule page to create second Tax Rule, add Tax Rate, unassign Default Tax Rate --> + <amOnPage url="{{AdminEditTaxRulePage.url($createSecondTaxRule.id$)}}" stepKey="goToSecondTaxRulePage"/> + <waitForPageLoad stepKey="waitForSecondTaxRatePage"/> + <actionGroup ref="AdminSelectTaxRateActionGroup" stepKey="assignSecondTaxRate"> + <argument name="taxRate" value="$createSecondTaxRate.code$"/> + </actionGroup> + <!-- Assign Product Tax Class and Unassign Default Product Tax Class --> + <actionGroup ref="AdminSelectProductTaxClassActionGroup" stepKey="assignSecondProductTaxClass"> + <argument name="taxClass" value="$productSecondTaxClass.class_name$"/> + </actionGroup> + <actionGroup ref="AdminUnassignProductTaxClassActionGroup" stepKey="unaSelectTaxRuleDefaultSecondProductTaxClass"> + <argument name="taxClass" value="{{taxableGoodsTaxClass.class_name}}"/> + </actionGroup> + + <!-- Save Tax Rule --> + <actionGroup ref="ClickSaveButtonActionGroup" stepKey="saveSecondTaxRule"> + <argument name="message" value="You saved the tax rule."/> + </actionGroup> + </before> + <after> + <!-- Delete product and category --> + <deleteData createDataKey="createFirstProduct" stepKey="deleteFirstProduct"/> + <deleteData createDataKey="createSecondProduct" stepKey="deleteSecondProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!--Delete Tax Rule --> + <deleteData createDataKey="createTaxRule" stepKey="deleteRule"/> + <deleteData createDataKey="createSecondTaxRule" stepKey="deleteSecondRule"/> + + <!-- Delete Tax Rate --> + <deleteData createDataKey="createTaxRate" stepKey="deleteTaxRate"/> + <deleteData createDataKey="createSecondTaxRate" stepKey="deleteSecondTaxRate"/> + + <!-- Delete Product Tax Class --> + <deleteData createDataKey="createProductTaxClass" stepKey="deleteProductTaxClass"/> + <deleteData createDataKey="createSecondProductTaxClass" stepKey="deleteSecondProductTaxClass"/> + + <!-- Clear filter Product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearFilterProduct"/> + + <!-- Delete Customer and clear filter --> + <actionGroup ref="DeleteCustomerByEmailActionGroup" stepKey="deleteCustomer"> + <argument name="email" value="{{Simple_US_Customer.email}}"/> + </actionGroup> + <actionGroup ref="AssertMessageInAdminPanelActionGroup" stepKey="assertSuccessMessage"> + <argument name="message" value="A total of 1 record(s) were deleted."/> + </actionGroup> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearFilterCustomer"/> + + <!-- Logout Admin --> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <!--Open Created product. In Tax Class select new created Product Tax class.--> + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="openProductForEdit"> + <argument name="productId" value="$createFirstProduct.id$"/> + </actionGroup> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="$productTaxClass.class_name$" stepKey="selectTexClassForProduct"/> + <!-- Save the second product --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> + + <!--Open Created Second Product. In Tax Class select new created Product Tax class.--> + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="openSecondProductForEdit"> + <argument name="productId" value="$createSecondProduct.id$"/> + </actionGroup> + + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="$productSecondTaxClass.class_name$" stepKey="selectTexClassForSecondProduct"/> + + <!-- Save the second product --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveSecondProduct"/> + + <!--Create an order with these 2 products in that zip code.--> + <actionGroup ref="NavigateToNewOrderPageNewCustomerActionGroup" stepKey="navigateToNewOrder"/> + <!--Check if order can be submitted without the required fields including email address--> + <scrollToTopOfPage stepKey="scrollToTopOfOrderFormPage"/> + <waitForElementVisible selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="waitForAddProductButton"/> + <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addFirstProductToOrder"> + <argument name="product" value="$createFirstProduct$"/> + </actionGroup> + <waitForElementVisible selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="waitForAddProductButtonAfterOneProductIsAdded"/> + <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addSecondProductToOrder"> + <argument name="product" value="$createSecondProduct$"/> + </actionGroup> + + <!--Fill customer group and customer email--> + <selectOption selector="{{AdminOrderFormAccountSection.group}}" userInput="{{GeneralCustomerGroup.code}}" stepKey="selectCustomerGroup"/> + <fillField selector="{{AdminOrderFormAccountSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillCustomerEmail"/> + + <!--Fill customer address information--> + <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerAddress"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="address" value="US_Address_TX"/> + </actionGroup> + + <!-- Select shipping --> + <actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="selectFlatRateShipping"/> + <!-- Checkout select Check/Money Order payment --> + <actionGroup ref="SelectCheckMoneyPaymentMethodActionGroup" stepKey="selectCheckMoneyPayment"/> + <!--Submit Order and verify information--> + <actionGroup ref="AdminSubmitOrderActionGroup" stepKey="submitOrder"/> + + <!-- Grab tax amounts --> + <!-- need check selector --> + <grabTextFrom selector="{{AdminOrderItemsOrderedSection.itemTaxAmountByProductName($createFirstProduct.name$)}}" stepKey="amountOfTaxOnFirstProduct"/> + <grabTextFrom selector="{{AdminOrderItemsOrderedSection.itemTaxAmountByProductName($createSecondProduct.name$)}}" stepKey="amountOfTaxOnSecondProduct"/> + <grabTextFrom selector="{{AdminOrderFormTotalSection.totalTax}}" stepKey="amountOfTotalTax"/> + + <!--Create Invoice and Shipment for this Order.--> + <actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startCreatingInvoice"/> + + <actionGroup ref="SubmitInvoiceActionGroup" stepKey="clickSubmitInvoice"/> + + <actionGroup ref="goToShipmentIntoOrder" stepKey="seeShipmentOrderPage"/> + <!--Submit Shipment--> + <actionGroup ref="submitShipmentIntoOrder" stepKey="clickSubmitShipment"/> + <!--Go to "Reports" -> "Sales" -> "Tax"--> + <amOnPage url="{{AdminSalesTaxReportPage.url}}" stepKey="navigateToReportsTaxPage"/> + <waitForPageLoad stepKey="waitForReportsTaxPageLoad"/> + + <!--click "here" to refresh last day's statistics --> + <click selector="{{AdminTaxReportsSection.refreshStatistics}}" stepKey="clickRefreshStatistics"/> + <waitForPageLoad time="30" stepKey="waitForRefresh"/> + + <!--Select Dates--> + <generateDate date="+0 day" format="m/d/Y" stepKey="today"/> + <fillField selector="{{AdminTaxReportsSection.fromDate}}" userInput="{$today}" stepKey="fillDateFrom"/> + <fillField selector="{{AdminTaxReportsSection.toDateInput}}" userInput="{$today}" stepKey="fillDateTo"/> + <!--Click "Show report" in the upper right corner.--> + <click selector="{{AdminTaxReportsSection.showReportButton}}" stepKey="clickShowReportButton"/> + <waitForPageLoad time="60" stepKey="waitForReload"/> + <!--Tax Report Grid displays Tax amount in rows. "Total" and "Subtotal" is a sum of all tax amounts--> + <see selector="{{AdminTaxReportsSection.taxRuleAmount(TaxRateTexas.code)}}" userInput="$amountOfTaxOnFirstProduct" stepKey="assertSubtotalFirstField"/> + <see selector="{{AdminTaxReportsSection.taxRuleAmount(SecondTaxRateTexas.code)}}" userInput="$amountOfTaxOnSecondProduct" stepKey="assertSubtotalSecondField"/> + <see selector="{{AdminTaxReportsSection.taxRuleAmount('Subtotal')}}" userInput="$amountOfTotalTax" stepKey="assertSubtotalField"/> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml index 1611704c43334..1c23f455d0ad0 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml @@ -12,13 +12,13 @@ <annotations> <features value="Tax"/> <stories value="MAGETWO-91521: Reports / Sales / Tax report show incorrect amount"/> - <title value="Checking Tax Report grid"/> + <title value="DEPRECATED Checking Tax Report grid"/> <description value="Tax Report Grid displays Tax amount in rows 'Total' and 'Subtotal' is a sum of all tax amounts"/> <severity value="MAJOR"/> <testCaseId value="MAGETWO-94338"/> <group value="Tax"/> <skip> - <issueId value="MAGETWO-96193"/> + <issueId value="DEPRECATED">Use AdminCheckingTaxReportGridTest instead.</issueId> </skip> </annotations> <before> From 854761db86de5a2bb960fc66c0b9922dd792f423 Mon Sep 17 00:00:00 2001 From: Michael Bottens <michael.bottens@blueacorn.com> Date: Mon, 2 Mar 2020 11:45:45 -0500 Subject: [PATCH 157/369] #27124: Update wishlist image logic to match logic on wishlist page --- .../Wishlist/Block/Share/Email/Items.php | 40 +++++++++++++++++++ .../view/frontend/templates/email/items.phtml | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Wishlist/Block/Share/Email/Items.php b/app/code/Magento/Wishlist/Block/Share/Email/Items.php index d4e6587fd6519..0887951d180aa 100644 --- a/app/code/Magento/Wishlist/Block/Share/Email/Items.php +++ b/app/code/Magento/Wishlist/Block/Share/Email/Items.php @@ -11,17 +11,57 @@ */ namespace Magento\Wishlist\Block\Share\Email; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; +use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Framework\View\ConfigInterface; +use Magento\Wishlist\Model\Item; + /** * @api * @since 100.0.2 */ class Items extends \Magento\Wishlist\Block\AbstractBlock { + /** @var ItemResolverInterface */ + private $itemResolver; + /** * @var string */ protected $_template = 'Magento_Wishlist::email/items.phtml'; + /** + * @param \Magento\Catalog\Block\Product\Context $context + * @param \Magento\Framework\App\Http\Context $httpContext + * @param ItemResolverInterface $itemResolver + * @param array $data + * @param ConfigInterface|null $config + * @param UrlBuilder|null $urlBuilder + */ + public function __construct( + \Magento\Catalog\Block\Product\Context $context, + \Magento\Framework\App\Http\Context $httpContext, + ItemResolverInterface $itemResolver, + array $data = [], + ConfigInterface $config = null, + UrlBuilder $urlBuilder = null + ) { + $this->itemResolver = $itemResolver; + parent::__construct($context, $httpContext, $data, $config, $urlBuilder); + } + + /** + * Identify the product from which thumbnail should be taken. + * + * @param Item $item + * @return Product + */ + public function getProductForThumbnail(Item $item) : Product + { + return $this->itemResolver->getFinalProduct($item); + } + /** * Retrieve Product View URL * diff --git a/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml b/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml index 449d4c43c5aa0..a7cfabc7da35f 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml @@ -18,7 +18,7 @@ <td class="col product"> <p> <a href="<?= $block->escapeUrl($block->getProductUrl($_product)) ?>"> - <?= /* @noEscape */ $block->getImage($_product, 'product_small_image')->toHtml() ?> + <?= /* @noEscape */ $block->getImage($block->getProductForThumbnail($item), 'product_small_image')->toHtml() ?> </a> </p> From c98388a50f696af27534d4532d616c663e54de52 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Mon, 2 Mar 2020 15:37:48 -0600 Subject: [PATCH 158/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Test stabilization --- .../Test/AdminExportImportConfigurableProductWithImagesTest.xml | 2 +- .../AdminCatalogPriceRuleSaveAndApplyActionGroup.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml index bac667adaebc9..1ba1abc78a8fb 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -134,7 +134,6 @@ <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> <!-- Delete created data --> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFisrtSimpleProduct"/> <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> @@ -147,6 +146,7 @@ <deleteData createDataKey="createConfigurableProduct" stepKey="deleteConfigurableProduct"/> <deleteData createDataKey="createConfigChildProduct" stepKey="deleteConfigChildProduct"/> <deleteData createDataKey="createConfigProductAttr" stepKey="deleteConfigProductAttr"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGridColumnsInitial"/> <!-- Admin logout--> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSaveAndApplyActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSaveAndApplyActionGroup.xml index 84cc7b862ef7c..12a3cad59a606 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSaveAndApplyActionGroup.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleSaveAndApplyActionGroup.xml @@ -13,6 +13,7 @@ </annotations> <scrollToTopOfPage stepKey="scrollToTop"/> + <waitForElementVisible selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="waitForSaveAndApplyButton"/> <click selector="{{AdminNewCatalogPriceRule.saveAndApply}}" stepKey="saveAndApplyRule"/> <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessageAppears"/> <see selector="{{AdminMessagesSection.success}}" userInput="You saved the rule." stepKey="checkSuccessSaveMessage"/> From 21afe21644fe586b83654344d56b6651a3279873 Mon Sep 17 00:00:00 2001 From: Vaha <vaha@atwix.com> Date: Wed, 26 Feb 2020 14:53:21 +0200 Subject: [PATCH 159/369] improved event handlers for prompt widget --- ...nsertEditImageTinyMCEButtonActionGroup.xml | 19 +++++++ ...CreateImageFolderByEnterKeyActionGroup.xml | 18 +++++++ .../ActionGroup/DeleteFolderActionGroup.xml | 28 ++++++++++ .../PressEscImageFolderActionGroup.xml | 27 ++++++++++ .../Cms/Test/Mftf/Section/TinyMCESection.xml | 1 + ...capeAndEnterHandlesForWYSIWYGBlockTest.xml | 53 +++++++++++++++++++ .../Ui/view/base/web/js/modal/prompt.js | 38 +++++++++++++ 7 files changed, 184 insertions(+) create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/ClickInsertEditImageTinyMCEButtonActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateImageFolderByEnterKeyActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/DeleteFolderActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/PressEscImageFolderActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/Test/AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest.xml diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClickInsertEditImageTinyMCEButtonActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClickInsertEditImageTinyMCEButtonActionGroup.xml new file mode 100644 index 0000000000000..1685898743596 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClickInsertEditImageTinyMCEButtonActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ClickInsertEditImageTinyMCEButtonActionGroup"> + <annotations> + <description>Clicks on the 'Insert/edit image' TinyMCE button.</description> + </annotations> + + <click selector="{{TinyMCESection.InsertImageIcon}}" stepKey="clickInsertImageBtn" /> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateImageFolderByEnterKeyActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateImageFolderByEnterKeyActionGroup.xml new file mode 100644 index 0000000000000..5d8138b9c9cc7 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateImageFolderByEnterKeyActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CreateImageFolderByEnterKeyActionGroup" extends="CreateImageFolderActionGroup"> + <annotations> + <description>Creates a folder (by enter key) in the Media Gallery based on the provided Folder.</description> + </annotations> + + <pressKey selector="{{MediaGallerySection.FolderName}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="acceptFolderName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeleteFolderActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeleteFolderActionGroup.xml new file mode 100644 index 0000000000000..4c33b3d8ce35d --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/DeleteFolderActionGroup.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="DeleteFolderActionGroup"> + <annotations> + <description>Deletes the provided folder by name from the Media Gallery.</description> + </annotations> + <arguments> + <argument name="ImageFolder" defaultValue="ImageFolder"/> + </arguments> + + <click userInput="{{ImageFolder.name}}" stepKey="clickOnCreatedFolder"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoading"/> + <see selector="{{MediaGallerySection.DeleteFolder}}" userInput="Delete Folder" stepKey="seeDeleteFolderBtn"/> + <click selector="{{MediaGallerySection.DeleteFolder}}" stepKey="clickDeleteFolderBtn"/> + <waitForText userInput="OK" stepKey="waitForConfirm"/> + <click selector="{{MediaGallerySection.confirmDelete}}" stepKey="confirmDelete"/> + <waitForPageLoad stepKey="waitForPopUpHide"/> + <dontSeeElement selector="{{ImageFolder.name}}" stepKey="dontSeeFolderName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/PressEscImageFolderActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/PressEscImageFolderActionGroup.xml new file mode 100644 index 0000000000000..85f83193e64e3 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/PressEscImageFolderActionGroup.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="PressEscImageFolderActionGroup"> + <annotations> + <description>Opens the 'create folder' modal, fills 'folder name' input with provided folder name, + presses escape key to cancel folder creation (close modal).</description> + </annotations> + <arguments> + <argument name="ImageFolder" defaultValue="ImageFolder"/> + </arguments> + + <click selector="{{MediaGallerySection.CreateFolder}}" stepKey="createFolder"/> + <waitForElementVisible selector="{{MediaGallerySection.FolderName}}" stepKey="waitForPopUp"/> + <fillField selector="{{MediaGallerySection.FolderName}}" userInput="{{ImageFolder.name}}" stepKey="fillFolderName"/> + <pressKey selector="{{MediaGallerySection.FolderName}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ESCAPE]" stepKey="cancelFolderName"/> + <waitForPageLoad stepKey="waitForPopUpHide"/> + <dontSeeElement selector="{{ImageFolder.name}}" stepKey="dontSeeFolderName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml index b85c7554b58ae..aebed8c9efec0 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml @@ -51,6 +51,7 @@ <element name="insertBtn" type="button" selector="#insert"/> <element name="InsertFile" type="text" selector="#insert_files"/> <element name="CreateFolder" type="button" selector="#new_folder" /> + <element name="DeleteFolder" type="button" selector="#delete_folder" /> <element name="DeleteSelectedBtn" type="text" selector="#delete_files"/> <element name="CancelBtn" type="button" selector="#cancel" /> <element name="FolderName" type="button" selector="input[data-role='promptField']" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest.xml new file mode 100644 index 0000000000000..8114310e46f73 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckCreateFolderEscapeAndEnterHandlesForWYSIWYGBlockTest"> + <annotations> + <features value="Cms"/> + <stories value="WYSIWYG toolbar configuration with Magento Media Gallery"/> + <group value="Cms"/> + <title value="Admin should be able to cancel and close 'create folder' modal window using ESC key and + to add image to new folder (using enter key) for WYSIWYG content of Block"/> + <description value="Admin should be able to cancel and close 'create folder' modal window using ESC key and + to add image to new folder (using enter key) for WYSIWYG content of Block"/> + </annotations> + + <before> + <createData entity="_defaultBlock" stepKey="createPreReqBlock" /> + <actionGroup ref="LoginActionGroup" stepKey="login"/> + <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> + <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> + </before> + + <after> + <deleteData createDataKey="createPreReqBlock" stepKey="deletePreReqBlock" /> + <actionGroup ref="DisabledWYSIWYGActionGroup" stepKey="disableWYSIWYG"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="NavigateToCreatedCMSBlockPageActionGroup" stepKey="navigateToCreatedCMSBlockPage"> + <argument name="CMSBlockPage" value="$$createPreReqBlock$$"/> + </actionGroup> + <actionGroup ref="ClickInsertEditImageTinyMCEButtonActionGroup" stepKey="clickInsertImageIcon"/> + <actionGroup ref="ClickBrowseBtnOnUploadPopupActionGroup" stepKey="clickBrowserBtn"/> + <actionGroup ref="VerifyMediaGalleryStorageActionsActionGroup" stepKey="VerifyMediaGalleryStorageBtn"/> + + <actionGroup ref="CreateImageFolderByEnterKeyActionGroup" stepKey="CreateImageFolderByEnterKeyPress"> + <argument name="ImageFolder" value="ImageFolder"/> + </actionGroup> + + <actionGroup ref="DeleteFolderActionGroup" stepKey="DeleteCreatedFolder"> + <argument name="ImageFolder" value="ImageFolder"/> + </actionGroup> + + <actionGroup ref="PressEscImageFolderActionGroup" stepKey="CancelImageFolderCreation"> + <argument name="ImageFolder" value="ImageFolder"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Ui/view/base/web/js/modal/prompt.js b/app/code/Magento/Ui/view/base/web/js/modal/prompt.js index 13b4d55ea2787..443d35f1b0ded 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/prompt.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/prompt.js @@ -27,6 +27,44 @@ define([ value: '', validation: false, validationRules: [], + keyEventHandlers: { + + /** + * Enter key press handler, + * submit result and close modal window + * @param {Object} event - event + */ + enterKey: function (event) { + if (this.options.isOpen && this.modal.find(document.activeElement).length || + this.options.isOpen && this.modal[0] === document.activeElement) { + this.closeModal(true); + event.preventDefault(); + } + }, + + /** + * Tab key press handler, + * set focus to elements + */ + tabKey: function () { + if (document.activeElement === this.modal[0]) { + this._setFocus('start'); + } + }, + + /** + * Escape key press handler, + * cancel and close modal window + * @param {Object} event - event + */ + escapeKey: function (event) { + if (this.options.isOpen && this.modal.find(document.activeElement).length || + this.options.isOpen && this.modal[0] === document.activeElement) { + this.closeModal(); + event.preventDefault(); + } + } + }, actions: { /** From 945c427b17f9a7199982b10efe73c5f53f1d18cd Mon Sep 17 00:00:00 2001 From: Adarsh Manickam <amanickam@ztech.io> Date: Tue, 3 Mar 2020 16:34:23 +0530 Subject: [PATCH 160/369] Removed unnecessary tabindex property --- app/code/Magento/Ui/view/base/web/templates/grid/masonry.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/masonry.html b/app/code/Magento/Ui/view/base/web/templates/grid/masonry.html index 788cb0c2b5e56..089ee21bec15c 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/masonry.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/masonry.html @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ --> -<div data-role="grid-wrapper" class="masonry-image-grid" attr="'data-id': containerId" tabindex="0"> +<div data-role="grid-wrapper" class="masonry-image-grid" attr="'data-id': containerId"> <div class="masonry-image-column" repeat="foreach: rows, item: '$row'"> <div outerfasteach="data: getVisible(), as: '$col'" template="getBody()"/> </div> From 4b069f6772badc952dc990c98df607184aa003ce Mon Sep 17 00:00:00 2001 From: Vadim Malesh <51680850+engcom-Charlie@users.noreply.github.com> Date: Tue, 3 Mar 2020 13:56:06 +0200 Subject: [PATCH 161/369] Update app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php --- .../Block/Adminhtml/System/Config/CollectionTimeLabel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php index 62ef86c7dafb5..664278debd655 100644 --- a/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php +++ b/app/code/Magento/Analytics/Block/Adminhtml/System/Config/CollectionTimeLabel.php @@ -43,7 +43,7 @@ public function __construct( * * @return string */ - public function render(AbstractElement $element) + public function render(AbstractElement $element): string { $timeZoneCode = $this->_localeDate->getConfigTimezone(); $locale = $this->localeResolver->getLocale(); From 25989946b2a32204516a43ad491c69252e441ef2 Mon Sep 17 00:00:00 2001 From: Alexander Steshuk <grp-engcom-vendorworker-Kilo@adobe.com> Date: Thu, 27 Feb 2020 15:55:55 +0200 Subject: [PATCH 162/369] magento2/pull/26939: Fixed static test. --- .../Block/System/Messages.php | 3 +++ .../Adminhtml/Notification/AjaxMarkAsRead.php | 7 ++++-- .../Adminhtml/Notification/MarkAsRead.php | 20 +++++++++++----- .../Adminhtml/Notification/MassMarkAsRead.php | 17 ++++++++++---- .../Adminhtml/Notification/MassRemove.php | 17 ++++++++++---- .../Adminhtml/Notification/Remove.php | 23 ++++++++++++------- .../Notification/MassMarkAsReadTest.php | 3 +++ .../Adminhtml/Notification/MassRemoveTest.php | 3 +++ 8 files changed, 69 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/AdminNotification/Block/System/Messages.php b/app/code/Magento/AdminNotification/Block/System/Messages.php index 318b8f8384e2e..c9b3a0b8844cc 100644 --- a/app/code/Magento/AdminNotification/Block/System/Messages.php +++ b/app/code/Magento/AdminNotification/Block/System/Messages.php @@ -12,6 +12,9 @@ use Magento\Framework\Notification\MessageInterface; use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; +/** + * AdminNotification Messages class + */ class Messages extends Template { /** diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php index e05de78c92356..be128435b62a1 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,9 +8,13 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\NotificationService; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class AjaxMarkAsRead extends Notification +/** + * AdminNotification AjaxMarkAsRead controller + */ +class AjaxMarkAsRead extends Notification implements HttpPostActionInterface { /** * @var NotificationService diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php index 6dd40b56e0a24..b1bfddc2ea67f 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,9 +8,13 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\NotificationService; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Exception\LocalizedException; -class MarkAsRead extends Notification +/** + * AdminNotification MarkAsRead controller + */ +class MarkAsRead extends Notification implements HttpGetActionInterface { /** * Authorization level of a basic admin session @@ -25,6 +28,10 @@ class MarkAsRead extends Notification */ private $notificationService; + /** + * @param Action\Context $context + * @param NotificationService $notificationService + */ public function __construct(Action\Context $context, NotificationService $notificationService) { parent::__construct($context); @@ -32,7 +39,9 @@ public function __construct(Action\Context $context, NotificationService $notifi } /** - * @return void + * @inheritdoc + * + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { @@ -50,9 +59,8 @@ public function execute() ); } - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl($this->getUrl('*'))); - return; + return $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl($this->getUrl('*'))); } - $this->_redirect('adminhtml/*/'); + return $this->_redirect('adminhtml/*/'); } } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php index e73f4219b7333..9d052e8474dd9 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,8 +8,12 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; -class MassMarkAsRead extends Notification +/** + * AdminNotification MassMarkAsRead controller + */ +class MassMarkAsRead extends Notification implements HttpPostActionInterface { /** * Authorization level of a basic admin session @@ -24,6 +27,10 @@ class MassMarkAsRead extends Notification */ private $inboxModelFactory; + /** + * @param Action\Context $context + * @param InboxModelFactory $inboxModelFactory + */ public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) { parent::__construct($context); @@ -31,7 +38,9 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod } /** - * @return void + * @inheritdoc + * + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { @@ -58,6 +67,6 @@ public function execute() ); } } - $this->_redirect('adminhtml/*/'); + return $this->_redirect('adminhtml/*/'); } } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php index a248430b5660c..3f6575cdd4c67 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,8 +8,12 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; -class MassRemove extends Notification +/** + * AdminNotification MassRemove controller + */ +class MassRemove extends Notification implements HttpPostActionInterface { /** @@ -24,6 +27,10 @@ class MassRemove extends Notification */ private $inboxModelFactory; + /** + * @param Action\Context $context + * @param InboxModelFactory $inboxModelFactory + */ public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) { parent::__construct($context); @@ -31,7 +38,9 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod } /** - * @return void + * @inheritdoc + * + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { @@ -56,6 +65,6 @@ public function execute() ); } } - $this->_redirect('adminhtml/*/'); + return $this->_redirect('adminhtml/*/'); } } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php index 0d74db43eef2b..39c563e8c1b29 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,8 +8,12 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; -class Remove extends Notification +/** + * AdminNotification Remove controller + */ +class Remove extends Notification implements HttpGetActionInterface { /** * Authorization level of a basic admin session @@ -24,6 +27,10 @@ class Remove extends Notification */ private $inboxModelFactory; + /** + * @param Action\Context $context + * @param InboxModelFactory $inboxModelFactory + */ public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) { parent::__construct($context); @@ -31,7 +38,9 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod } /** - * @return void + * @inheritdoc + * + * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { @@ -39,8 +48,7 @@ public function execute() $model = $this->inboxModelFactory->create()->load($id); if (!$model->getId()) { - $this->_redirect('adminhtml/*/'); - return; + return $this->_redirect('adminhtml/*/'); } try { @@ -55,9 +63,8 @@ public function execute() ); } - $this->_redirect('adminhtml/*/'); - return; + return $this->_redirect('adminhtml/*/'); } - $this->_redirect('adminhtml/*/'); + return $this->_redirect('adminhtml/*/'); } } diff --git a/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsReadTest.php b/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsReadTest.php index c611d6fd7289f..04a69fe200dd1 100644 --- a/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsReadTest.php +++ b/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsReadTest.php @@ -5,12 +5,15 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\Framework\App\Request\Http as HttpRequest; + class MassMarkAsReadTest extends \Magento\TestFramework\TestCase\AbstractBackendController { public function setUp() { $this->resource = 'Magento_AdminNotification::mark_as_read'; $this->uri = 'backend/admin/notification/massmarkasread'; + $this->httpMethod = HttpRequest::METHOD_POST; parent::setUp(); } } diff --git a/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemoveTest.php b/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemoveTest.php index f05985015833a..55ee6a58063a4 100644 --- a/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemoveTest.php +++ b/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemoveTest.php @@ -5,12 +5,15 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\Framework\App\Request\Http as HttpRequest; + class MassRemoveTest extends \Magento\TestFramework\TestCase\AbstractBackendController { public function setUp() { $this->resource = 'Magento_AdminNotification::adminnotification_remove'; $this->uri = 'backend/admin/notification/massremove'; + $this->httpMethod = HttpRequest::METHOD_POST; parent::setUp(); } } From 12ae6e594d260f39f316cf65d4de66516dbdb200 Mon Sep 17 00:00:00 2001 From: Michael Bottens <michael.bottens@blueacorn.com> Date: Tue, 3 Mar 2020 08:06:48 -0500 Subject: [PATCH 163/369] Update wishlist email product link to match wishlist page --- .../Wishlist/view/frontend/templates/email/items.phtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml b/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml index a7cfabc7da35f..8c71a8de033dc 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml @@ -17,13 +17,13 @@ <?php $_product = $item->getProduct(); ?> <td class="col product"> <p> - <a href="<?= $block->escapeUrl($block->getProductUrl($_product)) ?>"> + <a href="<?= $block->escapeUrl($block->getProductUrl($item)) ?>"> <?= /* @noEscape */ $block->getImage($block->getProductForThumbnail($item), 'product_small_image')->toHtml() ?> </a> </p> <p> - <a href="<?= $block->escapeUrl($block->getProductUrl($_product)) ?>"> + <a href="<?= $block->escapeUrl($block->getProductUrl($item)) ?>"> <strong><?= $block->escapeHtml($_product->getName()) ?></strong> </a> </p> @@ -34,7 +34,7 @@ </p> <?php endif; ?> <p> - <a href="<?= $block->escapeUrl($block->getProductUrl($_product)) ?>"> + <a href="<?= $block->escapeUrl($block->getProductUrl($item)) ?>"> <?= $block->escapeHtml(__('View Product')) ?> </a> </p> From 53c78d534063efa1d8275e27f9cd019dfd9000dc Mon Sep 17 00:00:00 2001 From: Vaha <vaha@atwix.com> Date: Tue, 3 Mar 2020 10:44:02 +0200 Subject: [PATCH 164/369] improved cms page custom layout update logic --- .../Page/CustomLayout/CustomLayoutManager.php | 3 +- .../Magento/Cms/Controller/PageTest.php | 16 ++++++++++ .../Model/Page/CustomLayoutManagerTest.php | 5 +++- .../Cms/_files/home_with_custom_handle.php | 30 +++++++++++++++++++ .../home_with_custom_handle_rollback.php | 21 +++++++++++++ 5 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Cms/_files/home_with_custom_handle.php create mode 100644 dev/tests/integration/testsuite/Magento/Cms/_files/home_with_custom_handle_rollback.php diff --git a/app/code/Magento/Cms/Model/Page/CustomLayout/CustomLayoutManager.php b/app/code/Magento/Cms/Model/Page/CustomLayout/CustomLayoutManager.php index 988bd5b4ac136..a172278015544 100644 --- a/app/code/Magento/Cms/Model/Page/CustomLayout/CustomLayoutManager.php +++ b/app/code/Magento/Cms/Model/Page/CustomLayout/CustomLayoutManager.php @@ -147,7 +147,8 @@ public function applyUpdate(PageLayout $layout, CustomLayoutSelectedInterface $l } $layout->addPageLayoutHandles( - ['selectable' => $this->sanitizeIdentifier($page) .'_' .$layoutSelected->getLayoutFileId()] + ['selectable' => $this->sanitizeIdentifier($page) .'_' .$layoutSelected->getLayoutFileId()], + 'cms_page_view' ); } } diff --git a/dev/tests/integration/testsuite/Magento/Cms/Controller/PageTest.php b/dev/tests/integration/testsuite/Magento/Cms/Controller/PageTest.php index 4600cd28fd3fc..d80644caca086 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Controller/PageTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Controller/PageTest.php @@ -85,4 +85,20 @@ public function testCustomHandles(): void $handles = $layout->getUpdate()->getHandles(); $this->assertContains('cms_page_view_selectable_test_custom_layout_page_3_test_selected', $handles); } + + /** + * Check home page custom handle is applied when rendering a page. + * + * @return void + * @throws \Throwable + * @magentoDataFixture Magento/Cms/_files/home_with_custom_handle.php + */ + public function testHomePageCustomHandles(): void + { + $this->dispatch('/'); + /** @var LayoutInterface $layout */ + $layout = Bootstrap::getObjectManager()->get(LayoutInterface::class); + $handles = $layout->getUpdate()->getHandles(); + $this->assertContains('cms_page_view_selectable_home_page_custom_layout', $handles); + } } diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/Page/CustomLayoutManagerTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/Page/CustomLayoutManagerTest.php index e741b95ff4371..6aa3dcd1c34f9 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Model/Page/CustomLayoutManagerTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/Page/CustomLayoutManagerTest.php @@ -97,6 +97,9 @@ public function testCustomLayoutUpdate(): void $result = $this->resultFactory->create(); $this->manager->applyUpdate($result, $this->repo->getFor($pageId)); $this->identityMap->remove((int)$page->getId()); - $this->assertContains('___selectable_page100_select2', $result->getLayout()->getUpdate()->getHandles()); + $this->assertContains( + 'cms_page_view_selectable_page100_select2', + $result->getLayout()->getUpdate()->getHandles() + ); } } diff --git a/dev/tests/integration/testsuite/Magento/Cms/_files/home_with_custom_handle.php b/dev/tests/integration/testsuite/Magento/Cms/_files/home_with_custom_handle.php new file mode 100644 index 0000000000000..2556e0318222d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/_files/home_with_custom_handle.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Cms\Model\Page as PageModel; +use Magento\Cms\Model\PageFactory as PageModelFactory; +use Magento\TestFramework\Cms\Model\CustomLayoutManager; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var PageModelFactory $pageFactory */ +$pageFactory = $objectManager->get(PageModelFactory::class); +/** @var CustomLayoutManager $fakeManager */ +$fakeManager = $objectManager->get(CustomLayoutManager::class); +$layoutRepo = $objectManager->create(PageModel\CustomLayoutRepositoryInterface::class, ['manager' => $fakeManager]); + +$customLayoutName = 'page_custom_layout'; + +/** @var PageModel $page */ +$page = $pageFactory->create(['customLayoutRepository' => $layoutRepo]); +$page->load('home'); +$cmsPageId = (int)$page->getId(); + +$fakeManager->fakeAvailableFiles($cmsPageId, [$customLayoutName]); +$page->setData('layout_update_selected', $customLayoutName); +$page->save(); diff --git a/dev/tests/integration/testsuite/Magento/Cms/_files/home_with_custom_handle_rollback.php b/dev/tests/integration/testsuite/Magento/Cms/_files/home_with_custom_handle_rollback.php new file mode 100644 index 0000000000000..3000e6f85d322 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/_files/home_with_custom_handle_rollback.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Cms\Model\Page\CustomLayout\CustomLayoutRepository; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var PageRepositoryInterface $pageRepository */ +$pageRepository = $objectManager->get(PageRepositoryInterface::class); +$cmsPage = $pageRepository->getById('home'); +$cmsPageId = (int)$cmsPage->getId(); + +/** @var CustomLayoutRepository $customLayoutRepository */ +$customLayoutRepository = $objectManager->get(CustomLayoutRepository::class); +$customLayoutRepository->deleteFor($cmsPageId); From 57af3d7395f1b2dbea7aa33758040f9cf25566ce Mon Sep 17 00:00:00 2001 From: Alexander Steshuk <grp-engcom-vendorworker-Kilo@adobe.com> Date: Thu, 27 Feb 2020 15:55:55 +0200 Subject: [PATCH 165/369] magento2/pull/26939: Fixed static test. --- .../Block/System/Messages.php | 3 +++ .../Adminhtml/Notification/AjaxMarkAsRead.php | 7 +++++-- .../Adminhtml/Notification/MarkAsRead.php | 18 ++++++++++------ .../Adminhtml/Notification/MassMarkAsRead.php | 15 +++++++++---- .../Adminhtml/Notification/MassRemove.php | 15 +++++++++---- .../Adminhtml/Notification/Remove.php | 21 ++++++++++++------- .../Notification/MassMarkAsReadTest.php | 3 +++ .../Adminhtml/Notification/MassRemoveTest.php | 3 +++ 8 files changed, 61 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/AdminNotification/Block/System/Messages.php b/app/code/Magento/AdminNotification/Block/System/Messages.php index 318b8f8384e2e..c9b3a0b8844cc 100644 --- a/app/code/Magento/AdminNotification/Block/System/Messages.php +++ b/app/code/Magento/AdminNotification/Block/System/Messages.php @@ -12,6 +12,9 @@ use Magento\Framework\Notification\MessageInterface; use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; +/** + * AdminNotification Messages class + */ class Messages extends Template { /** diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php index e05de78c92356..be128435b62a1 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/AjaxMarkAsRead.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,9 +8,13 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\NotificationService; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; -class AjaxMarkAsRead extends Notification +/** + * AdminNotification AjaxMarkAsRead controller + */ +class AjaxMarkAsRead extends Notification implements HttpPostActionInterface { /** * @var NotificationService diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php index 6dd40b56e0a24..7b3f99ae8e2c1 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,9 +8,13 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\NotificationService; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Exception\LocalizedException; -class MarkAsRead extends Notification +/** + * AdminNotification MarkAsRead controller + */ +class MarkAsRead extends Notification implements HttpGetActionInterface { /** * Authorization level of a basic admin session @@ -25,6 +28,10 @@ class MarkAsRead extends Notification */ private $notificationService; + /** + * @param Action\Context $context + * @param NotificationService $notificationService + */ public function __construct(Action\Context $context, NotificationService $notificationService) { parent::__construct($context); @@ -32,7 +39,7 @@ public function __construct(Action\Context $context, NotificationService $notifi } /** - * @return void + * @inheritdoc */ public function execute() { @@ -50,9 +57,8 @@ public function execute() ); } - $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl($this->getUrl('*'))); - return; + return $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl($this->getUrl('*'))); } - $this->_redirect('adminhtml/*/'); + return $this->_redirect('adminhtml/*/'); } } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php index e73f4219b7333..12198d05f6ae7 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,8 +8,12 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; -class MassMarkAsRead extends Notification +/** + * AdminNotification MassMarkAsRead controller + */ +class MassMarkAsRead extends Notification implements HttpPostActionInterface { /** * Authorization level of a basic admin session @@ -24,6 +27,10 @@ class MassMarkAsRead extends Notification */ private $inboxModelFactory; + /** + * @param Action\Context $context + * @param InboxModelFactory $inboxModelFactory + */ public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) { parent::__construct($context); @@ -31,7 +38,7 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod } /** - * @return void + * @inheritdoc */ public function execute() { @@ -58,6 +65,6 @@ public function execute() ); } } - $this->_redirect('adminhtml/*/'); + return $this->_redirect('adminhtml/*/'); } } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php index a248430b5660c..0ca114ac4021c 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,8 +8,12 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; -class MassRemove extends Notification +/** + * AdminNotification MassRemove controller + */ +class MassRemove extends Notification implements HttpPostActionInterface { /** @@ -24,6 +27,10 @@ class MassRemove extends Notification */ private $inboxModelFactory; + /** + * @param Action\Context $context + * @param InboxModelFactory $inboxModelFactory + */ public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) { parent::__construct($context); @@ -31,7 +38,7 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod } /** - * @return void + * @inheritdoc */ public function execute() { @@ -56,6 +63,6 @@ public function execute() ); } } - $this->_redirect('adminhtml/*/'); + return $this->_redirect('adminhtml/*/'); } } diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php index 0d74db43eef2b..fe699cd692160 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -9,8 +8,12 @@ use Magento\AdminNotification\Controller\Adminhtml\Notification; use Magento\AdminNotification\Model\InboxFactory as InboxModelFactory; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface; -class Remove extends Notification +/** + * AdminNotification Remove controller + */ +class Remove extends Notification implements HttpGetActionInterface { /** * Authorization level of a basic admin session @@ -24,6 +27,10 @@ class Remove extends Notification */ private $inboxModelFactory; + /** + * @param Action\Context $context + * @param InboxModelFactory $inboxModelFactory + */ public function __construct(Action\Context $context, InboxModelFactory $inboxModelFactory) { parent::__construct($context); @@ -31,7 +38,7 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod } /** - * @return void + * @inheritdoc */ public function execute() { @@ -39,8 +46,7 @@ public function execute() $model = $this->inboxModelFactory->create()->load($id); if (!$model->getId()) { - $this->_redirect('adminhtml/*/'); - return; + return $this->_redirect('adminhtml/*/'); } try { @@ -55,9 +61,8 @@ public function execute() ); } - $this->_redirect('adminhtml/*/'); - return; + return $this->_redirect('adminhtml/*/'); } - $this->_redirect('adminhtml/*/'); + return $this->_redirect('adminhtml/*/'); } } diff --git a/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsReadTest.php b/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsReadTest.php index c611d6fd7289f..04a69fe200dd1 100644 --- a/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsReadTest.php +++ b/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsReadTest.php @@ -5,12 +5,15 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\Framework\App\Request\Http as HttpRequest; + class MassMarkAsReadTest extends \Magento\TestFramework\TestCase\AbstractBackendController { public function setUp() { $this->resource = 'Magento_AdminNotification::mark_as_read'; $this->uri = 'backend/admin/notification/massmarkasread'; + $this->httpMethod = HttpRequest::METHOD_POST; parent::setUp(); } } diff --git a/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemoveTest.php b/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemoveTest.php index f05985015833a..55ee6a58063a4 100644 --- a/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemoveTest.php +++ b/dev/tests/integration/testsuite/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemoveTest.php @@ -5,12 +5,15 @@ */ namespace Magento\AdminNotification\Controller\Adminhtml\Notification; +use Magento\Framework\App\Request\Http as HttpRequest; + class MassRemoveTest extends \Magento\TestFramework\TestCase\AbstractBackendController { public function setUp() { $this->resource = 'Magento_AdminNotification::adminnotification_remove'; $this->uri = 'backend/admin/notification/massremove'; + $this->httpMethod = HttpRequest::METHOD_POST; parent::setUp(); } } From b7c1d0467cee29fa80b81d6cd20307a3dc8eed78 Mon Sep 17 00:00:00 2001 From: Alexander Steshuk <grp-engcom-vendorworker-Kilo@adobe.com> Date: Tue, 3 Mar 2020 16:42:06 +0200 Subject: [PATCH 166/369] magento2/pull/26939: Fixed static test. --- .../Controller/Adminhtml/Notification/MarkAsRead.php | 2 -- .../Controller/Adminhtml/Notification/MassMarkAsRead.php | 2 -- .../Controller/Adminhtml/Notification/MassRemove.php | 2 -- .../Controller/Adminhtml/Notification/Remove.php | 2 -- 4 files changed, 8 deletions(-) diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php index b1bfddc2ea67f..7b3f99ae8e2c1 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MarkAsRead.php @@ -40,8 +40,6 @@ public function __construct(Action\Context $context, NotificationService $notifi /** * @inheritdoc - * - * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php index 9d052e8474dd9..12198d05f6ae7 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassMarkAsRead.php @@ -39,8 +39,6 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod /** * @inheritdoc - * - * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php index 3f6575cdd4c67..0ca114ac4021c 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/MassRemove.php @@ -39,8 +39,6 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod /** * @inheritdoc - * - * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { diff --git a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php index 39c563e8c1b29..fe699cd692160 100644 --- a/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php +++ b/app/code/Magento/AdminNotification/Controller/Adminhtml/Notification/Remove.php @@ -39,8 +39,6 @@ public function __construct(Action\Context $context, InboxModelFactory $inboxMod /** * @inheritdoc - * - * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface */ public function execute() { From 0fd8a5146cdf4e524150e68f89085d90f0d42be3 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Tue, 3 Mar 2020 10:58:59 -0600 Subject: [PATCH 167/369] MC-23890: Customer module Recurring setup script performance problems --- app/code/Magento/Customer/Model/Customer.php | 29 +++- .../Magento/Customer/Setup/RecurringData.php | 28 +++- .../Test/Unit/Setup/RecurringDataTest.php | 129 ++++++++++++++++++ 3 files changed, 174 insertions(+), 12 deletions(-) create mode 100644 app/code/Magento/Customer/Test/Unit/Setup/RecurringDataTest.php diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 2692d1edf0143..1e4914b152de3 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -21,6 +21,7 @@ use Magento\Store\Model\ScopeInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Math\Random; +use Magento\Framework\Indexer\IndexerInterface; /** * Customer model @@ -62,8 +63,7 @@ class Customer extends \Magento\Framework\Model\AbstractModel const XML_PATH_RESET_PASSWORD_TEMPLATE = 'customer/password/reset_password_template'; /** - * @deprecated - * @see AccountConfirmation::XML_PATH_IS_CONFIRM + * @deprecated @see \Magento\Customer\Model\AccountConfirmation::XML_PATH_IS_CONFIRM */ const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm'; @@ -227,6 +227,11 @@ class Customer extends \Magento\Framework\Model\AbstractModel */ private $storedAddress; + /** + * @var IndexerInterface|null + */ + private $indexer; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -304,6 +309,19 @@ public function __construct( ); } + /** + * Micro-caching optimization + * + * @return IndexerInterface + */ + private function getIndexer() : IndexerInterface + { + if ($this->indexer === null) { + $this->indexer = $this->indexerRegistry->get(self::CUSTOMER_GRID_INDEXER_ID); + } + return $this->indexer; + } + /** * Initialize customer model * @@ -1075,8 +1093,7 @@ public function resetErrors() */ public function afterSave() { - $indexer = $this->indexerRegistry->get(self::CUSTOMER_GRID_INDEXER_ID); - if ($indexer->getState()->getStatus() == StateInterface::STATUS_VALID) { + if ($this->getIndexer()->getState()->getStatus() == StateInterface::STATUS_VALID) { $this->_getResource()->addCommitCallback([$this, 'reindex']); } return parent::afterSave(); @@ -1100,9 +1117,7 @@ public function afterDeleteCommit() */ public function reindex() { - /** @var \Magento\Framework\Indexer\IndexerInterface $indexer */ - $indexer = $this->indexerRegistry->get(self::CUSTOMER_GRID_INDEXER_ID); - $indexer->reindexRow($this->getId()); + $this->getIndexer()->reindexRow($this->getId()); } /** diff --git a/app/code/Magento/Customer/Setup/RecurringData.php b/app/code/Magento/Customer/Setup/RecurringData.php index fbef4c05d126d..76ff818ca7e5e 100644 --- a/app/code/Magento/Customer/Setup/RecurringData.php +++ b/app/code/Magento/Customer/Setup/RecurringData.php @@ -7,6 +7,7 @@ namespace Magento\Customer\Setup; use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Framework\Indexer\StateInterface; use Magento\Framework\Setup\InstallDataInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; @@ -27,17 +28,34 @@ class RecurringData implements InstallDataInterface * * @param IndexerRegistry $indexerRegistry */ - public function __construct(IndexerRegistry $indexerRegistry) - { + public function __construct( + IndexerRegistry $indexerRegistry + ) { $this->indexerRegistry = $indexerRegistry; } /** - * {@inheritdoc} + * @inheritDoc */ public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { - $indexer = $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); - $indexer->reindexAll(); + if ($this->isNeedToDoReindex($setup)) { + $indexer = $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); + $indexer->reindexAll(); + } + } + + /** + * Check is re-index needed + * + * @param ModuleDataSetupInterface $setup + * @return bool + */ + private function isNeedToDoReindex(ModuleDataSetupInterface $setup) : bool + { + return !$setup->tableExists('customer_grid_flat') + || $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID) + ->getState() + ->getStatus() == StateInterface::STATUS_INVALID; } } diff --git a/app/code/Magento/Customer/Test/Unit/Setup/RecurringDataTest.php b/app/code/Magento/Customer/Test/Unit/Setup/RecurringDataTest.php new file mode 100644 index 0000000000000..ee1af91552f6d --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Setup/RecurringDataTest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Test\Unit\Setup; + +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Framework\Indexer\StateInterface; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Customer\Model\Customer; +use Magento\Customer\Setup\RecurringData; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Test for recurring data + */ +class RecurringDataTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var IndexerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexer; + + /** + * @var StateInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $state; + + /** + * @var IndexerRegistry|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerRegistry; + + /** + * @var ModuleDataSetupInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $setup; + + /** + * @var ModuleContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $context; + + /** + * @var RecurringData + */ + private $recurringData; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->state = $this->getMockBuilder(StateInterface::class) + ->setMethods(['getStatus']) + ->getMockForAbstractClass(); + $this->indexer = $this->getMockBuilder(IndexerInterface::class) + ->setMethods(['getState', 'reindexAll']) + ->getMockForAbstractClass(); + $this->indexer->expects($this->any()) + ->method('getState') + ->willReturn($this->state); + $this->indexerRegistry = $this->getMockBuilder(IndexerRegistry::class) + ->disableOriginalConstructor() + ->setMethods(['get']) + ->getMock(); + $this->indexerRegistry->expects($this->any()) + ->method('get') + ->with(Customer::CUSTOMER_GRID_INDEXER_ID) + ->willReturn($this->indexer); + $this->setup = $this->getMockBuilder(ModuleDataSetupInterface::class) + ->setMethods(['tableExists']) + ->getMockForAbstractClass(); + $this->context = $this->getMockBuilder(ModuleContextInterface::class) + ->getMockForAbstractClass(); + + $this->recurringData = $this->objectManagerHelper->getObject( + RecurringData::class, + [ + 'indexerRegistry' => $this->indexerRegistry + ] + ); + } + + /** + * @param bool $isTableExists + * @param string $indexerState + * @param int $countReindex + * @return void + * @dataProvider installDataProvider + */ + public function testInstall(bool $isTableExists, string $indexerState, int $countReindex) + { + $this->setup->expects($this->any()) + ->method('tableExists') + ->with('customer_grid_flat') + ->willReturn($isTableExists); + $this->state->expects($this->any()) + ->method('getStatus') + ->willReturn($indexerState); + $this->indexer->expects($this->exactly($countReindex)) + ->method('reindexAll'); + $this->recurringData->install($this->setup, $this->context); + } + + /** + * @return array + */ + public function installDataProvider() : array + { + return [ + [true, StateInterface::STATUS_INVALID, 1], + [false, StateInterface::STATUS_INVALID, 1], + [true, StateInterface::STATUS_VALID, 0], + [false, StateInterface::STATUS_VALID, 1], + ]; + } +} From 436d0ae410101e526ac9326483788153de507f26 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Tue, 3 Mar 2020 11:19:07 -0600 Subject: [PATCH 168/369] MC-23890: Customer module Recurring setup script performance problems --- app/code/Magento/Customer/Model/Customer.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 1e4914b152de3..ea52994735c63 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -1003,6 +1003,7 @@ public function getSharedWebsiteIds() */ public function getAttributeSetId() { + // phpstan:ignore "Call to an undefined static method*" return parent::getAttributeSetId() ?: CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER; } From beaaf5bacfa51c99611943c79e0ffba5bfc1ba21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Tue, 3 Mar 2020 21:38:08 +0100 Subject: [PATCH 169/369] Cleanup ObjectManager usage - Magento_WebapiAsync --- app/code/Magento/WebapiAsync/Model/Config.php | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/WebapiAsync/Model/Config.php b/app/code/Magento/WebapiAsync/Model/Config.php index 7980be479dfa5..7329862ca528c 100644 --- a/app/code/Magento/WebapiAsync/Model/Config.php +++ b/app/code/Magento/WebapiAsync/Model/Config.php @@ -3,35 +3,34 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\WebapiAsync\Model; +use Magento\AsynchronousOperations\Model\ConfigInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Serialize\SerializerInterface; use Magento\Webapi\Model\Cache\Type\Webapi as WebapiCache; use Magento\Webapi\Model\Config as WebapiConfig; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Serialize\SerializerInterface; -use Magento\Framework\Exception\LocalizedException; use Magento\Webapi\Model\Config\Converter; /** * Class for accessing to Webapi_Async configuration. */ -class Config implements \Magento\AsynchronousOperations\Model\ConfigInterface +class Config implements ConfigInterface { /** - * @var \Magento\Webapi\Model\Cache\Type\Webapi + * @var WebapiCache */ private $cache; /** - * @var \Magento\Webapi\Model\Config + * @var WebapiConfig */ private $webApiConfig; /** - * @var \Magento\Framework\Serialize\SerializerInterface + * @var SerializerInterface */ private $serializer; @@ -43,18 +42,18 @@ class Config implements \Magento\AsynchronousOperations\Model\ConfigInterface /** * Initialize dependencies. * - * @param \Magento\Webapi\Model\Cache\Type\Webapi $cache - * @param \Magento\Webapi\Model\Config $webApiConfig - * @param \Magento\Framework\Serialize\SerializerInterface|null $serializer + * @param WebapiCache $cache + * @param WebapiConfig $webApiConfig + * @param SerializerInterface $serializer */ public function __construct( WebapiCache $cache, WebapiConfig $webApiConfig, - SerializerInterface $serializer = null + SerializerInterface $serializer ) { $this->cache = $cache; $this->webApiConfig = $webApiConfig; - $this->serializer = $serializer ? : ObjectManager::getInstance()->get(SerializerInterface::class); + $this->serializer = $serializer; } /** From 1045eb8a0cad9fef1502bfdf2de818e77df1fbe4 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Tue, 3 Mar 2020 23:14:11 +0100 Subject: [PATCH 170/369] #27117 Fix invalid test name --- .../Test/AdminCreateDownloadableProductWithTierPriceTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml index 768f766098aa6..131193e7743e2 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithTierPriceTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminCreateDownloadableProductWithTierPriceTextTest" extends="AdminCreateDownloadableProductWithGroupPriceTest"> + <test name="AdminCreateDownloadableProductWithTierPriceTest" extends="AdminCreateDownloadableProductWithGroupPriceTest"> <annotations> <features value="Catalog"/> <stories value="Create Downloadable Product"/> From 8f81132dcf27bc1b09ba89a6e7002a2f3dd02ef2 Mon Sep 17 00:00:00 2001 From: Vaha <vaha@atwix.com> Date: Wed, 4 Mar 2020 09:32:53 +0200 Subject: [PATCH 171/369] added date format adjustment for 'validate-dob' rule --- ...ateOfBirthValidationMessageActionGroup.xml | 25 +++++++++++++ ...AdminCustomerAccountInformationSection.xml | 1 + ...BirthValidationForFranceDateFormatTest.xml | 36 +++++++++++++++++++ .../view/base/web/js/lib/validation/rules.js | 4 +-- 4 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAdminCustomerDateOfBirthValidationMessageActionGroup.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/Test/AdminCheckDateOfBirthValidationForFranceDateFormatTest.xml diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAdminCustomerDateOfBirthValidationMessageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAdminCustomerDateOfBirthValidationMessageActionGroup.xml new file mode 100644 index 0000000000000..03682f9232eab --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAdminCustomerDateOfBirthValidationMessageActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminCustomerDateOfBirthValidationMessageActionGroup"> + <annotations> + <description>Fills 'Date of Birth' input with provided 'dob' value, clicks 'save' button, checks + there is no provided validation error message for the 'Date of Birth' input on the page.</description> + </annotations> + <arguments> + <argument name="message" type="string" defaultValue="The Date of Birth should not be greater than today."/> + <argument name="dob" type="string" defaultValue="15/01/1970"/> + </arguments> + + <fillField userInput="{{dob}}" selector="{{AdminCustomerAccountInformationSection.dateOfBirth}}" stepKey="fillDateOfBirth"/> + <click selector="{{AdminCustomerMainActionsSection.saveButton}}" stepKey="saveCustomer"/> + <dontSee selector="{{AdminCustomerAccountInformationSection.dateOfBirthValidationErrorField}}" userInput="{{message}}" stepKey="seeTheErrorMessageIsDisplayed"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml index 2c9e66c15bbab..50e923d9b32cc 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml @@ -36,5 +36,6 @@ <element name="disabledGroup" type="text" selector="//div[@class='admin__action-group-wrap admin__action-multiselect-wrap action-select-wrap _disabled']"/> <element name="customerAttribute" type="input" selector="//input[contains(@name,'{{attributeCode}}')]" parameterized="true"/> <element name="attributeImage" type="block" selector="//div[contains(concat(' ',normalize-space(@class),' '),' file-uploader-preview ')]//img"/> + <element name="dateOfBirthValidationErrorField" type="text" selector="input[name='customer[dob]'] ~ label.admin__field-error"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCheckDateOfBirthValidationForFranceDateFormatTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCheckDateOfBirthValidationForFranceDateFormatTest.xml new file mode 100644 index 0000000000000..78a1d122d59d4 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCheckDateOfBirthValidationForFranceDateFormatTest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckDateOfBirthValidationForFranceDateFormatTest"> + <annotations> + <features value="Customer"/> + <stories value="Checks 'Date of Birth' field validation for the France date format value"/> + <title value="Checks 'Date of Birth' field validation for the France date format value"/> + <group value="customer"/> + <group value="ui"/> + </annotations> + <before> + <magentoCLI command="setup:static-content:deploy fr_FR" stepKey="deployStaticContentWithFrenchLocale"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="SetAdminAccountActionGroup" stepKey="setAdminInterfaceLocaleToFrance"> + <argument name="InterfaceLocaleByValue" value="fr_FR"/> + </actionGroup> + </before> + + <after> + <actionGroup ref="SetAdminAccountActionGroup" stepKey="setAdminInterfaceLocaleToDefaultValue"> + <argument name="InterfaceLocaleByValue" value="en_US"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <actionGroup ref="AdminNavigateNewCustomerActionGroup" stepKey="navigateToNewCustomerPage"/> + <actionGroup ref="AssertAdminCustomerDateOfBirthValidationMessageActionGroup" stepKey="assertDateOfBirthValidationMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index 825b7f4a0546e..f9778ad8d688c 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -1069,12 +1069,12 @@ define([ $.mage.__('This link is not allowed.') ], 'validate-dob': [ - function (value) { + function (value, param, params) { if (value === '') { return true; } - return moment(value).isBefore(moment()); + return moment.utc(value, params.dateFormat).isSameOrBefore(moment.utc()); }, $.mage.__('The Date of Birth should not be greater than today.') ] From 93d524d2216edf9567a949e159b3d61561f221c6 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <ihor-sviziev@users.noreply.github.com> Date: Wed, 4 Mar 2020 14:36:44 +0200 Subject: [PATCH 172/369] magento/magento2#23742 Add Header (h1 - h6) tags to layout xml htmlTags Allowed types Update integration test to check all types of containers --- .../testsuite/Magento/Framework/View/LayoutTest.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/LayoutTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/LayoutTest.php index 6c4a6a4b82c9f..1029e904f7501 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/LayoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/View/LayoutTest.php @@ -253,17 +253,28 @@ public function testAddContainer($htmlTag) public function addContainerDataProvider() { return [ + ['aside'], ['dd'], ['div'], ['dl'], ['fieldset'], + ['main'], + ['nav'], ['header'], + ['footer'], ['ol'], ['p'], ['section'], ['table'], ['tfoot'], - ['ul'] + ['ul'], + ['article'], + ['h1'], + ['h2'], + ['h3'], + ['h4'], + ['h5'], + ['h6'], ]; } From ff2c0473366af2e5bca9b2fc6585268f32ae1264 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <ihor-sviziev@users.noreply.github.com> Date: Wed, 4 Mar 2020 14:38:37 +0200 Subject: [PATCH 173/369] magento/magento2#23742 Add Header (h1 - h6) tags to layout xml htmlTags Allowed types Add article as allowed html tag --- lib/internal/Magento/Framework/View/Layout/etc/elements.xsd | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd index 43456ce27b044..51f193109d938 100644 --- a/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd +++ b/lib/internal/Magento/Framework/View/Layout/etc/elements.xsd @@ -140,6 +140,7 @@ <xs:enumeration value="table"/> <xs:enumeration value="tfoot"/> <xs:enumeration value="ul"/> + <xs:enumeration value="article"/> <xs:enumeration value="h1"/> <xs:enumeration value="h2"/> <xs:enumeration value="h3"/> From 76e61aecca249855bf9f45c25672bd800121e58e Mon Sep 17 00:00:00 2001 From: Michael Bottens <michael.bottens@blueacorn.com> Date: Wed, 4 Mar 2020 07:57:47 -0500 Subject: [PATCH 174/369] CR fixes --- .../Magento/Wishlist/Block/Share/Email/Items.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Wishlist/Block/Share/Email/Items.php b/app/code/Magento/Wishlist/Block/Share/Email/Items.php index 0887951d180aa..c7ff49943b222 100644 --- a/app/code/Magento/Wishlist/Block/Share/Email/Items.php +++ b/app/code/Magento/Wishlist/Block/Share/Email/Items.php @@ -14,6 +14,7 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Framework\App\ObjectManager; use Magento\Framework\View\ConfigInterface; use Magento\Wishlist\Model\Item; @@ -23,7 +24,9 @@ */ class Items extends \Magento\Wishlist\Block\AbstractBlock { - /** @var ItemResolverInterface */ + /** + * @var ItemResolverInterface + */ private $itemResolver; /** @@ -32,23 +35,24 @@ class Items extends \Magento\Wishlist\Block\AbstractBlock protected $_template = 'Magento_Wishlist::email/items.phtml'; /** + * Items constructor. * @param \Magento\Catalog\Block\Product\Context $context * @param \Magento\Framework\App\Http\Context $httpContext - * @param ItemResolverInterface $itemResolver * @param array $data * @param ConfigInterface|null $config * @param UrlBuilder|null $urlBuilder + * @param ItemResolverInterface|null $itemResolver */ public function __construct( \Magento\Catalog\Block\Product\Context $context, \Magento\Framework\App\Http\Context $httpContext, - ItemResolverInterface $itemResolver, array $data = [], ConfigInterface $config = null, - UrlBuilder $urlBuilder = null + UrlBuilder $urlBuilder = null, + ItemResolverInterface $itemResolver = null ) { - $this->itemResolver = $itemResolver; parent::__construct($context, $httpContext, $data, $config, $urlBuilder); + $this->itemResolver = $itemResolver ?? ObjectManager::getInstance()->get(ItemResolverInterface::class); } /** @@ -57,7 +61,7 @@ public function __construct( * @param Item $item * @return Product */ - public function getProductForThumbnail(Item $item) : Product + public function getProductForThumbnail(Item $item): Product { return $this->itemResolver->getFinalProduct($item); } From 55c226636e4f45f63cf9bdcd3b12b438bbd2f634 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Wed, 4 Mar 2020 17:47:39 +0200 Subject: [PATCH 175/369] Fix static test --- .../Model/ProductUrlPathGenerator.php | 5 +- .../Model/ProductUrlPathGeneratorTest.php | 62 ++++++++++++++----- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php index a5553535b390a..da2dd8a505869 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductUrlPathGenerator.php @@ -3,20 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CatalogUrlRewrite\Model; -use Magento\Store\Model\Store; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\Product; -use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; /** - * Class ProductUrlPathGenerator + * Model product url path generator */ class ProductUrlPathGenerator { diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php index 233d0703448ca..95ef16c5ace4c 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductUrlPathGeneratorTest.php @@ -7,34 +7,42 @@ namespace Magento\CatalogUrlRewrite\Test\Unit\Model; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Product; +use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class ProductUrlPathGeneratorTest + * Verify ProductUrlPathGenerator class */ -class ProductUrlPathGeneratorTest extends \PHPUnit\Framework\TestCase +class ProductUrlPathGeneratorTest extends TestCase { - /** @var \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator */ + /** @var ProductUrlPathGenerator */ protected $productUrlPathGenerator; - /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var StoreManagerInterface|MockObject */ protected $storeManager; - /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ScopeConfigInterface|MockObject */ protected $scopeConfig; - /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */ + /** @var CategoryUrlPathGenerator|MockObject */ protected $categoryUrlPathGenerator; - /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ + /** @var Product|MockObject */ protected $product; - /** @var \Magento\Catalog\Api\ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ProductRepositoryInterface|MockObject */ protected $productRepository; - /** @var \Magento\Catalog\Model\Category|\PHPUnit_Framework_MockObject_MockObject */ + /** @var Category|MockObject */ protected $category; /** @@ -42,7 +50,7 @@ class ProductUrlPathGeneratorTest extends \PHPUnit\Framework\TestCase */ protected function setUp(): void { - $this->category = $this->createMock(\Magento\Catalog\Model\Category::class); + $this->category = $this->createMock(Category::class); $productMethods = [ '__wakeup', 'getData', @@ -54,17 +62,17 @@ protected function setUp(): void 'setStoreId', ]; - $this->product = $this->createPartialMock(\Magento\Catalog\Model\Product::class, $productMethods); - $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $this->product = $this->createPartialMock(Product::class, $productMethods); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); $this->categoryUrlPathGenerator = $this->createMock( - \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::class + CategoryUrlPathGenerator::class ); - $this->productRepository = $this->createMock(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $this->productRepository = $this->createMock(ProductRepositoryInterface::class); $this->productRepository->expects($this->any())->method('getById')->willReturn($this->product); $this->productUrlPathGenerator = (new ObjectManager($this))->getObject( - \Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator::class, + ProductUrlPathGenerator::class, [ 'storeManager' => $this->storeManager, 'scopeConfig' => $this->scopeConfig, @@ -75,6 +83,8 @@ protected function setUp(): void } /** + * Data provider for testGetUrlPath. + * * @return array */ public function getUrlPathDataProvider(): array @@ -89,6 +99,8 @@ public function getUrlPathDataProvider(): array } /** + * Verify get url path. + * * @dataProvider getUrlPathDataProvider * @param string|null|bool $urlKey * @param string|null|bool $productName @@ -109,6 +121,8 @@ public function testGetUrlPath($urlKey, $productName, $formatterCalled, $result) } /** + * Verify get url key. + * * @param string|bool $productUrlKey * @param string|bool $expectedUrlKey * @return void @@ -122,6 +136,8 @@ public function testGetUrlKey($productUrlKey, $expectedUrlKey): void } /** + * Data provider for testGetUrlKey. + * * @return array */ public function getUrlKeyDataProvider(): array @@ -133,6 +149,8 @@ public function getUrlKeyDataProvider(): array } /** + * Verify get url path with default utl key. + * * @param string|null|bool $storedUrlKey * @param string|null|bool $productName * @param string $expectedUrlKey @@ -150,6 +168,8 @@ public function testGetUrlPathDefaultUrlKey($storedUrlKey, $productName, $expect } /** + * Data provider for testGetUrlPathDefaultUrlKey. + * * @return array */ public function getUrlPathDefaultUrlKeyDataProvider(): array @@ -161,6 +181,8 @@ public function getUrlPathDefaultUrlKeyDataProvider(): array } /** + * Verify get url path with category. + * * @return void */ public function testGetUrlPathWithCategory(): void @@ -177,6 +199,8 @@ public function testGetUrlPathWithCategory(): void } /** + * Verify get url path with suffix. + * * @return void */ public function testGetUrlPathWithSuffix(): void @@ -198,6 +222,8 @@ public function testGetUrlPathWithSuffix(): void } /** + * Verify get url path with suffix and category and store. + * * @return void */ public function testGetUrlPathWithSuffixAndCategoryAndStore(): void @@ -219,6 +245,8 @@ public function testGetUrlPathWithSuffixAndCategoryAndStore(): void } /** + * Verify get canonical url path. + * * @return void */ public function testGetCanonicalUrlPath(): void @@ -232,6 +260,8 @@ public function testGetCanonicalUrlPath(): void } /** + * Verify get canonical path with category. + * * @return void */ public function testGetCanonicalUrlPathWithCategory(): void From 9c0211605537143b9cde9516a1be4a3173edb0b0 Mon Sep 17 00:00:00 2001 From: Vasilii Burlacu <v.burlacu@atwix.com> Date: Wed, 4 Mar 2020 17:58:06 +0200 Subject: [PATCH 176/369] Display category filter item in layered navigation based on the system configuration from admin area --- .../Model/Config/LayerCategoryConfig.php | 82 +++++++++++++++++ .../Catalog/Model/Layer/FilterList.php | 22 ++++- .../Test/Unit/Model/Layer/FilterListTest.php | 88 ++++++++++++++++++- .../Magento/Catalog/etc/adminhtml/system.xml | 6 ++ app/code/Magento/Catalog/etc/config.xml | 3 + 5 files changed, 196 insertions(+), 5 deletions(-) create mode 100644 app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php diff --git a/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php b/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php new file mode 100644 index 0000000000000..3ee9bd888f568 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Config; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Config for category in the layered navigation + */ +class LayerCategoryConfig +{ + private const XML_PATH_CATALOG_LAYERED_NAVIGATION_DISPLAY_CATEGORY = 'catalog/layered_navigation/display_category'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * LayerCategoryConfig constructor + * + * @param ScopeConfigInterface $scopeConfig + * @param StoreManagerInterface $storeManager + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + StoreManagerInterface $storeManager + ) { + $this->scopeConfig = $scopeConfig; + $this->storeManager = $storeManager; + } + + /** + * Check if category filter item should be added in the layered navigation + * + * @param string $scopeType + * @param null|int|string $scopeCode + * + * @return bool + */ + public function isCategoryFilterVisibleInLayerNavigation( + $scopeType = ScopeInterface::SCOPE_STORES, + $scopeCode = null + ): bool { + if (!$scopeCode) { + $scopeCode = $this->getStoreId(); + } + + return $this->scopeConfig->isSetFlag( + static::XML_PATH_CATALOG_LAYERED_NAVIGATION_DISPLAY_CATEGORY, + $scopeType, + $scopeCode + ); + } + + /** + * Get the current store ID + * + * @return int|null + */ + protected function getStoreId(): ?int + { + try { + return (int) $this->storeManager->getStore()->getId(); + } catch (NoSuchEntityException $e) { + return null; + } + } +} diff --git a/app/code/Magento/Catalog/Model/Layer/FilterList.php b/app/code/Magento/Catalog/Model/Layer/FilterList.php index b8e9b8ad4aaa5..2f32971c80bed 100644 --- a/app/code/Magento/Catalog/Model/Layer/FilterList.php +++ b/app/code/Magento/Catalog/Model/Layer/FilterList.php @@ -7,6 +7,9 @@ namespace Magento\Catalog\Model\Layer; +use Magento\Catalog\Model\Config\LayerCategoryConfig; +use Magento\Framework\App\ObjectManager; + /** * Layer navigation filters */ @@ -44,18 +47,27 @@ class FilterList */ protected $filters = []; + /** + * @var LayerCategoryConfig|null + */ + private $layerCategoryConfig; + /** * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param FilterableAttributeListInterface $filterableAttributes * @param array $filters + * @param LayerCategoryConfig|null $layerCategoryConfig */ public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, FilterableAttributeListInterface $filterableAttributes, - array $filters = [] + array $filters = [], + LayerCategoryConfig $layerCategoryConfig = null ) { $this->objectManager = $objectManager; $this->filterableAttributes = $filterableAttributes; + $this->layerCategoryConfig = $layerCategoryConfig ?: + ObjectManager::getInstance()->get(LayerCategoryConfig::class); /** Override default filter type models */ $this->filterTypes = array_merge($this->filterTypes, $filters); @@ -70,9 +82,11 @@ public function __construct( public function getFilters(\Magento\Catalog\Model\Layer $layer) { if (!count($this->filters)) { - $this->filters = [ - $this->objectManager->create($this->filterTypes[self::CATEGORY_FILTER], ['layer' => $layer]), - ]; + if ($this->layerCategoryConfig->isCategoryFilterVisibleInLayerNavigation()) { + $this->filters = [ + $this->objectManager->create($this->filterTypes[self::CATEGORY_FILTER], ['layer' => $layer]), + ]; + } foreach ($this->filterableAttributes->getList() as $attribute) { $this->filters[] = $this->createAttributeFilter($attribute, $layer); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php index 731c5efd99746..92734bae2e444 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php @@ -7,8 +7,13 @@ namespace Magento\Catalog\Test\Unit\Model\Layer; +use Magento\Catalog\Model\Config\LayerCategoryConfig; use \Magento\Catalog\Model\Layer\FilterList; +use PHPUnit\Framework\MockObject\MockObject; +/** + * Filter List Test + */ class FilterListTest extends \PHPUnit\Framework\TestCase { /** @@ -36,6 +41,14 @@ class FilterListTest extends \PHPUnit\Framework\TestCase */ protected $model; + /** + * @var LayerCategoryConfig|MockObject + */ + private $layerCategoryConfigMock; + + /** + * Set Up + */ protected function setUp() { $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); @@ -51,8 +64,14 @@ protected function setUp() ]; $this->layerMock = $this->createMock(\Magento\Catalog\Model\Layer::class); + $this->layerCategoryConfigMock = $this->createMock(LayerCategoryConfig::class); - $this->model = new FilterList($this->objectManagerMock, $this->attributeListMock, $filters); + $this->model = new FilterList( + $this->objectManagerMock, + $this->attributeListMock, + $filters, + $this->layerCategoryConfigMock + ); } /** @@ -90,9 +109,57 @@ public function testGetFilters($method, $value, $expectedClass) ->method('getList') ->will($this->returnValue([$this->attributeMock])); + $this->layerCategoryConfigMock->expects($this->once()) + ->method('isCategoryVisibleInLayer') + ->willReturn(true); + $this->assertEquals(['filter', 'filter'], $this->model->getFilters($this->layerMock)); } + /** + * Test filters list result when category should not be included + * + * @param string $method + * @param string $value + * @param string $expectedClass + * @param array $expectedResult + * + * @dataProvider getFiltersWithoutCategoryDataProvider + * + * @return void + */ + public function testGetFiltersWithoutCategoryFilter( + string $method, + string $value, + string $expectedClass, + array $expectedResult + ): void { + $this->objectManagerMock->expects($this->at(0)) + ->method('create') + ->with( + $expectedClass, + [ + 'data' => ['attribute_model' => $this->attributeMock], + 'layer' => $this->layerMock + ] + ) + ->will($this->returnValue('filter')); + + $this->attributeMock->expects($this->once()) + ->method($method) + ->will($this->returnValue($value)); + + $this->attributeListMock->expects($this->once()) + ->method('getList') + ->will($this->returnValue([$this->attributeMock])); + + $this->layerCategoryConfigMock->expects($this->once()) + ->method('isCategoryVisibleInLayer') + ->willReturn(false); + + $this->assertEquals($expectedResult, $this->model->getFilters($this->layerMock)); + } + /** * @return array */ @@ -116,4 +183,23 @@ public function getFiltersDataProvider() ] ]; } + + /** + * Provides attribute filters without category item + * + * @return array + */ + public function getFiltersWithoutCategoryDataProvider(): array + { + return [ + 'Filters contains only price attribute' => [ + 'method' => 'getFrontendInput', + 'value' => 'price', + 'expectedClass' => 'PriceFilterClass', + 'expectedResult' => [ + 'filter' + ] + ] + ]; + } } diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index f59990cdcea96..f548c23b68ce3 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -138,6 +138,12 @@ <hide_in_single_store_mode>1</hide_in_single_store_mode> </field> </group> + <group id="layered_navigation"> + <field id="display_category" translate="label" type="select" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Display Category</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + </group> <group id="navigation" translate="label" type="text" sortOrder="500" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Category Top Navigation</label> <field id="max_depth" translate="label" type="text" sortOrder="1" showInDefault="1" canRestore="1"> diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index 68289904db0cf..e7beb3d083226 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -54,6 +54,9 @@ <time_format>12h</time_format> <forbidden_extensions>php,exe</forbidden_extensions> </custom_options> + <layered_navigation> + <display_category>1</display_category> + </layered_navigation> </catalog> <indexer> <catalog_product_price> From e53624a1a017937c6f398c83eb2a95297f2b5af4 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Wed, 4 Mar 2020 10:03:05 -0600 Subject: [PATCH 177/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Stabilizing builds --- .../Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml index 94753a1e5e2b3..6ec42335e60e3 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateDownloadableProductWithLinkTest.xml @@ -69,6 +69,7 @@ <!-- Save product --> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> + <magentoCron stepKey="runIndexCronJobs" groups="index"/> <!-- Assert product in storefront category page --> <amOnPage url="$$createCategory.name$$.html" stepKey="amOnCategoryPage"/> From 1546434bbb11828c6dd1960f325a01d2125ebdcc Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <ihor-sviziev@users.noreply.github.com> Date: Wed, 4 Mar 2020 21:42:35 +0200 Subject: [PATCH 178/369] TinyMCE4 hard to input double byte characters on chrome Remove not needed line --- lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 3a2055259ec76..a441e22af6b3a 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -607,7 +607,6 @@ define([ } this.addContentEditableAttributeBackToNonEditableNodes(); - //this.fixRangeSelection(editor); content = editor.getContent(); content = this.decodeContent(content); From 3b4703b8c8ab31b8b50e88a8283cbf446d387bcc Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Wed, 4 Mar 2020 14:13:23 -0600 Subject: [PATCH 179/369] MC-31616: Forward-port 2.3.5 GraphQL bug fixes --- .../Model/Config/FilterAttributeReader.php | 29 ++- .../Model/Resolver/Category/Image.php | 48 ++++- .../ProductEntityAttributesForAst.php | 31 ++- .../CatalogGraphQl/etc/schema.graphqls | 12 +- .../AvailableShippingMethods.php | 18 +- .../GraphQl/Catalog/CategoryListTest.php | 54 +++++- .../Magento/GraphQl/Catalog/CategoryTest.php | 76 +++++++- .../GraphQl/Catalog/MediaGalleryTest.php | 40 ++-- .../GraphQl/Catalog/ProductSearchTest.php | 44 ++--- .../GetAvailableShippingMethodsTest.php | 45 +++++ .../Guest/GetAvailableShippingMethodsTest.php | 51 +++++ .../Catalog/_files/catalog_category_image.php | 3 + .../_files/catalog_category_with_image.php | 2 +- .../catalog_category_with_long_image_name.php | 32 ++++ ...category_with_long_image_name_rollback.php | 24 +++ ...ong_image_name_magento_long_image_name.jpg | Bin 0 -> 4025 bytes ...duct_simple_with_media_gallery_entries.php | 56 ++---- ...th_layered_navigation_custom_attribute.php | 181 ++++++++---------- ...d_navigation_custom_attribute_rollback.php | 32 ++-- ..._navigation_with_multiselect_attribute.php | 76 ++++---- .../Quote/_files/add_configurable_product.php | 50 +++++ 21 files changed, 636 insertions(+), 268 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_long_image_name.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_long_image_name_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/magento_long_image_name_magento_long_image_name_magento_long_image_name.jpg create mode 100644 dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_configurable_product.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php index 4f3a88cc788df..6976086e74890 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php @@ -10,16 +10,15 @@ use Magento\Framework\Config\ReaderInterface; use Magento\Framework\GraphQl\Schema\Type\Entity\MapperInterface; use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; -use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; /** * Adds custom/eav attributes to product filter type in the GraphQL config. * * Product Attribute should satisfy the following criteria: - * - Attribute is searchable - * - "Visible in Advanced Search" is set to "Yes" - * - Attribute of type "Select" must have options + * - (Attribute is searchable AND "Visible in Advanced Search" is set to "Yes") + * - OR attribute is "Used in Layered Navigation" + * - AND Attribute of type "Select" must have options */ class FilterAttributeReader implements ReaderInterface { @@ -77,7 +76,7 @@ public function read($scope = null) : array $typeNames = $this->mapper->getMappedTypes(self::ENTITY_TYPE); $config = []; - foreach ($this->getAttributeCollection() as $attribute) { + foreach ($this->getFilterAttributes() as $attribute) { $attributeCode = $attribute->getAttributeCode(); foreach ($typeNames as $typeName) { @@ -120,15 +119,25 @@ private function getFilterType(Attribute $attribute): string } /** - * Create attribute collection + * Get attributes to use in product filter input * - * @return Collection|\Magento\Catalog\Model\ResourceModel\Eav\Attribute[] + * @return array */ - private function getAttributeCollection() + private function getFilterAttributes(): array { - return $this->collectionFactory->create() + $filterableAttributes = $this->collectionFactory + ->create() + ->addHasOptionsFilter() + ->addIsFilterableFilter() + ->getItems(); + + $searchableAttributes = $this->collectionFactory + ->create() ->addHasOptionsFilter() ->addIsSearchableFilter() - ->addDisplayInAdvancedSearchFilter(); + ->addDisplayInAdvancedSearchFilter() + ->getItems(); + + return $filterableAttributes + $searchableAttributes; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Image.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Image.php index a06a8252d5a5e..5de7fdc10ff4a 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Image.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Image.php @@ -13,6 +13,8 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Store\Api\Data\StoreInterface; use Magento\Framework\Filesystem\DirectoryList; +use Magento\Catalog\Model\Category\FileInfo; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; /** * Resolve category image to a fully qualified URL @@ -22,12 +24,19 @@ class Image implements ResolverInterface /** @var DirectoryList */ private $directoryList; + /** @var FileInfo */ + private $fileInfo; + /** * @param DirectoryList $directoryList + * @param FileInfo $fileInfo */ - public function __construct(DirectoryList $directoryList) - { + public function __construct( + DirectoryList $directoryList, + FileInfo $fileInfo + ) { $this->directoryList = $directoryList; + $this->fileInfo = $fileInfo; } /** @@ -45,21 +54,40 @@ public function resolve( } /** @var \Magento\Catalog\Model\Category $category */ $category = $value['model']; - $imagePath = $category->getImage(); + $imagePath = $category->getData('image'); if (empty($imagePath)) { return null; } /** @var StoreInterface $store */ $store = $context->getExtensionAttributes()->getStore(); - $baseUrl = $store->getBaseUrl('media'); + $baseUrl = $store->getBaseUrl(); - $mediaPath = $this->directoryList->getUrlPath('media'); - $pos = strpos($imagePath, $mediaPath); - if ($pos !== false) { - $imagePath = substr($imagePath, $pos + strlen($mediaPath), strlen($baseUrl)); + $filenameWithMedia = $this->fileInfo->isBeginsWithMediaDirectoryPath($imagePath) + ? $imagePath : $this->formatFileNameWithMediaCategoryFolder($imagePath); + + if (!$this->fileInfo->isExist($filenameWithMedia)) { + throw new GraphQlInputException(__('Category image not found.')); } - $imageUrl = rtrim($baseUrl, '/') . '/' . ltrim($imagePath, '/'); - return $imageUrl; + // return full url + return rtrim($baseUrl, '/') . $filenameWithMedia; + } + + /** + * Format category media folder to filename + * + * @param string $fileName + * @return string + */ + private function formatFileNameWithMediaCategoryFolder(string $fileName): string + { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + $baseFileName = basename($fileName); + return '/' + . $this->directoryList->getUrlPath('media') + . '/' + . ltrim(FileInfo::ENTITY_MEDIA_PATH, '/') + . '/' + . $baseFileName; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php index 973b8fbcd6b0f..c2ce239ea74bf 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php @@ -17,6 +17,10 @@ */ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface { + private const PRODUCT_BASE_TYPE = 'SimpleProduct'; + + private const PRODUCT_FILTER_INPUT = 'ProductAttributeFilterInput'; + /** * @var ConfigInterface */ @@ -51,9 +55,9 @@ public function __construct( */ public function getEntityAttributes() : array { - $productTypeSchema = $this->config->getConfigElement('SimpleProduct'); + $productTypeSchema = $this->config->getConfigElement(self::PRODUCT_BASE_TYPE); if (!$productTypeSchema instanceof Type) { - throw new \LogicException(__("SimpleProduct type not defined in schema.")); + throw new \LogicException(__("%1 type not defined in schema.", self::PRODUCT_BASE_TYPE)); } $fields = []; @@ -69,6 +73,9 @@ public function getEntityAttributes() : array } } + $productAttributeFilterFields = $this->getProductAttributeFilterFields(); + $fields = array_merge($fields, $productAttributeFilterFields); + foreach ($this->additionalAttributes as $attributeName) { $fields[$attributeName] = [ 'type' => 'String', @@ -78,4 +85,24 @@ public function getEntityAttributes() : array return $fields; } + + /** + * Get fields from ProductAttributeFilterInput + * + * @return array + */ + private function getProductAttributeFilterFields() + { + $filterFields = []; + + $productAttributeFilterSchema = $this->config->getConfigElement(self::PRODUCT_FILTER_INPUT); + $productAttributeFilterFields = $productAttributeFilterSchema->getFields(); + foreach ($productAttributeFilterFields as $filterField) { + $filterFields[$filterField->getName()] = [ + 'type' => 'String', + 'fieldName' => $filterField->getName(), + ]; + } + return $filterFields; + } } diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 8da50beacb2fe..d4b98b311fca4 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -199,11 +199,17 @@ type CustomizableFileValue @doc(description: "CustomizableFileValue defines the interface MediaGalleryInterface @doc(description: "Contains basic information about a product image or video.") @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\MediaGalleryTypeResolver") { url: String @doc(description: "The URL of the product image or video.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\MediaGallery\\Url") label: String @doc(description: "The label of the product image or video.") + position: Int @doc(description: "The media item's position after it has been sorted.") + disabled: Boolean @doc(description: "Whether the image is hidden from view.") } type ProductImage implements MediaGalleryInterface @doc(description: "Product image information. Contains the image URL and label.") { } +type ProductVideo implements MediaGalleryInterface @doc(description: "Contains information about a product video.") { + video_content: ProductMediaGalleryEntriesVideoContent @doc(description: "Contains a ProductMediaGalleryEntriesVideoContent object.") +} + interface CustomizableOptionInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\CustomizableOptionTypeResolver") @doc(description: "The CustomizableOptionInterface contains basic information about a customizable option. It can be implemented by several types of configurable options.") { title: String @doc(description: "The display name for this option.") required: Boolean @doc(description: "Indicates whether the option is required.") @@ -403,7 +409,7 @@ type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characterist media_type: String @doc(description: "image or video.") label: String @doc(description: "The alt text displayed on the UI when the user points to the image.") position: Int @doc(description: "The media item's position after it has been sorted.") - disabled: Boolean @doc(description: "Whether the image is hidden from vie.") + disabled: Boolean @doc(description: "Whether the image is hidden from view.") types: [String] @doc(description: "Array of image types. It can have the following values: image, small_image, thumbnail.") file: String @doc(description: "The path of the image on the server.") content: ProductMediaGalleryEntriesContent @doc(description: "Contains a ProductMediaGalleryEntriesContent object.") @@ -466,7 +472,3 @@ type StoreConfig @doc(description: "The type contains information about a store catalog_default_sort_by : String @doc(description: "Default Sort By.") root_category_id: Int @doc(description: "The ID of the root category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\RootCategoryId") } - -type ProductVideo @doc(description: "Contains information about a product video.") implements MediaGalleryInterface { - video_content: ProductMediaGalleryEntriesVideoContent @doc(description: "Contains a ProductMediaGalleryEntriesVideoContent object.") -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php index eebed5aab6cc9..1eb481bc561d2 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php @@ -7,16 +7,14 @@ namespace Magento\QuoteGraphQl\Model\Resolver\ShippingAddress; -use Magento\Directory\Model\Currency; use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Api\Data\ShippingMethodInterface; use Magento\Quote\Model\Cart\ShippingMethodConverter; -use Magento\Store\Api\Data\StoreInterface; +use Magento\Quote\Model\Quote\TotalsCollector; /** * @inheritdoc @@ -33,16 +31,24 @@ class AvailableShippingMethods implements ResolverInterface */ private $shippingMethodConverter; + /** + * @var TotalsCollector + */ + private $totalsCollector; + /** * @param ExtensibleDataObjectConverter $dataObjectConverter * @param ShippingMethodConverter $shippingMethodConverter + * @param TotalsCollector $totalsCollector */ public function __construct( ExtensibleDataObjectConverter $dataObjectConverter, - ShippingMethodConverter $shippingMethodConverter + ShippingMethodConverter $shippingMethodConverter, + TotalsCollector $totalsCollector ) { $this->dataObjectConverter = $dataObjectConverter; $this->shippingMethodConverter = $shippingMethodConverter; + $this->totalsCollector = $totalsCollector; } /** @@ -61,9 +67,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $address->setCollectShippingRates(true); - $address->collectShippingRates(); $cart = $address->getQuote(); - + $this->totalsCollector->collectAddressTotals($cart, $address); $methods = []; $shippingRates = $address->getGroupedAllShippingRates(); foreach ($shippingRates as $carrierRates) { @@ -88,7 +93,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value * @param array $data * @param string $quoteCurrencyCode * @return array - * @throws NoSuchEntityException */ private function processMoneyTypeData(array $data, string $quoteCurrencyCode): array { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php index 96e8ae79b612e..a86c34e57b2bb 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryListTest.php @@ -7,6 +7,8 @@ namespace Magento\GraphQl\Catalog; +use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; +use Magento\Framework\ObjectManagerInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -16,6 +18,16 @@ */ class CategoryListTest extends GraphQlAbstract { + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + } + /** * @magentoApiDataFixture Magento/Catalog/_files/categories.php * @dataProvider filterSingleCategoryDataProvider @@ -333,7 +345,7 @@ public function testEmptyFiltersReturnRootCategory() } } QUERY; - $storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); + $storeManager = $this->objectManager->get(StoreManagerInterface::class); $storeRootCategoryId = $storeManager->getStore()->getRootCategoryId(); $result = $this->graphQlQuery($query); @@ -369,6 +381,46 @@ public function testMinimumMatchQueryLength() $this->assertEquals([], $result['categoryList']); } + /** + * Test category image full name is returned + * + * @magentoApiDataFixture Magento/Catalog/_files/catalog_category_with_long_image_name.php + */ + public function testCategoryImageName() + { + /** @var CategoryCollection $categoryCollection */ + $categoryCollection = $this->objectManager->get(CategoryCollection::class); + $categoryModel = $categoryCollection + ->addAttributeToSelect('image') + ->addAttributeToFilter('name', ['eq' => 'Parent Image Category']) + ->getFirstItem(); + $categoryId = $categoryModel->getId(); + + $query = <<<QUERY + { +categoryList(filters: {ids: {in: ["$categoryId"]}}) { + id + name + image + } +} +QUERY; + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $storeBaseUrl = $storeManager->getStore()->getBaseUrl('media'); + + $expected = "catalog/category/magento_long_image_name_magento_long_image_name_magento_long_image_name.jpg"; + $expectedImageUrl = rtrim($storeBaseUrl, '/') . '/' . $expected; + + $response = $this->graphQlQuery($query); + $categoryList = $response['categoryList']; + $this->assertArrayNotHasKey('errors', $response); + $this->assertNotEmpty($response['categoryList']); + $expectedImageUrl = str_replace('index.php/', '', $expectedImageUrl); + $categoryList[0]['image'] = str_replace('index.php/', '', $categoryList[0]['image']); + $this->assertEquals('Parent Image Category', $categoryList[0]['name']); + $this->assertEquals($expectedImageUrl, $categoryList[0]['image']); + } + /** * @return array */ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 480388db98d2f..da6a3604f1c67 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -13,6 +13,8 @@ use Magento\Catalog\Model\CategoryRepository; use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; use Magento\Framework\DataObject; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException; @@ -33,10 +35,22 @@ class CategoryTest extends GraphQlAbstract */ private $categoryRepository; + /** + * @var Store + */ + private $store; + + /** + * @var MetadataPool + */ + private $metadataPool; + protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $this->categoryRepository = $this->objectManager->get(CategoryRepository::class); + $this->store = $this->objectManager->get(Store::class); + $this->metadataPool = $this->objectManager->get(MetadataPool::class); } /** @@ -211,7 +225,7 @@ public function testCategoriesTreeWithDisabledCategory() productImagePreview: products(pageSize: 1) { items { id - } + } } } } @@ -557,10 +571,13 @@ public function testBreadCrumbs() /** * Test category image is returned as full url (not relative path) * + * @param string $imagePrefix * @magentoApiDataFixture Magento/Catalog/_files/catalog_category_with_image.php + * @dataProvider categoryImageDataProvider */ - public function testCategoryImage() + public function testCategoryImage(?string $imagePrefix) { + /** @var CategoryCollection $categoryCollection */ $categoryCollection = $this->objectManager->get(CategoryCollection::class); $categoryModel = $categoryCollection ->addAttributeToSelect('image') @@ -568,6 +585,35 @@ public function testCategoryImage() ->getFirstItem(); $categoryId = $categoryModel->getId(); + if ($imagePrefix !== null) { + // update image to account for different stored image formats + $connection = $categoryCollection->getConnection(); + $productLinkField = $this->metadataPool + ->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) + ->getLinkField(); + + $defaultStoreId = $this->store->getId(); + + $imageAttributeValue = $imagePrefix . basename($categoryModel->getImage()); + + if (!empty($imageAttributeValue)) { + $query = sprintf( + 'UPDATE %s SET `value` = "%s" ' . + 'WHERE `%s` = %d ' . + 'AND `store_id`= %d ' . + 'AND `attribute_id` = ' . + '(SELECT `ea`.`attribute_id` FROM %s ea WHERE `ea`.`attribute_code` = "image" LIMIT 1)', + $connection->getTableName('catalog_category_entity_varchar'), + $imageAttributeValue, + $productLinkField, + $categoryModel->getData($productLinkField), + $defaultStoreId, + $connection->getTableName('eav_attribute') + ); + $connection->query($query); + } + } + $query = <<<QUERY { categoryList(filters: {ids: {in: ["$categoryId"]}}) { @@ -591,17 +637,41 @@ public function testCategoryImage() $this->assertNotEmpty($response['categoryList']); $categoryList = $response['categoryList']; $storeBaseUrl = $this->objectManager->get(StoreManagerInterface::class)->getStore()->getBaseUrl('media'); - $expectedImageUrl = rtrim($storeBaseUrl, '/'). '/' . ltrim($categoryModel->getImage(), '/'); + $expectedImageUrl = rtrim($storeBaseUrl, '/') . '/' . ltrim($categoryModel->getImage(), '/'); + $expectedImageUrl = str_replace('index.php/', '', $expectedImageUrl); $this->assertEquals($categoryId, $categoryList[0]['id']); $this->assertEquals('Parent Image Category', $categoryList[0]['name']); + $categoryList[0]['image'] = str_replace('index.php/', '', $categoryList[0]['image']); $this->assertEquals($expectedImageUrl, $categoryList[0]['image']); $childCategory = $categoryList[0]['children'][0]; $this->assertEquals('Child Image Category', $childCategory['name']); + $childCategory['image'] = str_replace('index.php/', '', $childCategory['image']); $this->assertEquals($expectedImageUrl, $childCategory['image']); } + /** + * @return array + */ + public function categoryImageDataProvider(): array + { + return [ + 'default_filename_strategy' => [ + 'image_prefix' => null + ], + 'just_filename_strategy' => [ + 'image_prefix' => '' + ], + 'with_pub_media_strategy' => [ + 'image_prefix' => '/pub/media/catalog/category/' + ], + 'catalog_category_strategy' => [ + 'image_prefix' => 'catalog/category/' + ], + ]; + } + /** * @param ProductInterface $product * @param array $actualResponse diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/MediaGalleryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/MediaGalleryTest.php index b6687b4e171d3..86d36c1c767f7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/MediaGalleryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/MediaGalleryTest.php @@ -14,16 +14,6 @@ */ class MediaGalleryTest extends GraphQlAbstract { - /** - * @var \Magento\TestFramework\ObjectManager - */ - private $objectManager; - - protected function setUp() - { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - } - /** * @magentoApiDataFixture Magento/Catalog/_files/product_with_image.php */ @@ -38,7 +28,7 @@ public function testProductSmallImageUrlWithExistingImage() url } } - } + } } QUERY; $response = $this->graphQlQuery($query); @@ -57,7 +47,7 @@ public function testMediaGalleryTypesAreCorrect() $query = <<<QUERY { products(filter: {sku: {eq: "{$productSku}"}}) { - items { + items { media_gallery_entries { label media_type @@ -65,7 +55,7 @@ public function testMediaGalleryTypesAreCorrect() types } } - } + } } QUERY; $response = $this->graphQlQuery($query); @@ -91,13 +81,15 @@ public function testMediaGallery() $query = <<<QUERY { products(filter: {sku: {eq: "{$productSku}"}}) { - items { + items { media_gallery { label url + position + disabled } } - } + } } QUERY; $response = $this->graphQlQuery($query); @@ -105,9 +97,13 @@ public function testMediaGallery() $mediaGallery = $response['products']['items'][0]['media_gallery']; $this->assertCount(2, $mediaGallery); $this->assertEquals('Image Alt Text', $mediaGallery[0]['label']); - self::assertTrue($this->checkImageExists($mediaGallery[0]['url'])); + $this->assertEquals(1, $mediaGallery[0]['position']); + $this->assertFalse($mediaGallery[0]['disabled']); + $this->assertTrue($this->checkImageExists($mediaGallery[0]['url'])); $this->assertEquals('Thumbnail Image', $mediaGallery[1]['label']); - self::assertTrue($this->checkImageExists($mediaGallery[1]['url'])); + $this->assertEquals(2, $mediaGallery[1]['position']); + $this->assertFalse($mediaGallery[1]['disabled']); + $this->assertTrue($this->checkImageExists($mediaGallery[1]['url'])); } /** @@ -119,10 +115,12 @@ public function testMediaGalleryForProductVideos() $query = <<<QUERY { products(filter: {sku: {eq: "{$productSku}"}}) { - items { + items { media_gallery { label url + position + disabled ... on ProductVideo { video_content { media_type @@ -135,7 +133,7 @@ public function testMediaGalleryForProductVideos() } } } - } + } } QUERY; $response = $this->graphQlQuery($query); @@ -143,7 +141,9 @@ public function testMediaGalleryForProductVideos() $mediaGallery = $response['products']['items'][0]['media_gallery']; $this->assertCount(1, $mediaGallery); $this->assertEquals('Video Label', $mediaGallery[0]['label']); - self::assertTrue($this->checkImageExists($mediaGallery[0]['url'])); + $this->assertTrue($this->checkImageExists($mediaGallery[0]['url'])); + $this->assertFalse($mediaGallery[0]['disabled']); + $this->assertEquals(2, $mediaGallery[0]['position']); $this->assertNotEmpty($mediaGallery[0]['video_content']); $video_content = $mediaGallery[0]['video_content']; $this->assertEquals('external-video', $video_content['media_type']); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 9ac5f6959d12e..232a081228648 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -156,7 +156,7 @@ public function testLayeredNavigationForConfigurableProducts() CacheCleaner::cleanAll(); $attributeCode = 'test_configurable'; - /** @var \Magento\Eav\Model\Config $eavConfig */ + /** @var Config $eavConfig */ $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(Config::class); $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); /** @var AttributeOptionInterface[] $options */ @@ -262,15 +262,13 @@ public function testFilterProductsByDropDownCustomAttribute() $optionValue = $this->getDefaultAttributeOptionValue($attributeCode); $query = <<<QUERY { - products(filter:{ - $attributeCode: {eq: "{$optionValue}"} - } - - pageSize: 3 - currentPage: 1 - ) + products( + filter:{ $attributeCode: {eq: "{$optionValue}"} } + pageSize: 3 + currentPage: 1 + ) { - total_count + total_count items { name @@ -291,7 +289,6 @@ public function testFilterProductsByDropDownCustomAttribute() value_string __typename } - } aggregations{ attribute_code @@ -335,7 +332,7 @@ public function testFilterProductsByDropDownCustomAttribute() ); } - /** @var \Magento\Eav\Model\Config $eavConfig */ + /** @var Config $eavConfig */ $eavConfig = $objectManager->get(Config::class); $attribute = $eavConfig->getAttribute('catalog_product', 'second_test_configurable'); // Validate custom attribute filter layer data from aggregations @@ -380,8 +377,8 @@ public function testFilterProductsByMultiSelectCustomAttributes() $objectManager = Bootstrap::getObjectManager(); $this->reIndexAndCleanCache(); $attributeCode = 'multiselect_attribute'; - /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = $objectManager->get(\Magento\Eav\Model\Config::class); + /** @var Config $eavConfig */ + $eavConfig = $objectManager->get(Config::class); $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); /** @var AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); @@ -439,6 +436,7 @@ public function testFilterProductsByMultiSelectCustomAttributes() QUERY; $response = $this->graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response, 'Response has errors.'); $this->assertEquals(3, $response['products']['total_count']); $this->assertNotEmpty($response['products']['filters']); $this->assertNotEmpty($response['products']['aggregations']); @@ -452,8 +450,8 @@ public function testFilterProductsByMultiSelectCustomAttributes() */ private function getDefaultAttributeOptionValue(string $attributeCode) : string { - /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + /** @var Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(Config::class); $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); /** @var AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); @@ -519,9 +517,7 @@ public function testSearchAndFilterByCustomAttribute() value } } - - } - + } } QUERY; $response = $this->graphQlQuery($query); @@ -555,7 +551,7 @@ public function testSearchAndFilterByCustomAttribute() ); } - // Validate the price layer of aggregations from the response + // Validate the price layer of aggregations from the response $this->assertResponseFields( $response['products']['aggregations'][0], [ @@ -695,7 +691,7 @@ public function testFilterByCategoryIdAndCustomAttribute() //Validate the number of categories/sub-categories that contain the products with the custom attribute $this->assertCount(6, $actualCategoriesFromResponse); - $expectedCategoryInAggregrations = + $expectedCategoryInAggregations = [ [ 'count' => 2, @@ -732,9 +728,9 @@ public function testFilterByCategoryIdAndCustomAttribute() ], ]; // presort expected and actual results as different search engines have different orders - usort($expectedCategoryInAggregrations, [$this, 'compareLabels']); + usort($expectedCategoryInAggregations, [$this, 'compareLabels']); usort($actualCategoriesFromResponse, [$this, 'compareLabels']); - $categoryInAggregations = array_map(null, $expectedCategoryInAggregrations, $actualCategoriesFromResponse); + $categoryInAggregations = array_map(null, $expectedCategoryInAggregations, $actualCategoriesFromResponse); //Validate the categories and sub-categories data in the filter layer foreach ($categoryInAggregations as $index => $categoryAggregationsData) { @@ -971,8 +967,8 @@ public function testFilterByMultipleProductUrlKeys() */ private function getExpectedFiltersDataSet() { - /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + /** @var Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(Config::class); $attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); /** @var \Magento\Eav\Api\Data\AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php index f950d35f54658..ded3caa0d812c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php @@ -82,6 +82,51 @@ public function testGetAvailableShippingMethods() ); } + /** + * Test case: get available shipping methods from current customer quote with configurable product + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/CatalogRule/_files/configurable_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_configurable_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testGetAvailableShippingMethodsWithConfigurableProduct() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $response = $this->graphQlQuery($this->getQuery($maskedQuoteId), [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + self::assertCount(1, $response['cart']['shipping_addresses']); + self::assertArrayHasKey('available_shipping_methods', $response['cart']['shipping_addresses'][0]); + self::assertCount(1, $response['cart']['shipping_addresses'][0]['available_shipping_methods']); + + $expectedAddressData = [ + 'amount' => [ + 'value' => 5, + 'currency' => 'USD', + ], + 'carrier_code' => 'flatrate', + 'carrier_title' => 'Flat Rate', + 'error_message' => '', + 'method_code' => 'flatrate', + 'method_title' => 'Fixed', + 'price_incl_tax' => [ + 'value' => 5, + 'currency' => 'USD', + ], + 'price_excl_tax' => [ + 'value' => 5, + 'currency' => 'USD', + ], + ]; + self::assertEquals( + $expectedAddressData, + $response['cart']['shipping_addresses'][0]['available_shipping_methods'][0] + ); + } + /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php index 867aaab7b3a58..9a1eea82686e5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php @@ -81,6 +81,57 @@ public function testGetAvailableShippingMethods() ); } + /** + * Test case: get available shipping methods from current customer quote with configurable product + * + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/CatalogRule/_files/configurable_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_configurable_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testGetAvailableShippingMethodsWithConfigurableProduct() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $response = $this->graphQlQuery($this->getQuery($maskedQuoteId)); + + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + self::assertCount(1, $response['cart']['shipping_addresses']); + self::assertArrayHasKey('available_shipping_methods', $response['cart']['shipping_addresses'][0]); + self::assertCount(1, $response['cart']['shipping_addresses'][0]['available_shipping_methods']); + + $expectedAddressData = [ + 'amount' => [ + 'value' => 5, + 'currency' => 'USD', + ], + 'carrier_code' => 'flatrate', + 'carrier_title' => 'Flat Rate', + 'error_message' => '', + 'method_code' => 'flatrate', + 'method_title' => 'Fixed', + 'price_incl_tax' => [ + 'value' => 5, + 'currency' => 'USD', + ], + 'price_excl_tax' => [ + 'value' => 5, + 'currency' => 'USD', + ], + 'base_amount' => null, + ]; + self::assertEquals( + $expectedAddressData, + $response['cart']['shipping_addresses'][0]['available_shipping_methods'][0] + ); + self::assertCount(2, $response['cart']['shipping_addresses'][0]['cart_items']); + self::assertCount(2, $response['cart']['shipping_addresses'][0]['cart_items_v2']); + self::assertEquals( + 'configurable', + $response['cart']['shipping_addresses'][0]['cart_items_v2'][0]['product']['sku'] + ); + } + /** * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_image.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_image.php index 0764d466898b8..3491065323c9f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_image.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_image.php @@ -13,7 +13,10 @@ $mediaDirectory = $objectManager->get(\Magento\Framework\Filesystem::class) ->getDirectoryWrite(DirectoryList::MEDIA); $fileName = 'magento_small_image.jpg'; +$fileNameLong = 'magento_long_image_name_magento_long_image_name_magento_long_image_name.jpg'; $filePath = 'catalog/category/' . $fileName; +$filePathLong = 'catalog/category/' . $fileNameLong; $mediaDirectory->create('catalog/category'); copy(__DIR__ . DIRECTORY_SEPARATOR . $fileName, $mediaDirectory->getAbsolutePath($filePath)); +copy(__DIR__ . DIRECTORY_SEPARATOR . $fileNameLong, $mediaDirectory->getAbsolutePath($filePathLong)); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_image.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_image.php index 934abffcb9c5b..d8dfdbcd84993 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_image.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_image.php @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -require_once 'catalog_category_image.php'; +require 'catalog_category_image.php'; /** @var $category \Magento\Catalog\Model\Category */ $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_long_image_name.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_long_image_name.php new file mode 100644 index 0000000000000..f1fa3ee3318ee --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_long_image_name.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require 'catalog_category_image.php'; + +/** @var $category \Magento\Catalog\Model\Category */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +$categoryParent = $objectManager->create(\Magento\Catalog\Model\Category::class); +$categoryParent->setName('Parent Image Category') + ->setPath('1/2') + ->setLevel(2) + ->setImage($filePathLong) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setPosition(1) + ->save(); + +$categoryChild = $objectManager->create(\Magento\Catalog\Model\Category::class); +$categoryChild->setName('Child Image Category') + ->setPath($categoryParent->getPath()) + ->setLevel(3) + ->setImage($fileNameLong) + ->setAvailableSortBy('name') + ->setDefaultSortBy('name') + ->setIsActive(true) + ->setPosition(2) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_long_image_name_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_long_image_name_rollback.php new file mode 100644 index 0000000000000..baea438e9340c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/catalog_category_with_long_image_name_rollback.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Framework\ObjectManagerInterface $objectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = $objectManager->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */ +$collection = $objectManager->create(\Magento\Catalog\Model\ResourceModel\Category\Collection::class); +$collection + ->addAttributeToFilter('name', ['in' => ['Parent Image Category', 'Child Image Category']]) + ->load() + ->delete(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/magento_long_image_name_magento_long_image_name_magento_long_image_name.jpg b/dev/tests/integration/testsuite/Magento/Catalog/_files/magento_long_image_name_magento_long_image_name_magento_long_image_name.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bed66dfbcb1c336ea02db176ce9b36f2e3e82261 GIT binary patch literal 4025 zcmV;q4@U5bP)<h;3K|Lk000e1NJLTq00DOZ004#v0{{R35vMxD0002tP)t-s|NsB+ zN)aO;Ao5rl0002||Nrne0Ps8j{`~y#LjWHe9Pcs!^W4|)NDK1DzWT<z^NDltP#+l- z74c+2^yuYvb9A+}wKp_1Xl7^h^z<AT7<qVj%*)FW3JeGU0P<B4rlqCxZ$yKEg7QrO znV6X4<KvN#kk!@I!@|O%p`j`!C+h0zK07>&i;JzSt=ih!SXNd@MMi07XI)!c@?$Xb zgJbh{PxD_WJ2*M>qm%WqpZnFz^pSS+X*&An;`Dk|_MC(DubuY3uk?s%?{8A{os95T zEAM(>?~8TscwO&mNl`0m%>V!nlSxEDRCt{2U29|7HWvmW2XK<EbA!Rgb|_b4?$Arx zZoO^Y_U`}xpx4roY{{0vX2c|oo-YkZ7V@K`qvssSZl$82prD|jprD|jprD|jprCLP zG+vy$2hS3pE}HK?pSu&!4!3WccNcff59jX3^Mdv<WF2F0@#)-sc}BQ?Gq|9*Yrbuq zyF1SWjo+xT0|w2H=kC$7!1ux6Ld4+r>vMPNr^n}a&5IQ7nqQT(c~JZiTnuOOcfXvw zYd<kQy-&KB7|kzA%s&i>WY2~Y^A7^;<6s}_#M!)8V*b?NB>O&-|3j^>r$j=sQ}dQg zfWhGVx%>JP;g^(T$F}|3^=ru-82ql**V8~M*#~c~E1H&$p_G_E5ws5y7jrlGrjbJH z519i}V*WovCfV<rZ*N(-Ce`%KFSq*|RX6}kCfNrU{6>=|pAbsSACFM7OWk*}+U*zF z0-(hFk+}X{;z0(lC2~c3aVHxfCFYMnhGdt^bItl%pteFbG9-JZ1#9_dhe9SkOFj0R zeI8rOxf&>BVy|T1bJ?vD^Zx~MMgBI2B3EMmcw|JN`Ob!AiTQ62-07ze`Fi>__x0MX zG@J8Yt*?8by-c%KP?Tu0p{pw=zZW6z)vCyPVG*7d^VO>2eLo2A)tI#~=(LV#MG*60 zgW0@Krag@5zF+Z4mEN*=N1hCT_lWuZk{uumr8qHvSKZ6|0(lt7-17q-RMB(Y!P5le zoj98;$^Pjb|LIn`!NkNoZ7q}czFWhv*V%pYQ4Q+ky@@A?nD=URAlKcuZ=0`$&&`1} z=NpRoyQr;9-a7`6ibk1z+OaA}c)SO|Y+{aEr*4$v{pMGhvE$<$cM0Aza=zO2<ze18 zHjwHLv$`~+=3Yay0ypwt=%COuwol!vz`LZJ9Op>!o{{s-!~4Asr0PLdm!=-$eO2U6 z9tzuV_UR?_PIJR)-tlSdwA{?YJM|$Ut4>CZwtlEaspyo?I}3<pyvq)7<lw!*oJh^s z%4<xb8v8U)c_^Lt&+nKfm-C)Jz>$OZ$a!Zjo84$ec)g=<>x3wocRt~j!Fwv_n}hci z;hktnPZ}4&gfR9my=9_Y-alV(0ca-gIOofi<lx=Gw7NaKbm|*f1rt~Brg&uB?ti_z zQ=nYlU(_#h@qTy3xtbij6I#)1qi5+AtTXk%LVH$#a#1$#T)Hfq_iD8u?@Mgys6qa5 z7wPg50tbT*#I)rhH}6)r+wE7nVbEb776kivkC@Nm9akyH`w%-2M7_A1E+4xPFyV52 zF1fkcY?d9yTKftQlx9^S`u#pv?d2q#ui>IJKfEyQ=bfg@<h;XG1$ke%z&BAX$WN6U zp#w5@AmS6D)o9tBBS6qiUpX3XNLei|oAf(TvEQwc;`vzcZj<qFIHu$|eNx5lPH3Ss z91f?%B?dzR&W<Mw=#1N0M%?$pfMe0BIA&jd^BfNF4$RAVXIB;Ey=4HOM5SYbMUZ6| z562p(6TG<X)gnNi!6GW%K(@&IY>Ql*mDNIiEWhg^myMhmAbPRwqBWNqGLh>-eCD^2 z3snq6hie)Z((hah6e$5`&MMao5A`YYA@A1EpY$t-7p8-}V`n4D6I74n9rJ19?vs2$ zO2U&#U8~UaSyViWIEkPmeswUBA(VAG2yrcR5P!9SVg+k@AZV=}U&RH#((`H}m}6`S z?bTe*XS|PwmEEv%cwx%Hdua1X-np`Zykogybl5$)Q?ZoD7<XKu>EbA7X|ZTaw++Ll z_sp?3CFOQF>q8qS)UuQ&b}H6A`A&Bc@*oS1A_t6lcl+Ty<Hs!=imsG*$ka79@V#z? zouLCw8Z=jxkzW~(DktzW7|mA#@0Zo%d9Pvr-C|TGbpogz%r>mA6aF&HTd>~k)&YXM z_1JVE-l>MxzEx?g#vb!G;UJK)*$__AeCqpV-zLmQD+}h`-YpS~i>D*nHtEEaxO*TL zKrY+3Fv`KM-`XHLk_ihs3GX+j<DD2Z@enOTV3uI6GxbPJEnaumh8wPk4kdcE_5_31 zb^gw0ZA_IuZ;gAt6B=lr1Zgg9tI-E3MVm6A5RalgURf2(0ai(@Jn|jK!JbiXI|M!+ zjV}3dZv$PohTwA&cX9|N^KL;jivNVjRR~E}C?Xre*2t4|y|J)Qg4cm)Sti!%#&g@k zYe6JJS76KW<wL%@NnJ_kDcFsW9fCM^NaledZ-?Ta8<Anp&TC6iGVe2vPLl8S_{<s> znYuy#Bzt{vVl25i1zw8YDj~|?O2J*xEHc@>5>m>fK3#Ds??x?Fmykk=+Dpbw<Z8iQ zAlHWqZNrz$`y{q#*xwLxy+-7O;O5B+D+kA&75grV26r(DfQ=4+t-}pI@a}H-;SY#G zuuloz$sG{M6~jb@4ZdXFi5XK@XeDz2Pza_CbY&;Ty0J>+yc0vDnpl7=I^Q5#Mu7K3 zLW0bYXd)(fCxKrwl{&YJdiaugH#us-Npt(#_<k7$hZ1A`n0JV^guSVq9ksMc2fiRu zUD9#WOI-{reirY6Ebf4GQ}*mkqh#K}x3IVz)%YYkag&kXA(KIxDXv&6f64ioNt$<J z?_|}5@k{7wZekVXeI|{wtv$V!qh#J;)-!7f1Y0Cy61i2-g30SGf4mDq9Ss_t^Y5OJ z0yHAU0%~U67qE?!Lq&P_MO$IG7nXz^D4BQS9wMm#qrh$&>zh~&l5~`Ka*1TVTq6$A z=>P_L!UUao=D-;!-4#IMC=g3Uc~8U@3^0-f&YhIZd&l4{9Gn0>6ExvnKTV7w?oxll z<!E%C_i=LR<ebnl8R|y>qb<t2Y?-dm)wamOS~Bk&TkwN?_6sW%uBas>#ww!?V&Q)n z{iAlk@lJfMZbx-VKuHxgq*x3I5LpI`@(u*2Hp;}An*$~DPFzAPra2|u5LQ)ogUtMW znkI2k(CU%kpC6Vfgrsosh$Ino1Ih^RMyH!bi&TFmyt|xZ!N4Il7&$1+xG`Zu)03(w z7_x!r3e#Z=5u>;3w%R1KsIw~}bj?0^b|OEMa&?DLF7I)kSGWyK3-;$+j)f8i3z+4R zMb_n_6_NK!7m`z<`yl_iE~CIt2UP-!@;;TXtF1&RauDa>olBX{Sd0(iowG}mQ->tZ zvG4g{CBb{ezmL~k7qy~-N%9vHI8H~V+Jlw6z-t1^<$aWr+B+&N@kA8@4~M%!(0xLT znB;vD@=lZF!Gg29Svox<peXN>bWex|HjF}tJ<8?XNzHoZ2e6|TGJ2~x+a=49C3rVO zKV#2Vj&Dv9BAUeYl7kEE!lHQs$j&D$+eu`i52IY({d7o=2{*RH9S9aZixXp#t3*9R ztx!76vkop$<mCNcEKHu5-R}Pp3CiU?-p7kTlz_d~lY1aUSK7PyDnTq*Z6Q5{7l4du z?#T8NL(ITBbMhYNeSJxLBal<l-_Jm~yie(=Bo44?pr>V6oS-#R-f5+C`I!o}H+$Zb z=|#w<Cw+j4fV#uHgDuB*3GE3--C!%ENIJ4)-WNhz48WddvJ*+L_~g<gHhsVp33&p# z(^s>@NOsd-PQ>=13<<1EHb~E#2PTe+I}&cDc9*uWRbJ?&N9FQP?kMq?9(#H(dD<P0 zuHwJoP#-%l(=gkJCTQXj+^}O9Ha<iUY<rw9o5S2#Gjc6+jicYZd0@yiAo9R1`ECuz z6T0-rCv%{^*h3QK^1ibr`(b**cv;<eez)Mfe0yw~!;XiI-CK*>50+iyfO9za(UZwD zm>qUF9E<UyHK#{2^5%hIyS3(_U>+d&Epok!wo}s_)4UNXcxJp@-rKrldtd=4jgpTl zcH`74eMm%iJr8oYs89AqBHm4WmdW4_@`aVXXftj?-I8wL955XrG$&0T<~^EAx~{_< z8PQWw2JR0vnxaR>%jKPH{3kbSs2WHnH~L%#%8Z}RsnK(GcnB%)4RXc~?VM%z9v=x~ zwjbHxDo7o;V<*OP@IFG(WTK1vY%cUCoJxh(RE%<YM>zkQdIooG=(?R!dQ(!FGU7+M zcRifn*iv_1SPJ`1Z5->;b4k|9T<kn{h^xmMThJXUFnfd+(M;igg`!2i<Icp^81o)W z5KK`t7-5Gt#UJ=AmG?%c6(V)c#597y?j^tVw_)9Fv>1-z7&U6s158U?O4w_M=4s4@ z)nU~KT4JNMmTSZgarZe%>X2U%d7I*1Zh3iPu06OmlB12Ny;{H6Nil%-^70{gzqva7 zdr)Q*$4O{JjD?5QP4fHMTu(WW5pH00<iq#YXf#S(I+AxNAG8mb&6D$9ua@>-C^pi& z+5{u<px@(zcKhe$%}IH0Uj8Vl?-iD6lk6z#kDPb>qN<zb>3F}nd{ySV#Y{m^#vrNL zLaD0qp;`Zg&gL|{H|yoTtJcZzCqqv@9*+y!^{e_Rc)z)NS@NyIqaZ26?vaf6$zL0D za%kGe%PRNzINq;*E&pBQkVHToPP$m;hGc6Wy}4s()=TcwalHRH@MhbGKqD|9h((+3 zcsiYqaR!F$gFlY<*A&B1yq^cwhUQ;v!*xBE9^})}eSz)$M}sHCdl<Xc5SI~yR6wnF z?p_^*C&fE{8au!lF#=EiJonSU6XQL^%rS?He!o9nI*;OgaL3{4@&0o_<oz$c=vwjq zaCjk*+q{YLT-pbrMDc!FSbsyZj9L8UpYSf#PrufM0l8BDUDj!#y+|_r4Ufi^bTkxB z2Tl8N+2on8|Mjgde9^p7FL{3mXrKQTolTSf)V%yhx%LNw_Pr@sI5!6MSJ&t6-A@e7 z`bY5Sk+Ii5p1W&5IkfA)>M3Kt`uizlSf4gD?VGs1R_kZTvOh;?xBuhT*HE&bA<O<e zp}nZqm1I8yn3!)im1Hj$+V#JeO0u5;n)Y2ub_E3m1qB5K1qB5K1qB5K1qB5K1qB5K f1qB6#=YszOstSxE(39(#00000NkvXXu0mjfVr%Rb literal 0 HcmV?d00001 diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries.php index e3a23073b5d31..eb2d56d6d0b3a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_media_gallery_entries.php @@ -4,54 +4,44 @@ * See COPYING.txt for license details. */ +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory; +use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory; +use Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface; +use Magento\Framework\Api\Data\ImageContentInterfaceFactory; +use Magento\Framework\Api\Data\VideoContentInterfaceFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + include __DIR__ . '/product_simple_with_full_option_set.php'; -/** - * @var \Magento\TestFramework\ObjectManager $objectManager - */ -$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); -/** - * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory - */ - -$mediaGalleryEntryFactory = $objectManager->get( - \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterfaceFactory::class -); +/** @var ProductAttributeMediaGalleryEntryInterfaceFactory $mediaGalleryEntryFactory */ +$mediaGalleryEntryFactory = $objectManager->get(ProductAttributeMediaGalleryEntryInterfaceFactory::class); -/** - * @var \Magento\Framework\Api\Data\ImageContentInterfaceFactory $imageContentFactory - */ -$imageContentFactory = $objectManager->get(\Magento\Framework\Api\Data\ImageContentInterfaceFactory::class); +/** @var ImageContentInterfaceFactory $imageContentFactory */ +$imageContentFactory = $objectManager->get(ImageContentInterfaceFactory::class); $imageContent = $imageContentFactory->create(); -$testImagePath = __DIR__ .'/magento_image.jpg'; +$testImagePath = __DIR__ . '/magento_image.jpg'; $imageContent->setBase64EncodedData(base64_encode(file_get_contents($testImagePath))); $imageContent->setType("image/jpeg"); $imageContent->setName("1.jpg"); $video = $mediaGalleryEntryFactory->create(); $video->setDisabled(false); -//$video->setFile('1.png'); $video->setFile('1.jpg'); $video->setLabel('Video Label'); $video->setMediaType('external-video'); $video->setPosition(2); $video->setContent($imageContent); -/** - * @var \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory - */ -$mediaGalleryEntryExtensionFactory = $objectManager->get( - \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory::class -); +/** @var ProductAttributeMediaGalleryEntryExtensionFactory $mediaGalleryEntryExtensionFactory */ +$mediaGalleryEntryExtensionFactory = $objectManager->get(ProductAttributeMediaGalleryEntryExtensionFactory::class); $mediaGalleryEntryExtension = $mediaGalleryEntryExtensionFactory->create(); -/** - * @var \Magento\Framework\Api\Data\VideoContentInterfaceFactory $videoContentFactory - */ -$videoContentFactory = $objectManager->get( - \Magento\Framework\Api\Data\VideoContentInterfaceFactory::class -); +/** @var VideoContentInterfaceFactory $videoContentFactory */ +$videoContentFactory = $objectManager->get(VideoContentInterfaceFactory::class); $videoContent = $videoContentFactory->create(); $videoContent->setMediaType('external-video'); $videoContent->setVideoDescription('Video description'); @@ -63,10 +53,6 @@ $mediaGalleryEntryExtension->setVideoContent($videoContent); $video->setExtensionAttributes($mediaGalleryEntryExtension); -/** - * @var \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface $mediaGalleryManagement - */ -$mediaGalleryManagement = $objectManager->get( - \Magento\Catalog\Api\ProductAttributeMediaGalleryManagementInterface::class -); +/** @var ProductAttributeMediaGalleryManagementInterface $mediaGalleryManagement */ +$mediaGalleryManagement = $objectManager->get(ProductAttributeMediaGalleryManagementInterface::class); $mediaGalleryManagement->create('simple', $video); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php index 72336c48410d5..795c612ea3be1 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php @@ -5,127 +5,108 @@ */ declare(strict_types=1); -// phpcs:ignore Magento2.Security.IncludeFile require __DIR__ . '/../../Catalog/_files/attribute_set_based_on_default_set.php'; -// phpcs:ignore Magento2.Security.IncludeFile require __DIR__ . '/../../Catalog/_files/categories.php'; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Catalog\Setup\CategorySetup; +use Magento\Eav\Model\Config; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Indexer\Model\Indexer; +use Magento\Indexer\Model\Indexer\Collection as IndexerCollection; use Magento\TestFramework\Helper\Bootstrap; use Magento\Eav\Api\AttributeRepositoryInterface; -$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); -$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); - -$eavConfig->clear(); - -$attribute1 = $eavConfig->getAttribute('catalog_product', ' second_test_configurable'); +$objectManager = Bootstrap::getObjectManager(); +/** @var Config $eavConfig */ +$eavConfig = $objectManager->get(Config::class); +/** @var AttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->create(AttributeRepositoryInterface::class); +/** @var CategorySetup $installer */ +$installer = $objectManager->create(CategorySetup::class); $eavConfig->clear(); -/** @var $installer \Magento\Catalog\Setup\CategorySetup */ -$installer = Bootstrap::getObjectManager()->create(\Magento\Catalog\Setup\CategorySetup::class); - +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); if (!$attribute->getId()) { - - /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ - $attribute = Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class - ); - - /** @var AttributeRepositoryInterface $attributeRepository */ - $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); - - $attribute->setData( - [ - 'attribute_code' => 'test_configurable', - 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), - 'is_global' => 1, - 'is_user_defined' => 1, - 'frontend_input' => 'select', - 'is_unique' => 0, - 'is_required' => 0, - 'is_searchable' => 1, - 'is_visible_in_advanced_search' => 1, - 'is_comparable' => 1, - 'is_filterable' => 1, - 'is_filterable_in_search' => 1, - 'is_used_for_promo_rules' => 0, - 'is_html_allowed_on_front' => 1, - 'is_visible_on_front' => 1, - 'used_in_product_listing' => 1, - 'used_for_sort_by' => 1, - 'frontend_label' => ['Test Configurable'], - 'backend_type' => 'int', - 'option' => [ - 'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']], - 'order' => ['option_0' => 1, 'option_1' => 2], - ], - 'default_value' => 'option_0' - ] - ); - + /** @var $attribute Attribute */ + $attribute->setData([ + 'attribute_code' => 'test_configurable', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'select', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 1, + 'used_in_product_listing' => 1, + 'used_for_sort_by' => 1, + 'frontend_label' => ['Test Configurable'], + 'backend_type' => 'int', + 'option' => [ + 'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']], + 'order' => ['option_0' => 1, 'option_1' => 2], + ], + 'default_value' => 'option_0' + ]); $attributeRepository->save($attribute); /* Assign attribute to attribute set */ $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); } -// create a second attribute -if (!$attribute1->getId()) { - - /** @var $attribute1 \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ - $attribute1 = Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class - ); - - /** @var AttributeRepositoryInterface $attributeRepository */ - $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); - $attribute1->setData( - [ - 'attribute_code' => 'second_test_configurable', - 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), - 'is_global' => 1, - 'is_user_defined' => 1, - 'frontend_input' => 'select', - 'is_unique' => 0, - 'is_required' => 0, - 'is_searchable' => 1, - 'is_visible_in_advanced_search' => 1, - 'is_comparable' => 1, - 'is_filterable' => 1, - 'is_filterable_in_search' => 1, - 'is_used_for_promo_rules' => 0, - 'is_html_allowed_on_front' => 1, - 'is_visible_on_front' => 1, - 'used_in_product_listing' => 1, - 'used_for_sort_by' => 1, - 'frontend_label' => ['Second Test Configurable'], - 'backend_type' => 'int', - 'option' => [ - 'value' => ['option_0' => ['Option 3'], 'option_1' => ['Option 4']], - 'order' => ['option_0' => 1, 'option_1' => 2], - ], - 'default' => ['option_0'] - ] - ); - - $attributeRepository->save($attribute1); +// create a second attribute +/** @var Attribute $secondAttribute */ +$secondAttribute = $eavConfig->getAttribute('catalog_product', ' second_test_configurable'); +if (!$secondAttribute->getId()) { + $secondAttribute->setData([ + 'attribute_code' => 'second_test_configurable', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'select', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 0, + 'is_visible_in_advanced_search' => 0, + 'is_comparable' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 1, + 'used_in_product_listing' => 1, + 'used_for_sort_by' => 1, + 'frontend_label' => ['Second Test Configurable'], + 'backend_type' => 'int', + 'option' => [ + 'value' => ['option_0' => ['Option 3'], 'option_1' => ['Option 4']], + 'order' => ['option_0' => 1, 'option_1' => 2], + ], + 'default' => ['option_0'] + ]); + $attributeRepository->save($secondAttribute); /* Assign attribute to attribute set */ $installer->addAttributeToGroup( 'catalog_product', $attributeSet->getId(), $attributeSet->getDefaultGroupId(), - $attribute1->getId() + $secondAttribute->getId() ); } $eavConfig->clear(); -/** @var \Magento\Framework\ObjectManagerInterface $objectManager */ -$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - -/** @var $productRepository \Magento\Catalog\Api\ProductRepositoryInterface */ -$productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); $productsWithNewAttributeSet = ['simple', '12345', 'simple-4']; foreach ($productsWithNewAttributeSet as $sku) { @@ -139,14 +120,14 @@ 'is_in_stock' => 1] ); $productRepository->save($product); - } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + } catch (NoSuchEntityException $e) { } } -/** @var \Magento\Indexer\Model\Indexer\Collection $indexerCollection */ -$indexerCollection = Bootstrap::getObjectManager()->get(\Magento\Indexer\Model\Indexer\Collection::class); -$indexerCollection->load(); -/** @var \Magento\Indexer\Model\Indexer $indexer */ + +/** @var IndexerCollection $indexerCollection */ +$indexerCollection = $objectManager->get(IndexerCollection::class)->load(); +/** @var Indexer $indexer */ foreach ($indexerCollection->getItems() as $indexer) { $indexer->reindexAll(); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php index 5cababbc988c7..2f7a08bf8b460 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php @@ -5,42 +5,44 @@ */ declare(strict_types=1); -// phpcs:ignore Magento2.Security.IncludeFile require __DIR__ . '/../../Eav/_files/empty_attribute_set_rollback.php'; -// phpcs:ignore Magento2.Security.IncludeFile require __DIR__ . '/../../Catalog/_files/categories_rollback.php'; +use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Attribute\Set as AttributeSet; +use Magento\Eav\Model\Entity\Type; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection as AttributeSetCollection; +use Magento\Framework\App\ObjectManager; use Magento\TestFramework\Helper\Bootstrap; use Magento\Eav\Api\AttributeRepositoryInterface; -use Magento\TestFramework\Helper\CacheCleaner; -$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +$eavConfig = $objectManager->get(Config::class); $attributesToDelete = ['test_configurable', 'second_test_configurable']; /** @var AttributeRepositoryInterface $attributeRepository */ -$attributeRepository = Bootstrap::getObjectManager()->get(AttributeRepositoryInterface::class); +$attributeRepository = $objectManager->get(AttributeRepositoryInterface::class); foreach ($attributesToDelete as $attributeCode) { - /** @var \Magento\Eav\Api\Data\AttributeInterface $attribute */ + /** @var AttributeInterface $attribute */ $attribute = $attributeRepository->get('catalog_product', $attributeCode); $attributeRepository->delete($attribute); } -/** @var $product \Magento\Catalog\Model\Product */ -$objectManager = Bootstrap::getObjectManager(); - -$entityType = $objectManager->create(\Magento\Eav\Model\Entity\Type::class)->loadByCode('catalog_product'); // remove attribute set - -/** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection $attributeSetCollection */ +$entityType = $objectManager->create(Type::class)->loadByCode('catalog_product'); +/** @var AttributeSetCollection $attributeSetCollection */ $attributeSetCollection = $objectManager->create( - \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection::class + AttributeSetCollection::class ); $attributeSetCollection->addFilter('attribute_set_name', 'second_attribute_set'); $attributeSetCollection->addFilter('entity_type_id', $entityType->getId()); -$attributeSetCollection->setOrder('attribute_set_id'); // descending is default value +$attributeSetCollection->setOrder('attribute_set_id'); $attributeSetCollection->setPageSize(1); $attributeSetCollection->load(); -/** @var \Magento\Eav\Model\Entity\Attribute\Set $attributeSet */ +/** @var AttributeSet $attributeSet */ $attributeSet = $attributeSetCollection->fetchItem(); $attributeSet->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php index 7d4f22e154030..7be314499a675 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php @@ -4,49 +4,55 @@ * See COPYING.txt for license details. */ -/** - * Create multiselect attribute - */ +/** Create multiselect attribute */ require __DIR__ . '/multiselect_attribute.php'; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type as ProductType; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute as EavAttribute; +use Magento\Catalog\Setup\CategorySetup; +use Magento\Eav\Model\Config as EavConfig; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection as OptionCollection; +use Magento\Indexer\Model\Indexer; +use Magento\Indexer\Model\Indexer\Collection as IndexerCollection; use Magento\TestFramework\Helper\Bootstrap; use Magento\Eav\Api\AttributeRepositoryInterface; /** Create product with options and multiselect attribute */ -/** @var $installer \Magento\Catalog\Setup\CategorySetup */ -$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Setup\CategorySetup::class -); +$objectManager = Bootstrap::getObjectManager(); +/** @var CategorySetup $installer */ +$installer = $objectManager->create(CategorySetup::class); -/** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ -$options = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class -); -$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +/** @var OptionCollection $options */ +$options = $objectManager->create(OptionCollection::class); +$eavConfig = $objectManager->get(EavConfig::class); -/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ +/** @var $attribute EavAttribute */ $attribute = $eavConfig->getAttribute('catalog_product', 'multiselect_attribute'); $eavConfig->clear(); $attribute->setIsSearchable(1) ->setIsVisibleInAdvancedSearch(1) - ->setIsFilterable(true) - ->setIsFilterableInSearch(true) - ->setIsVisibleOnFront(1); + ->setIsFilterable(false) + ->setIsFilterableInSearch(false) + ->setIsVisibleOnFront(0); /** @var AttributeRepositoryInterface $attributeRepository */ -$attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); +$attributeRepository = $objectManager->create(AttributeRepositoryInterface::class); $attributeRepository->save($attribute); $options->setAttributeFilter($attribute->getId()); $optionIds = $options->getAllIds(); -/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ -$productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); -/** @var $product \Magento\Catalog\Model\Product */ -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); -$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) +/** @var Product $product */ +$product = $objectManager->create(Product::class); +$product->setTypeId(ProductType::TYPE_SIMPLE) ->setId($optionIds[0] * 10) ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) ->setWebsiteIds([1]) @@ -54,45 +60,45 @@ ->setSku('simple_ms_1') ->setPrice(10) ->setDescription('Hello " &" Bring the water bottle when you can!') - ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setVisibility(Visibility::VISIBILITY_BOTH) ->setMultiselectAttribute([$optionIds[1],$optionIds[2]]) - ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStatus(Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); $productRepository->save($product); -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); -$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) +$product = $objectManager->create(Product::class); +$product->setTypeId(ProductType::TYPE_SIMPLE) ->setId($optionIds[1] * 10) ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) ->setWebsiteIds([1]) ->setName('With Multiselect 2 and 3') ->setSku('simple_ms_2') ->setPrice(10) - ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setVisibility(Visibility::VISIBILITY_BOTH) ->setMultiselectAttribute([$optionIds[2], $optionIds[3]]) - ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStatus(Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); $productRepository->save($product); -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); -$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) +$product = $objectManager->create(Product::class); +$product->setTypeId(ProductType::TYPE_SIMPLE) ->setId($optionIds[2] * 10) ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) ->setWebsiteIds([1]) ->setName('With Multiselect 1 and 3') ->setSku('simple_ms_2') ->setPrice(10) - ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setVisibility(Visibility::VISIBILITY_BOTH) ->setMultiselectAttribute([$optionIds[2], $optionIds[3]]) - ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStatus(Status::STATUS_ENABLED) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); $productRepository->save($product); -/** @var \Magento\Indexer\Model\Indexer\Collection $indexerCollection */ -$indexerCollection = Bootstrap::getObjectManager()->get(\Magento\Indexer\Model\Indexer\Collection::class); +/** @var IndexerCollection $indexerCollection */ +$indexerCollection = $objectManager->get(IndexerCollection::class); $indexerCollection->load(); -/** @var \Magento\Indexer\Model\Indexer $indexer */ +/** @var Indexer $indexer */ foreach ($indexerCollection->getItems() as $indexer) { $indexer->reindexAll(); } diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_configurable_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_configurable_product.php new file mode 100644 index 0000000000000..e46fafe7bb4ce --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_configurable_product.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); +/** @var QuoteFactory $quoteFactory */ +$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class); +/** @var QuoteResource $quoteResource */ +$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class); +/** @var CartRepositoryInterface $cartRepository */ +$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $productRepository->get('configurable'); +/** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ +$options = Bootstrap::getObjectManager()->create( + \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class +); + +$attributeRepository = Bootstrap::getObjectManager()->get(\Magento\Eav\Api\AttributeRepositoryInterface::class); +$attribute = $attributeRepository->get('catalog_product', 'test_configurable'); + +$option = $options->setAttributeFilter($attribute->getId())->getFirstItem(); + +$requestInfo = new \Magento\Framework\DataObject( + [ + 'product' => 1, + 'selected_configurable_option' => 1, + 'qty' => 1, + 'super_attribute' => [ + $attribute->getId() => $option->getId() + ] + ] +); + + +$quote = $quoteFactory->create(); +$quoteResource->load($quote, 'test_quote', 'reserved_order_id'); +$quote->addProduct($product, $requestInfo); +$cartRepository->save($quote); From 2e1daf1b0406453f43faabbc6fac9e245860dd55 Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Wed, 4 Mar 2020 16:13:33 -0600 Subject: [PATCH 180/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Stabilizing builds --- ...VerifyCategoryProductAndProductCategoryPartialReindexTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml index 4b5689e6b2720..d1a91369359bd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml @@ -202,6 +202,7 @@ <!-- Run Cron once to reindex product changes --> <wait stepKey="waitBeforeRunCronIndexAfterProductAssignToCategory" time="30"/> <magentoCLI stepKey="runCronIndexAfterProductAssignToCategory" command="cron:run --group=index"/> + <wait stepKey="waitAfterRunCronIndexAfterProductAssignToCategory" time="30"/> <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> From 2619c78466015857a37c421110e69dd80156633d Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Thu, 5 Mar 2020 09:21:00 +0200 Subject: [PATCH 181/369] Fixing failed tests --- app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php | 2 +- .../Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php b/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php index 3ee9bd888f568..50cf9f39f26ba 100644 --- a/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php +++ b/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php @@ -71,7 +71,7 @@ public function isCategoryFilterVisibleInLayerNavigation( * * @return int|null */ - protected function getStoreId(): ?int + private function getStoreId(): ?int { try { return (int) $this->storeManager->getStore()->getId(); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php index 92734bae2e444..2b579ecea6b83 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php @@ -13,6 +13,8 @@ /** * Filter List Test + * + * Check whenever the given filters list matches the expected result */ class FilterListTest extends \PHPUnit\Framework\TestCase { From 4d22c1f983ec344ea713ad491f6613d0bed6d04c Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Thu, 5 Mar 2020 10:36:15 +0200 Subject: [PATCH 182/369] MC-32111: MFTF Test for Recently Viewed products issues in does not work on store view level --- ...refrontRecentlyViewedWidgetActionGroup.xml | 22 +++ .../Data/RecentlyViewedProductStoreData.xml | 23 +++ .../StoreFrontRecentProductSection.xml | 15 ++ ...rontRecentlyViewedAtStoreViewLevelTest.xml | 149 ++++++++++++++++++ .../AdminEditCMSPageContentActionGroup.xml | 24 +++ ...nInsertRecentlyViewedWidgetActionGroup.xml | 40 +++++ .../Cms/Test/Mftf/Data/CmsHomepageData.xml | 16 ++ .../AdminRecentlyViewedWidgetSection.xml | 14 ++ 8 files changed, 303 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Data/RecentlyViewedProductStoreData.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Section/StoreFrontRecentProductSection.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEditCMSPageContentActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminInsertRecentlyViewedWidgetActionGroup.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/Data/CmsHomepageData.xml create mode 100644 app/code/Magento/Cms/Test/Mftf/Section/AdminRecentlyViewedWidgetSection.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup.xml new file mode 100644 index 0000000000000..8986db7af9246 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup"> + <annotations> + <description>Goes to the home Page Recently VIewed Product and Grab the Prdouct name and Position from it.</description> + </annotations> + <arguments> + <argument name="productName" type="string"/> + <argument name="productPosition" type="string"/> + </arguments> + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName(productPosition)}}" stepKey="grabRelatedProductPosition"/> + <assertContains expected="{{productName}}" actual="$grabRelatedProductPosition" stepKey="assertRelatedProductName"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/RecentlyViewedProductStoreData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/RecentlyViewedProductStoreData.xml new file mode 100644 index 0000000000000..15eec8495234b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/RecentlyViewedProductStoreData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="RecentlyViewedProductScopeStore"> + <data key="path">catalog/recently_products/scope</data> + <data key="value">store</data> + </entity> + <entity name="RecentlyViewedProductScopeWebsite"> + <data key="path">catalog/recently_products/scope</data> + <data key="value">website</data> + </entity> + <entity name="RecentlyViewedProductScopeStoreGroup"> + <data key="path">catalog/recently_products/scope</data> + <data key="value">group</data> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StoreFrontRecentProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StoreFrontRecentProductSection.xml new file mode 100644 index 0000000000000..387e252ae93d4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StoreFrontRecentProductSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> + <!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + --> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + + <section name="StoreFrontRecentlyViewedProductSection"> + <element name="ProductName" type="text" selector="//div[@class='products-grid']/ol/li[position()={{position}}]/div/div[@class='product-item-details']/strong/a" parameterized="true"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml new file mode 100644 index 0000000000000..afcf42fb0431a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml @@ -0,0 +1,149 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontRecentlyViewedAtStoreViewLevelTest"> + <annotations> + <stories value="Recently Viewed Product"/> + <title value="Recently Viewed Product at store view level"/> + <description value="Recently Viewed Product should not be displayed on second store view, if configured as, Per Store View "/> + <testCaseId value="MC-30453"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create Simple Product and Category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct3"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct4"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!--Create storeView 1--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewOne"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <!-- Set Stores > Configurations > Catalog > Recently Viewed/Compared Products > Show for Current = store view--> + <magentoCLI command="config:set {{RecentlyViewedProductScopeStore.path}} {{RecentlyViewedProductScopeStore.value}}" stepKey="RecentlyViewedProductScopeStore"/> + </before> + + <after> + <!-- Delete Product and Category --> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createSimpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="createSimpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <waitForPageLoad time="30" stepKey="waitForPageLoadWebSite"/> + <magentoCLI command="config:set {{RecentlyViewedProductScopeWebsite.path}} {{RecentlyViewedProductScopeWebsite.value}}" stepKey="RecentlyViewedProductScopeWebsite"/> + <!--Delete store views--> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteFirstStoreView"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + + <!-- Clear Widget--> + <actionGroup ref="AdminEditCMSPageContentActionGroup" stepKey="clearRecentlyViewedWidgetsFromCMSContent"> + <argument name="content" value="{{CmsHomePageContent.content}}"/> + <argument name="pageId" value="{{CmsHomePageContent.page_id}}"/> + </actionGroup> + + <!-- Logout Admin --> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterDeletion"/> + + </after> + + <!--Create widget for recently viewed products--> + <actionGroup ref="AdminEditCMSPageContentActionGroup" stepKey="clearRecentlyViewedWidgetsFromCMSContentBefore"> + <argument name="content" value="{{CmsHomePageContent.content}}"/> + <argument name="pageId" value="{{CmsHomePageContent.page_id}}"/> + </actionGroup> + + <amOnPage url="{{AdminCmsPageEditPage.url(CmsHomePageContent.page_id)}}" stepKey="navigateToEditHomePagePage"/> + <waitForPageLoad time="50" stepKey="waitForContentPageToLoad"/> + + <actionGroup ref="AdminInsertRecentlyViewedWidgetActionGroup" stepKey="insertRecentlyViewedWidget"> + <argument name="attributeSelector1" value="show_attributes"/> + <argument name="attributeSelector2" value="show_buttons"/> + <argument name="productAttributeSection1" value="1"/> + <argument name="productAttributeSection2" value="4"/> + <argument name="buttonToShowSection1" value="1"/> + <argument name="buttonToShowSection2" value="3"/> + </actionGroup> + + <!-- Warm up cache --> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterWidgetCreated"/> + + <!-- Navigate to product 3 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct2.name$)}}" stepKey="goToStore1ProductPage2"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct3.name$)}}" stepKey="goToStore1ProductPage3"/> + <!-- Go to Home Page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePage"/> + <waitForPageLoad time="30" stepKey="homeWaitForPageLoad"/> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertStore1RecentlyViewedProduct3"> + <argument name="productName" value="$$createSimpleProduct3.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <!-- Switch store view --> + <waitForPageLoad time="40" stepKey="waitForStorefrontPageLoad"/> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreViewActionGroup"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct1.name$)}}" stepKey="goToStore2ProductPage1"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct2.name$)}}" stepKey="goToStore2ProductPage2"/> + + <!-- Go to Home Page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStoreViewHomePage"/> + <waitForPageLoad time="30" stepKey="homePageWaitForStoreView"/> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStore1RecentlyViewedProduct1"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabDontSeeHomeProduct3"/> + <assertNotContains expected="$$createSimpleProduct3.name$$" actual="$grabDontSeeHomeProduct3" stepKey="assertNotSeeProduct3"/> + + <actionGroup ref="StorefrontSwitchDefaultStoreViewActionGroup" stepKey="switchToDefualtStoreView"/> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertSwitchStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertSwitchStore1RecentlyViewedProduct3"> + <argument name="productName" value="$$createSimpleProduct3.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabDontSeeHomeProduct1"/> + <assertNotContains expected="$$createSimpleProduct1.name$$" actual="$grabDontSeeHomeProduct1" stepKey="assertNotSeeProduct1"/> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEditCMSPageContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEditCMSPageContentActionGroup.xml new file mode 100644 index 0000000000000..b745e9705ed30 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminEditCMSPageContentActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminEditCMSPageContentActionGroup"> + <arguments> + <argument name="content" type="string" /> + <argument name="pageId" type="string" /> + </arguments> + <amOnPage url="{{AdminCmsPageEditPage.url(pageId)}}" stepKey="navigateToEditCMSPage"/> + <waitForPageLoad stepKey="waitForCmsPageEditPage"/> + <conditionalClick selector="{{CmsNewPagePageActionsSection.contentSectionName}}" dependentSelector="{{CatalogWidgetSection.insertWidgetButton}}" visible="false" stepKey="clickShowHideEditorIfVisible"/> + <waitForElementVisible selector="{{CmsNewPagePageContentSection.content}}" stepKey="waitForContentField"/> + <fillField selector="{{CmsNewPagePageContentSection.content}}" userInput="{{content}}" stepKey="resetCMSPageToDefaultContent"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="clickSave"/> + <waitForPageLoad stepKey="waitForSettingsApply"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminInsertRecentlyViewedWidgetActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminInsertRecentlyViewedWidgetActionGroup.xml new file mode 100644 index 0000000000000..e8c66c68348fc --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminInsertRecentlyViewedWidgetActionGroup.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminInsertRecentlyViewedWidgetActionGroup"> + <arguments> + <argument name="attributeSelector1" type="string" defaultValue="show_attributes"/> + <argument name="attributeSelector2" type="string" defaultValue="show_buttons"/> + <argument name="productAttributeSection1" type="string" defaultValue="1"/> + <argument name="productAttributeSection2" type="string" defaultValue="4" /> + <argument name="buttonToShowSection1" type="string" defaultValue="1"/> + <argument name="buttonToShowSection2" type="string" defaultValue="3" /> + </arguments> + <click selector="{{CmsNewPagePageActionsSection.contentSectionName}}" stepKey="expandContent"/> + <waitForPageLoad time="50" stepKey="waitForPageLoadContentSection"/> + <conditionalClick selector="{{CmsNewPagePageActionsSection.showHideEditor}}" dependentSelector="{{CmsNewPagePageActionsSection.showHideEditor}}" visible="true" stepKey="clickNextShowHideEditorIfVisible"/> + <waitForElementVisible selector="{{CatalogWidgetSection.insertWidgetButton}}" stepKey="waitForInsertWidgetElement"/> + <click selector="{{CatalogWidgetSection.insertWidgetButton}}" stepKey="clickInsertWidget"/> + <waitForElementVisible selector="{{InsertWidgetSection.widgetTypeDropDown}}" time="30" stepKey="waitForWidgetTypeDropDownVisible"/> + <!--Select "Widget Type"--> + <selectOption selector="{{WidgetSection.WidgetType}}" userInput="Recently Viewed Products" stepKey="selectRecentlyViewedProducts"/> + <waitForPageLoad time="30" stepKey="waitForPageLoadWidgetType"/> + <!--Select all product attributes--> + <dragAndDrop selector1="{{AdminRecentlyViewedWidgetSection.attributeSelector(attributeSelector1,productAttributeSection1)}}" selector2="{{AdminRecentlyViewedWidgetSection.attributeSelector(attributeSelector1,productAttributeSection2)}}" stepKey="selectProductSpecifiedOptions"/> + <!--Select all buttons to show--> + <dragAndDrop selector1="{{AdminRecentlyViewedWidgetSection.attributeSelector(attributeSelector2,buttonToShowSection2)}}" selector2="{{AdminRecentlyViewedWidgetSection.attributeSelector(attributeSelector2,buttonToShowSection2)}}" stepKey="selectButtonSpecifiedOptions"/> + <click selector="{{WidgetSection.InsertWidget}}" stepKey="clickInsertWidgetToSave"/> + <waitForPageLoad time="30" stepKey="waitForWidgetInsertPageLoad"/> + <!-- Check that widget is inserted --> + <waitForElementVisible selector="#cms_page_form_content" stepKey="checkCMSContent" time="30"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="clickNextSave"/> + <waitForPageLoad stepKey="waitForPageActionSave" time="30"/> + <waitForElementVisible selector="*[data-ui-id='messages-message-success']" time="60" stepKey="waitForSaveSuccess"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/Data/CmsHomepageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/CmsHomepageData.xml new file mode 100644 index 0000000000000..ce2ce747716a2 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Data/CmsHomepageData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="CmsHomePageContent"> + <data key="content">CMS homepage content goes here</data> + <data key="page_id">2</data> + </entity> + +</entities> \ No newline at end of file diff --git a/app/code/Magento/Cms/Test/Mftf/Section/AdminRecentlyViewedWidgetSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/AdminRecentlyViewedWidgetSection.xml new file mode 100644 index 0000000000000..37d1491945491 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Section/AdminRecentlyViewedWidgetSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminRecentlyViewedWidgetSection"> + <element name="attributeSelector" type="multiselect" selector="select[name='parameters[{{attributeName}}][]'] option:nth-of-type({{attributePosition}})" parameterized="true"/> + </section> +</sections> From cdfbf14051e5e81db906f405642252703b8dd9b3 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Thu, 5 Mar 2020 11:42:24 +0200 Subject: [PATCH 183/369] MC-32153: Customer Import - Customer address is not appearing in customer grid after import --- .../Model/Import/Address.php | 16 ++- .../Test/Unit/Model/Import/AddressTest.php | 112 +----------------- .../Model/Import/AddressTest.php | 35 ++++++ 3 files changed, 52 insertions(+), 111 deletions(-) diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Address.php b/app/code/Magento/CustomerImportExport/Model/Import/Address.php index 4eee5a39d55e1..09d76ec3fb71f 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Address.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Address.php @@ -13,6 +13,7 @@ use Magento\Store\Model\Store; use Magento\CustomerImportExport\Model\ResourceModel\Import\Address\Storage as AddressStorage; use Magento\ImportExport\Model\Import\AbstractSource; +use Magento\Customer\Model\Indexer\Processor; /** * Customer address import @@ -254,6 +255,11 @@ class Address extends AbstractCustomer */ private $addressStorage; + /** + * @var Processor + */ + private $indexerProcessor; + /** * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig @@ -274,6 +280,7 @@ class Address extends AbstractCustomer * @param array $data * @param CountryWithWebsitesSource|null $countryWithWebsites * @param AddressStorage|null $addressStorage + * @param Processor $indexerProcessor * * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -296,8 +303,9 @@ public function __construct( \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Customer\Model\Address\Validator\Postcode $postcodeValidator, array $data = [], - CountryWithWebsitesSource $countryWithWebsites = null, - AddressStorage $addressStorage = null + ?CountryWithWebsitesSource $countryWithWebsites = null, + ?AddressStorage $addressStorage = null, + ?Processor $indexerProcessor = null ) { $this->_customerFactory = $customerFactory; $this->_addressFactory = $addressFactory; @@ -348,6 +356,9 @@ public function __construct( $this->addressStorage = $addressStorage ?: ObjectManager::getInstance()->get(AddressStorage::class); + $this->indexerProcessor = $indexerProcessor + ?: ObjectManager::getInstance()->get(Processor::class); + $this->_initAttributes(); $this->_initCountryRegions(); } @@ -562,6 +573,7 @@ protected function _importData() $this->_deleteAddressEntities($deleteRowIds); } + $this->indexerProcessor->markIndexerAsInvalid(); return true; } diff --git a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php index 126a9e1791779..3df8b1ae5ef72 100644 --- a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php +++ b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/AddressTest.php @@ -298,9 +298,6 @@ protected function _createCustomerEntityMock() public function getWebsites($withDefault = false) { $websites = []; - if (!$withDefault) { - unset($websites[0]); - } foreach ($this->_websites as $id => $code) { if (!$withDefault && $id == \Magento\Store\Model\Store::DEFAULT_STORE_ID) { continue; @@ -330,97 +327,6 @@ public function iterate(\Magento\Framework\Data\Collection $collection, $pageSiz } } - /** - * Create mock for custom behavior test - * - * @return Address|\PHPUnit_Framework_MockObject_MockObject - */ - protected function _getModelMockForTestImportDataWithCustomBehaviour() - { - // input data - $customBehaviorRows = [ - [ - AbstractEntity::COLUMN_ACTION => 'update', - Address::COLUMN_ADDRESS_ID => $this->_customBehaviour['update_id'], - ], - [ - AbstractEntity::COLUMN_ACTION => AbstractEntity::COLUMN_ACTION_VALUE_DELETE, - Address::COLUMN_ADDRESS_ID => $this->_customBehaviour['delete_id'] - ], - ]; - $updateResult = [ - 'entity_row_new' => [], - 'entity_row_update' => $this->_customBehaviour['update_id'], - 'attributes' => [], - 'defaults' => [], - ]; - // entity adapter mock - $modelMock = $this->createPartialMock( - \Magento\CustomerImportExport\Model\Import\Address::class, - [ - 'validateRow', - '_prepareDataForUpdate', - '_saveAddressEntities', - '_saveAddressAttributes', - '_saveCustomerDefaults', - '_deleteAddressEntities', - '_mergeEntityAttributes', - 'getErrorAggregator', - 'getCustomerStorage', - 'prepareCustomerData', - ] - ); - //Adding behaviours - $availableBehaviors = new \ReflectionProperty($modelMock, '_availableBehaviors'); - $availableBehaviors->setAccessible(true); - $availableBehaviors->setValue($modelMock, $this->_availableBehaviors); - // mock to imitate data source model - $dataSourceMock = $this->createPartialMock( - \Magento\ImportExport\Model\ResourceModel\Import\Data::class, - ['getNextBunch', '__wakeup', 'getIterator'] - ); - $dataSourceMock->expects($this->at(0))->method('getNextBunch')->will($this->returnValue($customBehaviorRows)); - $dataSourceMock->expects($this->at(1))->method('getNextBunch')->will($this->returnValue(null)); - $dataSourceMock->expects($this->any()) - ->method('getIterator') - ->willReturn($this->getMockForAbstractClass(\Iterator::class)); - - $dataSourceModel = new \ReflectionProperty( - \Magento\CustomerImportExport\Model\Import\Address::class, - '_dataSourceModel' - ); - $dataSourceModel->setAccessible(true); - $dataSourceModel->setValue($modelMock, $dataSourceMock); - // mock expects for entity adapter - $modelMock->expects($this->any())->method('validateRow')->will($this->returnValue(true)); - $modelMock->expects($this->any()) - ->method('getErrorAggregator') - ->will($this->returnValue($this->errorAggregator)); - $modelMock->expects($this->any())->method('_prepareDataForUpdate')->will($this->returnValue($updateResult)); - $modelMock->expects( - $this->any() - )->method( - '_saveAddressEntities' - )->will( - $this->returnCallback([$this, 'validateSaveAddressEntities']) - ); - $modelMock->expects($this->any())->method('_saveAddressAttributes')->will($this->returnValue($modelMock)); - $modelMock->expects($this->any())->method('_saveCustomerDefaults')->will($this->returnValue($modelMock)); - $modelMock->expects( - $this->any() - )->method( - '_deleteAddressEntities' - )->will( - $this->returnCallback([$this, 'validateDeleteAddressEntities']) - ); - $modelMock->expects($this->any())->method('_mergeEntityAttributes')->will($this->returnValue([])); - $modelMock->expects($this->any()) - ->method('getCustomerStorage') - ->willReturn($this->_createCustomerStorageMock()); - - return $modelMock; - } - /** * Create mock for customer address model class * @@ -449,7 +355,9 @@ protected function _getModelMock() new \Magento\Framework\Stdlib\DateTime(), $this->createMock(\Magento\Customer\Model\Address\Validator\Postcode::class), $this->_getModelDependencies(), - $this->countryWithWebsites + $this->countryWithWebsites, + $this->createMock(\Magento\CustomerImportExport\Model\ResourceModel\Import\Address\Storage::class), + $this->createMock(\Magento\Customer\Model\Indexer\Processor::class) ); $property = new \ReflectionProperty($modelMock, '_availableBehaviors'); @@ -606,20 +514,6 @@ public function testGetDefaultAddressAttributeMapping() ); } - /** - * Test if correct methods are invoked according to different custom behaviours - * - * @covers \Magento\CustomerImportExport\Model\Import\Address::_importData - */ - public function testImportDataWithCustomBehaviour() - { - $this->_model = $this->_getModelMockForTestImportDataWithCustomBehaviour(); - $this->_model->setParameters(['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_CUSTOM]); - - // validation in validateSaveAddressEntities and validateDeleteAddressEntities - $this->_model->importData(); - } - /** * Validation method for _saveAddressEntities (callback for _saveAddressEntities) * diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/AddressTest.php b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/AddressTest.php index 96b7f8ce8ed67..e83b144e395b2 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/AddressTest.php @@ -14,6 +14,7 @@ use Magento\ImportExport\Model\Import as ImportModel; use Magento\ImportExport\Model\Import\Adapter as ImportAdapter; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Indexer\StateInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -84,6 +85,11 @@ class AddressTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Customer\Model\ResourceModel\Customer */ protected $customerResource; + /** + * @var \Magento\Customer\Model\Indexer\Processor + */ + private $indexerProcessor; + /** * Init new instance of address entity adapter */ @@ -96,6 +102,9 @@ protected function setUp() $this->_entityAdapter = Bootstrap::getObjectManager()->create( $this->_testClassName ); + $this->indexerProcessor = Bootstrap::getObjectManager()->create( + \Magento\Customer\Model\Indexer\Processor::class + ); } /** @@ -353,6 +362,7 @@ public function testImportDataAddUpdate() $requiredAttributes[] = $keyAttribute; foreach (['update', 'remove'] as $action) { foreach ($this->_updateData[$action] as $attributes) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $requiredAttributes = array_merge($requiredAttributes, array_keys($attributes)); } } @@ -494,4 +504,29 @@ public function testDifferentOptions(): void $imported = $this->_entityAdapter->importData(); $this->assertTrue($imported, 'Must be successfully imported'); } + + /** + * Test customer indexer gets invalidated after import when Update on Schedule mode is set + * + * @magentoDbIsolation enabled + */ + public function testCustomerIndexer(): void + { + $file = __DIR__ . '/_files/address_import_update.csv'; + $filesystem = Bootstrap::getObjectManager()->create(Filesystem::class); + $directoryWrite = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = new \Magento\ImportExport\Model\Import\Source\Csv($file, $directoryWrite); + $this->_entityAdapter + ->setParameters(['behavior' => ImportModel::BEHAVIOR_ADD_UPDATE]) + ->setSource($source) + ->validateData() + ->hasToBeTerminated(); + $this->indexerProcessor->getIndexer()->reindexAll(); + $statusBeforeImport = $this->indexerProcessor->getIndexer()->getStatus(); + $this->indexerProcessor->getIndexer()->setScheduled(true); + $this->_entityAdapter->importData(); + $statusAfterImport = $this->indexerProcessor->getIndexer()->getStatus(); + $this->assertEquals(StateInterface::STATUS_VALID, $statusBeforeImport); + $this->assertEquals(StateInterface::STATUS_INVALID, $statusAfterImport); + } } From d48cc2f8d08adb30a954ea12a03a58abacb23000 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 5 Mar 2020 11:51:08 +0200 Subject: [PATCH 184/369] improvement: cover Chart with integration test --- .../Backend/Model/Dashboard/ChartTest.php | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php new file mode 100644 index 0000000000000..a6fa0da1f6568 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Backend\Model; + +use Magento\Backend\Model\Dashboard\Chart; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Verify chart data by different period. + * + * @magentoAppArea adminhtml + */ +class ChartTest extends TestCase +{ + /** + * @var Chart + */ + private $model; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->model = Bootstrap::getObjectManager()->create(Chart::class); + } + + /** + * Verify getByPeriod with all types of period + * + * @magentoDataFixture Magento/Sales/_files/order_list_with_invoice.php + * @dataProvider getChartDataProvider + * @return void + */ + public function testGetByPeriodWithParam(int $expectedDataQty, string $period, string $chartParam): void + { + $this->assertCount($expectedDataQty, $this->model->getByPeriod($period, $chartParam)); + } + + /** + * Expected chart data + * + * @return array + */ + public function getChartDataProvider(): array + { + return [ + [ + 24, + '24h', + 'quantity' + ], + [ + 8, + '7d', + 'quantity' + ], + [ + 6, + '1m', + 'quantity' + ], + [ + 16, + '1y', + 'quantity' + ], + [ + 28, + '2y', + 'quantity' + ] + ]; + } +} From e45bb9694a4cc56b8d8ef06f66bdbb6db7dab05b Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 5 Mar 2020 12:32:05 +0200 Subject: [PATCH 185/369] fix incorrect namespace --- .../testsuite/Magento/Backend/Model/Dashboard/ChartTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php index a6fa0da1f6568..fbca1b8af7e0e 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\Backend\Model; +namespace Magento\Backend\Model\Dashboard; use Magento\Backend\Model\Dashboard\Chart; use Magento\TestFramework\Helper\Bootstrap; From 2b2858e71d4b676d82bdda9f826dbe2177517a24 Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Thu, 5 Mar 2020 13:43:29 +0200 Subject: [PATCH 186/369] Added adjustments as per review --- .../Catalog/Model/Config/LayerCategoryConfig.php | 12 +++++------- app/code/Magento/Catalog/Model/Layer/FilterList.php | 11 +++++------ app/code/Magento/Catalog/etc/adminhtml/system.xml | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php b/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php index 50cf9f39f26ba..0e8565e3b25d1 100644 --- a/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php +++ b/app/code/Magento/Catalog/Model/Config/LayerCategoryConfig.php @@ -69,14 +69,12 @@ public function isCategoryFilterVisibleInLayerNavigation( /** * Get the current store ID * - * @return int|null + * @return int + * + * @throws NoSuchEntityException */ - private function getStoreId(): ?int + private function getStoreId(): int { - try { - return (int) $this->storeManager->getStore()->getId(); - } catch (NoSuchEntityException $e) { - return null; - } + return (int) $this->storeManager->getStore()->getId(); } } diff --git a/app/code/Magento/Catalog/Model/Layer/FilterList.php b/app/code/Magento/Catalog/Model/Layer/FilterList.php index 2f32971c80bed..a7eba474c58d8 100644 --- a/app/code/Magento/Catalog/Model/Layer/FilterList.php +++ b/app/code/Magento/Catalog/Model/Layer/FilterList.php @@ -48,26 +48,25 @@ class FilterList protected $filters = []; /** - * @var LayerCategoryConfig|null + * @var LayerCategoryConfig */ private $layerCategoryConfig; /** * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param FilterableAttributeListInterface $filterableAttributes + * @param LayerCategoryConfig $layerCategoryConfig * @param array $filters - * @param LayerCategoryConfig|null $layerCategoryConfig */ public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, FilterableAttributeListInterface $filterableAttributes, - array $filters = [], - LayerCategoryConfig $layerCategoryConfig = null + LayerCategoryConfig $layerCategoryConfig, + array $filters = [] ) { $this->objectManager = $objectManager; $this->filterableAttributes = $filterableAttributes; - $this->layerCategoryConfig = $layerCategoryConfig ?: - ObjectManager::getInstance()->get(LayerCategoryConfig::class); + $this->layerCategoryConfig = $layerCategoryConfig; /** Override default filter type models */ $this->filterTypes = array_merge($this->filterTypes, $filters); diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index f548c23b68ce3..30a8ec8a81ec5 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -140,7 +140,7 @@ </group> <group id="layered_navigation"> <field id="display_category" translate="label" type="select" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> - <label>Display Category</label> + <label>Display Category Filter</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> </group> From 471ff6ab5269fc70fa0ad0641ed28d13d3f6ac29 Mon Sep 17 00:00:00 2001 From: Franciszek Wawrzak <f.wawrzak@macopedia.com> Date: Thu, 5 Mar 2020 13:24:19 +0100 Subject: [PATCH 187/369] improve image uploader error handler --- app/code/Magento/Catalog/Model/ImageUploader.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php index 0c3e008fa8bb5..b0c8d56057431 100644 --- a/app/code/Magento/Catalog/Model/ImageUploader.php +++ b/app/code/Magento/Catalog/Model/ImageUploader.php @@ -221,8 +221,10 @@ public function moveFileFromTmp($imageName, $returnRelativePath = false) $baseImagePath ); } catch (\Exception $e) { + $this->logger->critical($e); throw new \Magento\Framework\Exception\LocalizedException( - __('Something went wrong while saving the file(s).') + __('Something went wrong while saving the file(s).'), + $e ); } @@ -276,7 +278,8 @@ public function saveFileToTmpDir($fileId) } catch (\Exception $e) { $this->logger->critical($e); throw new \Magento\Framework\Exception\LocalizedException( - __('Something went wrong while saving the file(s).') + __('Something went wrong while saving the file(s).'), + $e ); } } From abdb45844fd3862a8a2f852fb34d35cc0e958829 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 5 Mar 2020 14:51:14 +0200 Subject: [PATCH 188/369] MC-31573: PayflowPro Checkout Broken with SameSite Cookie Changes from Chrome 80 --- .../Payment/Block/Transparent/Redirect.php | 62 ++++++++++ .../templates/transparent/redirect.phtml | 21 ++++ .../templates/transparent/redirect.phtml | 21 ++++ .../Adminhtml/Transparent/Redirect.php | 13 ++ .../Controller/Transparent/Redirect.php | 98 +++++++++++++++ .../Payflow/Service/Request/SecureToken.php | 6 +- .../Payflow/Service/Response/Transaction.php | 5 +- .../Plugin/TransparentSessionChecker.php | 50 ++++++++ app/code/Magento/Paypal/etc/di.xml | 3 + .../Paypal/etc/frontend/page_types.xml | 1 + .../layout/transparent_payment_redirect.xml | 16 +++ .../layout/transparent_payment_redirect.xml | 16 +++ .../Controller/Transparent/RedirectTest.php | 112 ++++++++++++++++++ .../controller_acl_test_whitelist_ce.txt | 1 + 14 files changed, 420 insertions(+), 5 deletions(-) create mode 100644 app/code/Magento/Payment/Block/Transparent/Redirect.php create mode 100644 app/code/Magento/Payment/view/adminhtml/templates/transparent/redirect.phtml create mode 100644 app/code/Magento/Payment/view/frontend/templates/transparent/redirect.phtml create mode 100644 app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php create mode 100644 app/code/Magento/Paypal/Controller/Transparent/Redirect.php create mode 100644 app/code/Magento/Paypal/Plugin/TransparentSessionChecker.php create mode 100644 app/code/Magento/Paypal/view/adminhtml/layout/transparent_payment_redirect.xml create mode 100644 app/code/Magento/Paypal/view/frontend/layout/transparent_payment_redirect.xml create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/RedirectTest.php diff --git a/app/code/Magento/Payment/Block/Transparent/Redirect.php b/app/code/Magento/Payment/Block/Transparent/Redirect.php new file mode 100644 index 0000000000000..1be6dec4cc1d8 --- /dev/null +++ b/app/code/Magento/Payment/Block/Transparent/Redirect.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Block\Transparent; + +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Element\Template; +use Magento\Framework\View\Element\Template\Context; + +/** + * Redirect block for register specific params in layout + * + * @api + */ +class Redirect extends Template +{ + /** + * Route path key to make redirect url. + */ + private const ROUTE_PATH = 'route_path'; + + /** + * @var UrlInterface + */ + private $url; + + /** + * @param Context $context + * @param UrlInterface $url + * @param array $data + */ + public function __construct( + Context $context, + UrlInterface $url, + array $data = [] + ) { + $this->url = $url; + parent::__construct($context, $data); + } + + /** + * Returns url for redirect. + * + * @return string + */ + public function getRedirectUrl(): string + { + return $this->url->getUrl($this->getData(self::ROUTE_PATH)); + } + + /** + * Returns params to be redirected. + * + * @return array + */ + public function getPostParams(): array + { + return (array)$this->_request->getPostValue(); + } +} diff --git a/app/code/Magento/Payment/view/adminhtml/templates/transparent/redirect.phtml b/app/code/Magento/Payment/view/adminhtml/templates/transparent/redirect.phtml new file mode 100644 index 0000000000000..17fbdf780a40a --- /dev/null +++ b/app/code/Magento/Payment/view/adminhtml/templates/transparent/redirect.phtml @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Payment\Block\Transparent\Redirect $block */ +$params = $block->getPostParams(); +$redirectUrl = $block->getRedirectUrl(); +?> +<html> +<head></head> +<body onload="document.forms['proxy_form'].submit()"> +<form id="proxy_form" action="<?= $block->escapeUrl($redirectUrl) ?>" + method="POST" hidden enctype="application/x-www-form-urlencoded" class="no-display"> + <?php foreach ($params as $name => $value):?> + <input value="<?= $block->escapeHtmlAttr($value) ?>" name="<?= $block->escapeHtmlAttr($name) ?>" type="hidden"/> + <?php endforeach?> +</form> +</body> +</html> diff --git a/app/code/Magento/Payment/view/frontend/templates/transparent/redirect.phtml b/app/code/Magento/Payment/view/frontend/templates/transparent/redirect.phtml new file mode 100644 index 0000000000000..17fbdf780a40a --- /dev/null +++ b/app/code/Magento/Payment/view/frontend/templates/transparent/redirect.phtml @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Payment\Block\Transparent\Redirect $block */ +$params = $block->getPostParams(); +$redirectUrl = $block->getRedirectUrl(); +?> +<html> +<head></head> +<body onload="document.forms['proxy_form'].submit()"> +<form id="proxy_form" action="<?= $block->escapeUrl($redirectUrl) ?>" + method="POST" hidden enctype="application/x-www-form-urlencoded" class="no-display"> + <?php foreach ($params as $name => $value):?> + <input value="<?= $block->escapeHtmlAttr($value) ?>" name="<?= $block->escapeHtmlAttr($name) ?>" type="hidden"/> + <?php endforeach?> +</form> +</body> +</html> diff --git a/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php new file mode 100644 index 0000000000000..8201761cc3a29 --- /dev/null +++ b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Paypal\Controller\Adminhtml\Transparent; + +/** + * Class for redirecting the Paypal response result to Magento controller. + */ +class Redirect extends \Magento\Paypal\Controller\Transparent\Redirect +{ +} diff --git a/app/code/Magento/Paypal/Controller/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Transparent/Redirect.php new file mode 100644 index 0000000000000..c6cee15d23c7a --- /dev/null +++ b/app/code/Magento/Paypal/Controller/Transparent/Redirect.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Paypal\Controller\Transparent; + +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\View\Result\LayoutFactory; +use Magento\Payment\Model\Method\Logger; +use Magento\Paypal\Model\Payflow\Transparent; + +/** + * Class for redirecting the Paypal response result to Magento controller. + */ +class Redirect extends \Magento\Framework\App\Action\Action implements CsrfAwareActionInterface, HttpPostActionInterface +{ + /** + * @var LayoutFactory + */ + private $resultLayoutFactory; + + /** + * @var Transparent + */ + private $transparent; + + /** + * @var Logger + */ + private $logger; + + /** + * Constructor + * + * @param Context $context + * @param LayoutFactory $resultLayoutFactory + * @param Transparent $transparent + * @param Logger $logger + */ + public function __construct( + Context $context, + LayoutFactory $resultLayoutFactory, + Transparent $transparent, + Logger $logger + ) { + $this->resultLayoutFactory = $resultLayoutFactory; + $this->transparent = $transparent; + $this->logger = $logger; + + parent::__construct($context); + } + + /** + * @inheritDoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritDoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } + + /** + * Saves the payment in quote + * + * @return ResultInterface + * @throws LocalizedException + */ + public function execute() + { + $gatewayResponse = (array)$this->getRequest()->getPostValue(); + $this->logger->debug( + ['PayPal PayflowPro redirect:' => $gatewayResponse], + $this->transparent->getDebugReplacePrivateDataKeys(), + $this->transparent->getDebugFlag() + ); + + $resultLayout = $this->resultLayoutFactory->create(); + $resultLayout->addDefaultHandle(); + $resultLayout->getLayout()->getUpdate()->load(['transparent_payment_redirect']); + + return $resultLayout; + } +} diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php index 2a4ec764c4172..6e9990f65c450 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php @@ -70,9 +70,9 @@ public function requestToken(Quote $quote, array $urls = []) $request->setCurrency($quote->getBaseCurrencyCode()); $request->setCreatesecuretoken('Y'); $request->setSecuretokenid($this->mathRandom->getUniqueHash()); - $request->setReturnurl($urls['return_url'] ?? $this->url->getUrl('paypal/transparent/response')); - $request->setErrorurl($urls['error_url'] ?? $this->url->getUrl('paypal/transparent/response')); - $request->setCancelurl($urls['cancel_url'] ?? $this->url->getUrl('paypal/transparent/response')); + $request->setReturnurl($urls['return_url'] ?? $this->url->getUrl('paypal/transparent/redirect')); + $request->setErrorurl($urls['error_url'] ?? $this->url->getUrl('paypal/transparent/redirect')); + $request->setCancelurl($urls['cancel_url'] ?? $this->url->getUrl('paypal/transparent/redirect')); $request->setDisablereceipt('TRUE'); $request->setSilenttran('TRUE'); diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php index 5db78e6fac520..1e97ac8b8c766 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php @@ -19,7 +19,8 @@ use Magento\Sales\Api\Data\OrderPaymentInterface; /** - * Class Transaction + * Process PayPal transaction response. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Transaction @@ -90,7 +91,7 @@ public function getResponseObject($gatewayTransactionResponse) $response = $this->transparent->mapGatewayResponse((array) $gatewayTransactionResponse, $response); $this->logger->debug( - (array) $gatewayTransactionResponse, + ['PayPal PayflowPro response:' => (array)$gatewayTransactionResponse], (array) $this->transparent->getDebugReplacePrivateDataKeys(), (bool) $this->transparent->getDebugFlag() ); diff --git a/app/code/Magento/Paypal/Plugin/TransparentSessionChecker.php b/app/code/Magento/Paypal/Plugin/TransparentSessionChecker.php new file mode 100644 index 0000000000000..5157ba3208fb7 --- /dev/null +++ b/app/code/Magento/Paypal/Plugin/TransparentSessionChecker.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Plugin; + +use Magento\Framework\App\Request\Http; +use Magento\Framework\Session\SessionStartChecker; + +/** + * Intended to preserve session cookie after submitting POST form from PayPal to Magento controller. + */ +class TransparentSessionChecker +{ + private const TRANSPARENT_REDIRECT_PATH = 'paypal/transparent/redirect'; + + /** + * @var Http + */ + private $request; + + /** + * @param Http $request + */ + public function __construct( + Http $request + ) { + $this->request = $request; + } + + /** + * Prevents session starting while instantiating PayPal transparent redirect controller. + * + * @param SessionStartChecker $subject + * @param bool $result + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterCheck(SessionStartChecker $subject, bool $result): bool + { + if ($result === false) { + return false; + } + + return strpos((string)$this->request->getPathInfo(), self::TRANSPARENT_REDIRECT_PATH) === false; + } +} diff --git a/app/code/Magento/Paypal/etc/di.xml b/app/code/Magento/Paypal/etc/di.xml index c0141bbb3215e..973ed0f91924c 100644 --- a/app/code/Magento/Paypal/etc/di.xml +++ b/app/code/Magento/Paypal/etc/di.xml @@ -252,4 +252,7 @@ </argument> </arguments> </type> + <type name="Magento\Framework\Session\SessionStartChecker"> + <plugin name="transparent_session_checker" type="Magento\Paypal\Plugin\TransparentSessionChecker"/> + </type> </config> diff --git a/app/code/Magento/Paypal/etc/frontend/page_types.xml b/app/code/Magento/Paypal/etc/frontend/page_types.xml index 133ab1ca76162..1da5d54fb385d 100644 --- a/app/code/Magento/Paypal/etc/frontend/page_types.xml +++ b/app/code/Magento/Paypal/etc/frontend/page_types.xml @@ -14,6 +14,7 @@ <type id="paypal_payflow_form" label="Paypal Payflow Form"/> <type id="transparent" label="Paypal Payflow TR Form"/> <type id="transparent_payment_response" label="Paypal Payflow TR Response"/> + <type id="transparent_payment_redirect" label="Paypal Payflow TR Redirect"/> <type id="paypal_payflow_returnurl" label="Paypal Payflow Return URL"/> <type id="paypal_payflowadvanced_cancelpayment" label="Paypal Payflow Advanced Cancel Payment"/> <type id="paypal_payflowadvanced_form" label="Paypal Payflow Advanced Form"/> diff --git a/app/code/Magento/Paypal/view/adminhtml/layout/transparent_payment_redirect.xml b/app/code/Magento/Paypal/view/adminhtml/layout/transparent_payment_redirect.xml new file mode 100644 index 0000000000000..01acf03c0d077 --- /dev/null +++ b/app/code/Magento/Paypal/view/adminhtml/layout/transparent_payment_redirect.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <container name="root" label="Root"> + <block class="Magento\Payment\Block\Transparent\Redirect" name="transparent_redirect" template="Magento_Payment::transparent/redirect.phtml"> + <arguments> + <argument name="route_path" xsi:type="string">paypal/transparent/response</argument> + </arguments> + </block> + </container> +</layout> diff --git a/app/code/Magento/Paypal/view/frontend/layout/transparent_payment_redirect.xml b/app/code/Magento/Paypal/view/frontend/layout/transparent_payment_redirect.xml new file mode 100644 index 0000000000000..01acf03c0d077 --- /dev/null +++ b/app/code/Magento/Paypal/view/frontend/layout/transparent_payment_redirect.xml @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/layout_generic.xsd"> + <container name="root" label="Root"> + <block class="Magento\Payment\Block\Transparent\Redirect" name="transparent_redirect" template="Magento_Payment::transparent/redirect.phtml"> + <arguments> + <argument name="route_path" xsi:type="string">paypal/transparent/response</argument> + </arguments> + </block> + </container> +</layout> diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/RedirectTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/RedirectTest.php new file mode 100644 index 0000000000000..4a30231012acf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/Transparent/RedirectTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Controller\Transparent; + +use Magento\TestFramework\TestCase\AbstractController; +use Zend\Stdlib\Parameters; + +/** + * Tests PayPal transparent redirect controller. + */ +class RedirectTest extends AbstractController +{ + /** + * Tests transparent redirect for PayPal PayflowPro payment flow. + * + * @SuppressWarnings(PHPMD.Superglobals) + */ + public function testRequestRedirect() + { + $redirectUri = 'paypal/transparent/redirect'; + $postData = [ + 'BILLTOCITY' => 'culver city', + 'AMT' => '0.00', + 'BILLTOEMAIL' => 'user_1@example.com', + 'BILLTOSTREET' => '123 Freedom Blvd. #123 app.111', + 'VISACARDLEVEL' => '12', + 'SHIPTOCITY' => 'culver city' + ]; + + $this->setRequestUri($redirectUri); + $this->getRequest()->setPostValue($postData); + $this->getRequest()->setMethod('POST'); + + $this->dispatch($redirectUri); + + $responseHtml = $this->getResponse()->getBody(); + try { + $responseNvp = $this->convertToNvp($responseHtml); + $this->assertEquals( + $postData, + $responseNvp, + 'POST form should contain all params from POST request' + ); + } catch (\InvalidArgumentException $exception) { + $this->fail($exception->getMessage()); + } + + $this->assertEmpty( + $_SESSION, + 'Session start has to be skipped for current controller' + ); + } + + /** + * Sets REQUEST_URI into request object. + * + * @param string $requestUri + * @return void + */ + private function setRequestUri(string $requestUri) + { + $request = $this->getRequest(); + $reflection = new \ReflectionClass($request); + $property = $reflection->getProperty('requestUri'); + $property->setAccessible(true); + $property->setValue($request, null); + + $request->setServer(new Parameters(['REQUEST_URI' => $requestUri])); + } + + /** + * Converts HTML response to NVP structure + * + * @param string $response + * @return array + */ + private function convertToNvp(string $response): array + { + $document = new \DOMDocument(); + + libxml_use_internal_errors(true); + if (!$document->loadHTML($response)) { + throw new \InvalidArgumentException( + __('The response format was incorrect. Should be valid HTML') + ); + } + libxml_use_internal_errors(false); + + $document->getElementsByTagName('input'); + + $convertedResponse = []; + /** @var \DOMNode $inputNode */ + foreach ($document->getElementsByTagName('input') as $inputNode) { + if (!$inputNode->attributes->getNamedItem('value') + || !$inputNode->attributes->getNamedItem('name') + ) { + continue; + } + $convertedResponse[$inputNode->attributes->getNamedItem('name')->nodeValue] + = $inputNode->attributes->getNamedItem('value')->nodeValue; + } + + unset($convertedResponse['form_key']); + + return $convertedResponse; + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/_files/controller_acl_test_whitelist_ce.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/_files/controller_acl_test_whitelist_ce.txt index 1119824f217bb..8561818022112 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/_files/controller_acl_test_whitelist_ce.txt +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Backend/_files/controller_acl_test_whitelist_ce.txt @@ -21,6 +21,7 @@ Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\P Magento\Downloadable\Controller\Adminhtml\Product\Initialization\Helper\Plugin\Downloadable Magento\Paypal\Controller\Adminhtml\Transparent\RequestSecureToken Magento\Paypal\Controller\Adminhtml\Transparent\Response +Magento\Paypal\Controller\Adminhtml\Transparent\Redirect Magento\Sales\Controller\Adminhtml\Order\CreditmemoLoader Magento\Search\Controller\Adminhtml\Synonyms\ResultPageBuilder Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader From 960732ab38a0346feb41661c9989c992219f8a4a Mon Sep 17 00:00:00 2001 From: Vaha <vaha@atwix.com> Date: Thu, 5 Mar 2020 09:07:54 +0200 Subject: [PATCH 189/369] added improvements to galleryManagement 'create' method to set all image roles for first product entity --- .../Product/Gallery/GalleryManagement.php | 4 ++ ...minOpenProductImagesSectionActionGroup.xml | 18 ++++++++ ...inProductImageRolesSelectedActionGroup.xml | 26 ++++++++++++ .../ProductAttributeMediaGalleryEntryData.xml | 7 ++++ ...MediaRolesForFirstAddedImageViaApiTest.xml | 41 +++++++++++++++++++ .../Product/Gallery/GalleryManagementTest.php | 10 +++++ 6 files changed, 106 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductImagesSectionActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductImageRolesSelectedActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php index a9afb7cec45e2..6a078a915119c 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php @@ -61,6 +61,10 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry) $existingMediaGalleryEntries = $product->getMediaGalleryEntries(); $existingEntryIds = []; if ($existingMediaGalleryEntries == null) { + // set all media types if not specified + if ($entry->getTypes() == null) { + $entry->setTypes(array_keys($product->getMediaAttributes())); + } $existingMediaGalleryEntries = [$entry]; } else { foreach ($existingMediaGalleryEntries as $existingEntries) { diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductImagesSectionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductImagesSectionActionGroup.xml new file mode 100644 index 0000000000000..4d49b13a8bf5a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenProductImagesSectionActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenProductImagesSectionActionGroup"> + <annotations> + <description>Requires the navigation to the Product page. Opens 'Image and Videos' section.</description> + </annotations> + <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageUploadButton}}" visible="false" stepKey="openProductImagesSection"/> + <waitForElementVisible selector="{{AdminProductImagesSection.imageUploadButton}}" stepKey="waitForImageUploadButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductImageRolesSelectedActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductImageRolesSelectedActionGroup.xml new file mode 100644 index 0000000000000..3bb6210d6b824 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductImageRolesSelectedActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminProductImageRolesSelectedActionGroup"> + <annotations> + <description>Requires the navigation to the Product page and opened 'Image and Videos' section. + Checks the Base, Small, Thumbnail and Swatch Roles are selected for provided image.</description> + </annotations> + <arguments> + <argument name="imageFileName" type="string" defaultValue="test_image"/> + </arguments> + <waitForElementVisible selector="{{AdminProductImagesSection.imageFile(imageFileName)}}" stepKey="seeProductImageName"/> + <click selector="{{AdminProductImagesSection.imageFile(imageFileName)}}" stepKey="clickProductImage"/> + <waitForElementVisible selector="{{AdminProductImagesSection.isBaseSelected}}" stepKey="checkRoleBaseSelected"/> + <waitForElementVisible selector="{{AdminProductImagesSection.isSmallSelected}}" stepKey="checkRoleSmallSelected"/> + <waitForElementVisible selector="{{AdminProductImagesSection.isThumbnailSelected}}" stepKey="checkRoleThumbnailSelected"/> + <waitForElementVisible selector="{{AdminProductImagesSection.isSwatchSelected}}" stepKey="checkRoleSwatchSelected"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml index 75b4ef773a934..7016a1c1d0358 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml @@ -35,4 +35,11 @@ <data key="label">Magento Logo</data> <requiredEntity type="ImageContent">MagentoLogoImageContentExportImport</requiredEntity> </entity> + <entity name="ApiProductAttributeMediaGalleryEntryWithoutTypesTestImage" type="ProductAttributeMediaGalleryEntry"> + <data key="media_type">image</data> + <data key="label" unique="suffix">Test Image</data> + <data key="position">0</data> + <data key="disabled">false</data> + <requiredEntity type="ImageContent">TestImageContent</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml new file mode 100644 index 0000000000000..c31054e3dc192 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckMediaRolesForFirstAddedImageViaApiTest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckMediaRolesForFirstAddedImageViaApiTest"> + <annotations> + <stories value="Add Simple Product with image via API"/> + <title value="Check that added image for created product has selected image roles."/> + <description value="Login as admin, create simple product, add image to created product (via API).Go to + Admin Product Edit page for created product to check that added image has selected image roles."/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/> + <createData entity="SimpleOutOfStockProduct" stepKey="createSimpleProduct"/> + <createData entity="ApiProductAttributeMediaGalleryEntryWithoutTypesTestImage" stepKey="createSimpleProductImage"> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + </after> + + <actionGroup ref="GoToProductPageViaIDActionGroup" stepKey="goToSimpleProduct"> + <argument name="productId" value="$$createSimpleProduct.id$$"/> + </actionGroup> + <actionGroup ref="AdminOpenProductImagesSectionActionGroup" stepKey="openProductImagesSection"/> + <actionGroup ref="AssertAdminProductImageRolesSelectedActionGroup" stepKey="checkImageRolesSelected"> + <argument name="imageFileName" value="$createSimpleProductImage.entry[content][name]$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php index 6d4e98b60ad18..30994eda87273 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php @@ -59,6 +59,7 @@ protected function setUp() 'getCustomAttribute', 'getMediaGalleryEntries', 'setMediaGalleryEntries', + 'getMediaAttributes', ] ); $this->mediaGalleryEntryMock = @@ -99,6 +100,9 @@ public function testCreateWithCannotSaveException() $entryContentMock = $this->getMockBuilder(\Magento\Framework\Api\Data\ImageContentInterface::class) ->disableOriginalConstructor() ->getMock(); + $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->getMock(); $this->mediaGalleryEntryMock->expects($this->any())->method('getContent')->willReturn($entryContentMock); $this->productRepositoryMock->expects($this->once()) ->method('get') @@ -108,6 +112,10 @@ public function testCreateWithCannotSaveException() $this->contentValidatorMock->expects($this->once())->method('isValid')->with($entryContentMock) ->willReturn(true); + $this->productMock->expects($this->any()) + ->method('getMediaAttributes') + ->willReturn(['small_image' => $attributeMock]); + $this->productRepositoryMock->expects($this->once())->method('save')->with($this->productMock) ->willThrowException(new \Exception()); $this->model->create($productSku, $this->mediaGalleryEntryMock); @@ -133,6 +141,8 @@ public function testCreate() $this->contentValidatorMock->expects($this->once())->method('isValid')->with($entryContentMock) ->willReturn(true); + $this->mediaGalleryEntryMock->expects($this->any())->method('getTypes')->willReturn(['small_image']); + $newEntryMock = $this->createMock(\Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface::class); $newEntryMock->expects($this->exactly(2))->method('getId')->willReturn(42); $this->productMock->expects($this->at(2))->method('getMediaGalleryEntries') From 52db731a99cc7db1d1ad4efe87dcfb141118658f Mon Sep 17 00:00:00 2001 From: Viktor Sevch <svitja@ukr.net> Date: Thu, 5 Mar 2020 15:08:35 +0200 Subject: [PATCH 190/369] MC-29052: Magento\FunctionalTestingFramework.functional.AdminTaxReportGridTest fails randomly --- .../Section/AdminOrderItemsOrderedSection.xml | 1 - .../Section/AdminOrderItemsOrderedSection.xml | 14 ++ .../Test/AdminCheckingTaxReportGridTest.xml | 2 +- .../Test/Mftf/Test/AdminTaxReportGridTest.xml | 225 ------------------ 4 files changed, 15 insertions(+), 227 deletions(-) create mode 100644 app/code/Magento/Tax/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml delete mode 100644 app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml index 94af3c79c8e02..a2c82de60a78e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml @@ -20,7 +20,6 @@ <element name="itemTaxPercent" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-tax-percent" parameterized="true"/> <element name="itemDiscountAmount" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-discont .price" parameterized="true"/> <element name="itemTotal" type="text" selector=".edit-order-table tr:nth-of-type({{row}}) .col-total .price" parameterized="true"/> - <element name="itemTaxAmountByProductName" type="text" selector="//table[contains(@class,'edit-order-table')]//div[contains(text(),'{{productName}}')]/ancestor::tr//td[contains(@class, 'col-tax-amount')]//span" parameterized="true"/> <element name="productNameColumn" type="text" selector=".edit-order-table .col-product .product-title"/> <element name="productNameOptions" type="text" selector=".edit-order-table .col-product .item-options"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml new file mode 100644 index 0000000000000..969b59af2a3c3 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminOrderItemsOrderedSection"> + <element name="itemTaxAmountByProductName" type="text" selector="//table[contains(@class,'edit-order-table')]//div[contains(text(),'{{productName}}')]/ancestor::tr//td[contains(@class, 'col-tax-amount')]//span" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml index 9b1a0ca2a9892..b8e0881c4431f 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml @@ -16,7 +16,7 @@ <description value="Tax Report Grid displays Tax amount in rows 'Total' and 'Subtotal' is a sum of all tax amounts"/> <severity value="MAJOR"/> <testCaseId value="MC-6230"/> - <useCaseId value="MAGETWO-91521"/> + <useCaseId value="MC-25815"/> <group value="Tax"/> </annotations> <before> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml deleted file mode 100644 index 1c23f455d0ad0..0000000000000 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml +++ /dev/null @@ -1,225 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminTaxReportGridTest"> - <annotations> - <features value="Tax"/> - <stories value="MAGETWO-91521: Reports / Sales / Tax report show incorrect amount"/> - <title value="DEPRECATED Checking Tax Report grid"/> - <description value="Tax Report Grid displays Tax amount in rows 'Total' and 'Subtotal' is a sum of all tax amounts"/> - <severity value="MAJOR"/> - <testCaseId value="MAGETWO-94338"/> - <group value="Tax"/> - <skip> - <issueId value="DEPRECATED">Use AdminCheckingTaxReportGridTest instead.</issueId> - </skip> - </annotations> - <before> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - - <!-- Go to tax rule page --> - <actionGroup ref="AddNewTaxRuleActionGroup" stepKey="addFirstTaxRuleActionGroup"/> - <fillField stepKey="fillRuleName" selector="{{AdminTaxRulesSection.ruleName}}" userInput="TaxRule1"/> - - <!-- Add NY and CA tax rules --> - <actionGroup ref="AddNewTaxRateNoZipActionGroup" stepKey="addNYTaxRate"> - <argument name="taxCode" value="SimpleTaxWithZipCode"/> - </actionGroup> - - <actionGroup ref="addProductTaxClass" stepKey="addProductTaxClass"> - <argument name="prodTaxClassName" value="TaxClasses1"/> - </actionGroup> - - <click stepKey="disableDefaultProdTaxClass" selector="{{AdminTaxRulesSection.defaultTaxClass}}"/> - <waitForPageLoad stepKey="waitForTaxRulePage"/> - <click stepKey="clickSave" selector="{{AdminTaxRulesSection.saveRule}}"/> - <waitForPageLoad stepKey="waitForNewTaxRuleCreated"/> - - <!-- Go to tax rule page to create second Tax Rule--> - <actionGroup ref="AddNewTaxRuleActionGroup" stepKey="addSecondTaxRuleActionGroup"/> - <fillField stepKey="fillSecondRuleName" selector="{{AdminTaxRulesSection.ruleName}}" userInput="TaxRule2"/> - - <actionGroup ref="AddNewTaxRateNoZipActionGroup" stepKey="addCATaxRate"> - <argument name="taxCode" value="SimpleSecondTaxWithZipCode"/> - </actionGroup> - - <actionGroup ref="addProductTaxClass" stepKey="addSecondProductTaxClass"> - <argument name="prodTaxClassName" value="TaxClasses2"/> - </actionGroup> - - <click stepKey="disableSecondProdTaxClass" selector="{{AdminTaxRulesSection.defaultTaxClass}}"/> - <waitForPageLoad stepKey="waitForTaxRulePage2"/> - <click stepKey="clickSaveBtn" selector="{{AdminTaxRulesSection.saveRule}}"/> - <waitForPageLoad stepKey="waitForSecondTaxRuleCreated"/> - - <createData entity="_defaultCategory" stepKey="createCategory"/> - <createData entity="_defaultProduct" stepKey="firstProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - - <createData entity="_defaultProduct" stepKey="secondProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - </before> - - <!--Open Created products. In Tax Class select new created Product Tax classes.--> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductIndex"/> - <waitForPageLoad stepKey="wait1"/> - <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetProductGrid"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridBySku"> - <argument name="product" value="$$firstProduct$$"/> - </actionGroup> - <actionGroup ref="OpenProductForEditByClickingRowXColumnYInProductGridActionGroup" stepKey="openFirstProductForEdit"/> - <selectOption selector="{{AdminProductFormSection.productTaxClass}}" stepKey="selectTexClassForFirstProduct" userInput="TaxClasses1"/> - <!-- Save the second product --> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveFirstProduct"/> - <waitForPageLoad stepKey="waitForFirstProductSaved"/> - - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="againGoToProductIndex"/> - <waitForPageLoad stepKey="wait2"/> - <actionGroup ref="ResetProductGridToDefaultViewActionGroup" stepKey="resetSecondProductGrid"/> - <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterSecondProductGridBySku"> - <argument name="product" value="$$secondProduct$$"/> - </actionGroup> - <actionGroup ref="OpenProductForEditByClickingRowXColumnYInProductGridActionGroup" stepKey="openSecondProductForEdit"/> - <selectOption selector="{{AdminProductFormSection.productTaxClass}}" stepKey="selectTexClassForSecondProduct" userInput="TaxClasses2"/> - <!-- Save the second product --> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveSecondProduct"/> - <waitForPageLoad stepKey="waitForSecondProductSaved"/> - - <!--Create an order with these 2 products in that zip code.--> - <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> - <waitForPageLoad stepKey="waitForIndexPageLoad"/> - <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> - <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickCreateNewOrder"/> - <click selector="{{AdminOrderFormActionSection.CreateNewCustomer}}" stepKey="clickCreateCustomer"/> - <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> - <waitForPageLoad stepKey="waitForPage" time="60"/> - <!--Check if order can be submitted without the required fields including email address--> - <scrollToTopOfPage stepKey="scrollToTopOfOrderFormPage" after="seeNewOrderPageTitle"/> - <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addFirstProductToOrder" after="scrollToTopOfOrderFormPage"> - <argument name="product" value="$$firstProduct$$"/> - </actionGroup> - <actionGroup ref="AddSimpleProductToOrderActionGroup" stepKey="addSecondProductToOrder" after="addFirstProductToOrder"> - <argument name="product" value="$$secondProduct$$"/> - </actionGroup> - - <!--Fill customer group and customer email--> - <selectOption selector="{{AdminOrderFormAccountSection.group}}" userInput="{{GeneralCustomerGroup.code}}" stepKey="selectCustomerGroup" after="addSecondProductToOrder"/> - <fillField selector="{{AdminOrderFormAccountSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillCustomerEmail" after="selectCustomerGroup"/> - - <!--Fill customer address information--> - <actionGroup ref="FillOrderCustomerInformationActionGroup" stepKey="fillCustomerAddress" after="fillCustomerEmail"> - <argument name="customer" value="Simple_US_Customer"/> - <argument name="address" value="US_Address_TX"/> - </actionGroup> - - <!-- Select shipping --> - <actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="selectFlatRateShipping" after="fillCustomerAddress"/> - <!-- Checkout select Check/Money Order payment --> - <actionGroup ref="SelectCheckMoneyPaymentMethodActionGroup" after="selectFlatRateShipping" stepKey="selectCheckMoneyPayment"/> - <!--Submit Order and verify information--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" after="selectCheckMoneyPayment" stepKey="clickSubmitOrder"/> - <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage" after="clickSubmitOrder"/> - <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage" after="seeViewOrderPage"/> - - <!--Create Invoice and Shipment for this Order.--> - <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> - <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seeNewInvoiceInPageTitle" after="clickInvoiceButton"/> - <waitForPageLoad stepKey="waitForInvoicePageOpened"/> - - <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> - - <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> - <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl" after="clickShipAction"/> - <!--Submit Shipment--> - <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment" after="seeOrderShipmentUrl"/> - <waitForPageLoad stepKey="waitForShipmentSaved"/> - - <!--Go to "Reports" -> "Sales" -> "Tax"--> - <amOnPage url="/admin/reports/report_sales/tax/" stepKey="navigateToReportsTaxPage"/> - <waitForPageLoad stepKey="waitForReportsTaxPageLoad"/> - - <!--click "here" to refresh last day's statistics --> - <click stepKey="clickRefrashStatisticsHere" selector="{{AdminTaxReportsSection.refreshStatistics}}"/> - <waitForPageLoad stepKey="waitForRefresh"/> - - <!--Select Dates--> - <fillField selector="{{AdminTaxReportsSection.fromDate}}" userInput="05/16/2018" stepKey="fillDateFrom"/> - <click selector="{{AdminTaxReportsSection.toDate}}" stepKey="clickDateTo"/> - <click selector="{{AdminTaxReportsSection.goTodayButton}}" stepKey="clickGoTodayDate"/> - <!--Click "Show report" in the upper right corner.--> - <click selector="{{AdminTaxReportsSection.showReportButton}}" stepKey="clickShowReportButton"/> - <waitForPageLoad time="60" stepKey="waitForReload"/> - <!--Tax Report Grid displays Tax amount in rows. "Total" and "Subtotal" is a sum of all tax amounts--> - <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Texas-0.125')}}" stepKey="amountOfFirstTaxRate"/> - <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Texas-7.25')}}" stepKey="amountOfSecondTaxRate"/> - <grabTextFrom selector="{{AdminTaxReportsSection.taxRuleAmount('Subtotal')}}" stepKey="amountOfSubtotalTaxRate"/> - <assertEquals stepKey="assertSubtotalFirstField"> - <expectedResult type="string">$0.15</expectedResult> - <actualResult type="variable">amountOfFirstTaxRate</actualResult> - </assertEquals> - - <assertEquals stepKey="assertSubtotalSecondField"> - <expectedResult type="string">$8.92</expectedResult> - <actualResult type="variable">amountOfSecondTaxRate</actualResult> - </assertEquals> - - <assertEquals stepKey="assertSubtotalField"> - <expectedResult type="string">$9.07</expectedResult> - <actualResult type="variable">amountOfSubtotalTaxRate</actualResult> - </assertEquals> - - <after> - <!-- Go to the tax rule page and delete the row we created--> - <amOnPage url="{{AdminTaxRuleGridPage.url}}" stepKey="goToTaxRulesPage"/> - <waitForPageLoad stepKey="waitForRulesPage"/> - - <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteRule"> - <argument name="name" value="TaxRule1"/> - <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> - </actionGroup> - - <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteSecondRule"> - <argument name="name" value="TaxRule2"/> - <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> - </actionGroup> - - <!-- Go to the tax rate page --> - <amOnPage url="{{AdminTaxRateGridPage.url}}" stepKey="goToTaxRatesPage"/> - <waitForPageLoad stepKey="waitForRatesPage"/> - - <!-- Delete the two tax rates that were created --> - <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteNYRate"> - <argument name="name" value="{{SimpleTaxWithZipCode.state}}-{{SimpleTaxWithZipCode.rate}}"/> - <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> - </actionGroup> - - <actionGroup ref="deleteEntitySecondaryGrid" stepKey="deleteCARate"> - <argument name="name" value="{{SimpleSecondTaxWithZipCode.state}}-{{SimpleSecondTaxWithZipCode.rate}}"/> - <argument name="searchInput" value="{{AdminSecondaryGridSection.taxIdentifierSearch}}"/> - </actionGroup> - - <deleteData createDataKey="firstProduct" stepKey="deleteFirstProduct"/> - <deleteData createDataKey="secondProduct" stepKey="deleteSecondProduct"/> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - - <actionGroup ref="DeleteProductTaxClassActionGroup" stepKey="deleteFirstProductTaxClass"> - <argument name="taxClassName" value="TaxClasses1"/> - </actionGroup> - - <actionGroup ref="DeleteProductTaxClassActionGroup" stepKey="deleteSecondProductTaxClass"> - <argument name="taxClassName" value="TaxClasses2"/> - </actionGroup> - - <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> - </after> - </test> -</tests> From 6724a91cf01a6b31a56ac0967206d6a365c840cd Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Thu, 5 Mar 2020 15:20:02 +0200 Subject: [PATCH 191/369] MC-23743: [2.3.1] Updating Youtube video via API causes data to be lost. --- .../MediaGalleryProcessor.php | 10 +- .../Api/ProductRepositoryInterfaceTest.php | 91 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php index 2aa92b8f0316e..ecb7322ac10d2 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php +++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php @@ -224,7 +224,15 @@ private function processEntries(ProductInterface $product, array $newEntries, ar $this->processNewMediaGalleryEntry($product, $newEntry); $finalGallery = $product->getData('media_gallery'); - $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById)); + + $entryIds = array_keys( + array_diff_key( + $product->getData('media_gallery')['images'], + $entriesById + ) + ); + $newEntryId = array_pop($entryIds); + $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); $entriesById[$newEntryId] = $newEntry; $finalGallery['images'][$newEntryId] = $newEntry; diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index 3123295166a35..8c3ec20b0ede5 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -381,6 +381,97 @@ public function testCreate($product) $this->deleteProduct($product[ProductInterface::SKU]); } + /** + * Media gallery entries with external videos + * + * @return array + */ + public function externalVideoDataProvider(): array + { + return [ + [ + [ + [ + 'media_type' => 'external-video', + 'disabled' => false, + 'label' => 'Test Video Created', + 'types' => [], + 'position' => 1, + 'content' => [ + 'type' => 'image/png', + 'name' => 'thumbnail.png', + 'base64_encoded_data' => 'iVBORw0KGgoAAAANSUhEUgAAAP8AAADGCAMAAAAqo6adAAAAA1BMVEUAAP79f' + . '+LBAAAASElEQVR4nO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + . 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+BsYAAAF7hZJ0AAAAAElFTkSuQmCC', + ], + 'extension_attributes' => [ + 'video_content' => [ + 'media_type' => 'external-video', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/', + 'video_title' => 'Video title', + 'video_description' => 'Video description', + 'video_metadata' => 'Video meta', + ], + ], + ] + ] + ], + [ + [ + [ + 'media_type' => 'external-video', + 'disabled' => false, + 'label' => 'Test Video Updated', + 'types' => [], + 'position' => 1, + 'content' => [ + 'type' => 'image/png', + 'name' => 'thumbnail.png', + 'base64_encoded_data' => 'iVBORw0KGgoAAAANSUhEUgAAAP8AAADGCAMAAAAqo6adAAAAA1BMVEUAAP79f' + . '+LBAAAASElEQVR4nO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + . 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA+BsYAAAF7hZJ0AAAAAElFTkSuQmCC', + ], + 'extension_attributes' => [ + 'video_content' => [ + 'media_type' => 'external-video', + 'video_provider' => 'vimeo', + 'video_url' => 'https://www.vimeo.com/', + 'video_title' => 'Video title', + 'video_description' => 'Video description', + 'video_metadata' => 'Video meta', + ], + ], + ] + ] + ] + ]; + } + + /** + * Test create/ update product with external video media gallery entry + * + * @dataProvider externalVideoDataProvider + * @param array $mediaGalleryData + */ + public function testCreateWithExternalVideo(array $mediaGalleryData) + { + $simpleProductBaseData = $this->getSimpleProductData( + [ + ProductInterface::NAME => 'Product With Ext. Video', + ProductInterface::SKU => 'prod-with-ext-video' + ] + ); + + $simpleProductBaseData['media_gallery_entries'] = $mediaGalleryData; + + $response = $this->saveProduct($simpleProductBaseData); + $this->assertEquals( + $simpleProductBaseData['media_gallery_entries'][0]['extension_attributes'], + $response["media_gallery_entries"][0]["extension_attributes"] + ); + } + /** * @param array $fixtureProduct * From afb752e629e96b35a1380d8c3909f7366eddfac6 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Thu, 5 Mar 2020 15:48:38 +0200 Subject: [PATCH 192/369] 26117: "Current user does not have an active cart" even when he actually has one --- app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php | 4 +--- .../testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php | 2 +- .../Magento/GraphQl/Quote/Customer/MergeCartsTest.php | 2 +- .../testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php index af70809a1053d..32be4332d30e6 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php @@ -72,9 +72,7 @@ public function execute(string $cartHash, ?int $customerId, int $storeId): Quote } if (false === (bool)$cart->getIsActive()) { - throw new GraphQlNoSuchEntityException( - __('Current user does not have an active cart.') - ); + throw new GraphQlNoSuchEntityException(__('This cart isn\'t active.')); } if ((int)$cart->getStoreId() !== $storeId) { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php index 7ffce2a7f541d..ec7ea0bbf5e2a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php @@ -162,7 +162,7 @@ public function testGetNonExistentCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php * * @expectedException Exception - * @expectedExceptionMessage Current user does not have an active cart. + * @expectedExceptionMessage This cart isn't active. */ public function testGetInactiveCart() { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php index 0a8d98eefe9e3..04f9b25c8d7cd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php @@ -108,7 +108,7 @@ public function testMergeGuestWithCustomerCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @expectedException \Exception - * @expectedExceptionMessage Current user does not have an active cart. + * @expectedExceptionMessage This cart isn't active. */ public function testGuestCartExpiryAfterMerge() { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php index ae9b7b32b2dab..2124af961bd8b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php @@ -119,7 +119,7 @@ public function testGetNonExistentCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php * * @expectedException Exception - * @expectedExceptionMessage Current user does not have an active cart. + * @expectedExceptionMessage This cart isn't active. */ public function testGetInactiveCart() { From 8ed20d151301484271b03755006e203e80ef76eb Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 5 Mar 2020 16:11:16 +0200 Subject: [PATCH 193/369] MC-31573: PayflowPro Checkout Broken with SameSite Cookie Changes from Chrome 80 --- .../Paypal/Controller/Transparent/Redirect.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Paypal/Controller/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Transparent/Redirect.php index c6cee15d23c7a..7358d51f5c3d6 100644 --- a/app/code/Magento/Paypal/Controller/Transparent/Redirect.php +++ b/app/code/Magento/Paypal/Controller/Transparent/Redirect.php @@ -5,8 +5,8 @@ */ namespace Magento\Paypal\Controller\Transparent; -use Magento\Framework\App\Action\Context; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\ActionInterface; use Magento\Framework\App\CsrfAwareActionInterface; use Magento\Framework\App\Request\InvalidRequestException; use Magento\Framework\App\RequestInterface; @@ -19,8 +19,13 @@ /** * Class for redirecting the Paypal response result to Magento controller. */ -class Redirect extends \Magento\Framework\App\Action\Action implements CsrfAwareActionInterface, HttpPostActionInterface +class Redirect implements ActionInterface, CsrfAwareActionInterface, HttpPostActionInterface { + /** + * @var RequestInterface + */ + private $request; + /** * @var LayoutFactory */ @@ -39,22 +44,21 @@ class Redirect extends \Magento\Framework\App\Action\Action implements CsrfAware /** * Constructor * - * @param Context $context + * @param RequestInterface $request * @param LayoutFactory $resultLayoutFactory * @param Transparent $transparent * @param Logger $logger */ public function __construct( - Context $context, + RequestInterface $request, LayoutFactory $resultLayoutFactory, Transparent $transparent, Logger $logger ) { + $this->request = $request; $this->resultLayoutFactory = $resultLayoutFactory; $this->transparent = $transparent; $this->logger = $logger; - - parent::__construct($context); } /** @@ -82,7 +86,7 @@ public function validateForCsrf(RequestInterface $request): ?bool */ public function execute() { - $gatewayResponse = (array)$this->getRequest()->getPostValue(); + $gatewayResponse = (array)$this->request->getPostValue(); $this->logger->debug( ['PayPal PayflowPro redirect:' => $gatewayResponse], $this->transparent->getDebugReplacePrivateDataKeys(), From e88b1ccfe7acd0875ab9170dfa50fe396994a9d7 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Thu, 5 Mar 2020 16:48:26 +0200 Subject: [PATCH 194/369] MC-32154: Customer grid not indexing automatically --- .../Model/Import/Customer.php | 14 +- .../Test/Unit/Model/Import/CustomerTest.php | 236 ------------------ .../Model/Import/CustomerTest.php | 25 ++ 3 files changed, 37 insertions(+), 238 deletions(-) delete mode 100644 app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerTest.php diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index cf3fa6b0b521f..8f5bb951ce737 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -11,6 +11,8 @@ use Magento\ImportExport\Model\Import; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; use Magento\ImportExport\Model\Import\AbstractSource; +use Magento\Customer\Model\Indexer\Processor; +use Magento\Framework\App\ObjectManager; /** * Customer entity import @@ -168,6 +170,11 @@ class Customer extends AbstractCustomer 'lock_expires', ]; + /** + * @var Processor + */ + private $indexerProcessor; + /** * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig @@ -182,6 +189,7 @@ class Customer extends AbstractCustomer * @param \Magento\Customer\Model\ResourceModel\Attribute\CollectionFactory $attrCollectionFactory * @param \Magento\Customer\Model\CustomerFactory $customerFactory * @param array $data + * @param Processor $indexerProcessor * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -197,7 +205,8 @@ public function __construct( \Magento\CustomerImportExport\Model\ResourceModel\Import\Customer\StorageFactory $storageFactory, \Magento\Customer\Model\ResourceModel\Attribute\CollectionFactory $attrCollectionFactory, \Magento\Customer\Model\CustomerFactory $customerFactory, - array $data = [] + array $data = [], + ?Processor $indexerProcessor = null ) { $this->_resourceHelper = $resourceHelper; @@ -254,6 +263,7 @@ public function __construct( /** @var $customerResource \Magento\Customer\Model\ResourceModel\Customer */ $customerResource = $this->_customerModel->getResource(); $this->_entityTable = $customerResource->getEntityTable(); + $this->indexerProcessor = $indexerProcessor ?: ObjectManager::getInstance()->get(Processor::class); } /** @@ -554,7 +564,7 @@ protected function _importData() $this->_deleteCustomerEntities($entitiesToDelete); } } - + $this->indexerProcessor->markIndexerAsInvalid(); return true; } diff --git a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerTest.php b/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerTest.php deleted file mode 100644 index 9a7183d5b5f72..0000000000000 --- a/app/code/Magento/CustomerImportExport/Test/Unit/Model/Import/CustomerTest.php +++ /dev/null @@ -1,236 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * Test class for \Magento\CustomerImportExport\Model\Import\Customer - */ -namespace Magento\CustomerImportExport\Test\Unit\Model\Import; - -use Magento\CustomerImportExport\Model\Import\Customer; -use Magento\CustomerImportExport\Model\ResourceModel\Import\Customer\Storage; - -class CustomerTest extends \PHPUnit\Framework\TestCase -{ - /** - * Customer entity import model - * - * @var Customer|\PHPUnit_Framework_MockObject_MockObject - */ - protected $_model; - - /** - * Available behaviours - * - * @var array - */ - protected $_availableBehaviors = [ - \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE, - \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE, - \Magento\ImportExport\Model\Import::BEHAVIOR_CUSTOM, - ]; - - /** - * Custom behavior input rows - * - * @var array - */ - protected $_inputRows = [ - 'create' => [ - Customer::COLUMN_ACTION => 'create', - Customer::COLUMN_EMAIL => 'create@email.com', - Customer::COLUMN_WEBSITE => 'website1', - ], - 'update' => [ - Customer::COLUMN_ACTION => 'update', - Customer::COLUMN_EMAIL => 'update@email.com', - Customer::COLUMN_WEBSITE => 'website1', - ], - 'delete' => [ - Customer::COLUMN_ACTION => Customer::COLUMN_ACTION_VALUE_DELETE, - Customer::COLUMN_EMAIL => 'delete@email.com', - Customer::COLUMN_WEBSITE => 'website1', - ], - ]; - - /** - * Customer ids for all custom behavior input rows - * - * @var array - */ - protected $_customerIds = ['create' => 1, 'update' => 2, 'delete' => 3]; - - /** - * Unset entity adapter model - */ - protected function tearDown() - { - unset($this->_model); - - parent::tearDown(); - } - - /** - * Create mock for import with custom behavior test - * - * @return Customer|\PHPUnit_Framework_MockObject_MockObject - */ - protected function _getModelMockForTestImportDataWithCustomBehaviour() - { - // entity adapter mock - $modelMock = $this->getMockBuilder(\Magento\CustomerImportExport\Model\Import\Customer::class) - ->disableOriginalConstructor() - ->setMethods( - [ - 'validateRow', - '_getCustomerId', - '_prepareDataForUpdate', - '_saveCustomerEntities', - '_saveCustomerAttributes', - '_deleteCustomerEntities', - 'getErrorAggregator', - 'getCustomerStorage', - ] - ) - ->getMock(); - - $errorAggregator = $this->createPartialMock( - \Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregator::class, - ['hasToBeTerminated'] - ); - - $availableBehaviors = new \ReflectionProperty($modelMock, '_availableBehaviors'); - $availableBehaviors->setAccessible(true); - $availableBehaviors->setValue($modelMock, $this->_availableBehaviors); - - // mock to imitate data source model - $dataSourceModelMock = $this->getMockBuilder(\Magento\ImportExport\Model\ResourceModel\Import\Data::class) - ->disableOriginalConstructor() - ->setMethods([ - 'getNextBunch', - '__wakeup', - ]) - ->getMock(); - - $dataSourceModelMock->expects($this->at(0)) - ->method('getNextBunch') - ->will($this->returnValue($this->_inputRows)); - $dataSourceModelMock->expects($this->at(1)) - ->method('getNextBunch') - ->will($this->returnValue(null)); - - $property = new \ReflectionProperty( - \Magento\CustomerImportExport\Model\Import\Customer::class, - '_dataSourceModel' - ); - $property->setAccessible(true); - $property->setValue($modelMock, $dataSourceModelMock); - - $modelMock->expects($this->any()) - ->method('validateRow') - ->will($this->returnValue(true)); - - $modelMock->expects($this->any()) - ->method('_getCustomerId') - ->will($this->returnValue($this->_customerIds['delete'])); - - $modelMock->expects($this->any()) - ->method('_prepareDataForUpdate') - ->will($this->returnCallback([$this, 'prepareForUpdateMock'])); - - $modelMock->expects($this->any()) - ->method('_saveCustomerEntities') - ->will($this->returnCallback([$this, 'validateSaveCustomerEntities'])); - - $modelMock->expects($this->any()) - ->method('_saveCustomerAttributes') - ->will($this->returnValue($modelMock)); - - $modelMock->expects($this->any()) - ->method('_deleteCustomerEntities') - ->will($this->returnCallback([$this, 'validateDeleteCustomerEntities'])); - - $modelMock->expects($this->any()) - ->method('getErrorAggregator') - ->will($this->returnValue($errorAggregator)); - /** @var \PHPUnit_Framework_MockObject_MockObject $storageMock */ - $storageMock = $this->createMock(Storage::class); - $storageMock->expects($this->any())->method('prepareCustomers'); - $modelMock->expects($this->any()) - ->method('getCustomerStorage') - ->willReturn($storageMock); - - return $modelMock; - } - - /** - * Test whether correct methods are invoked in case of custom behaviour for each row in action column - */ - public function testImportDataWithCustomBehaviour() - { - $this->_model = $this->_getModelMockForTestImportDataWithCustomBehaviour(); - $this->_model->setParameters(['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_CUSTOM]); - - // validation in validateSaveCustomerEntities and validateDeleteCustomerEntities - $this->_model->importData(); - } - - /** - * Emulate data preparing depending on value in row action column - * - * @param array $rowData - * @return int - */ - public function prepareForUpdateMock(array $rowData) - { - $preparedResult = [ - Customer::ENTITIES_TO_CREATE_KEY => [], - Customer::ENTITIES_TO_UPDATE_KEY => [], - Customer::ATTRIBUTES_TO_SAVE_KEY => ['table' => []], - ]; - - $actionColumnKey = Customer::COLUMN_ACTION; - if ($rowData[$actionColumnKey] == 'create') { - $preparedResult[Customer::ENTITIES_TO_CREATE_KEY] = [ - ['entity_id' => $this->_customerIds['create']], - ]; - } elseif ($rowData[$actionColumnKey] == 'update') { - $preparedResult[Customer::ENTITIES_TO_UPDATE_KEY] = [ - ['entity_id' => $this->_customerIds['update']], - ]; - } - - return $preparedResult; - } - - /** - * Validation method for _saveCustomerEntities - * - * @param array $entitiesToCreate - * @param array $entitiesToUpdate - * @return Customer|\PHPUnit_Framework_MockObject_MockObject - */ - public function validateSaveCustomerEntities(array $entitiesToCreate, array $entitiesToUpdate) - { - $this->assertCount(1, $entitiesToCreate); - $this->assertEquals($this->_customerIds['create'], $entitiesToCreate[0]['entity_id']); - $this->assertCount(1, $entitiesToUpdate); - $this->assertEquals($this->_customerIds['update'], $entitiesToUpdate[0]['entity_id']); - return $this->_model; - } - - /** - * Validation method for _deleteCustomerEntities - * - * @param array $customerIdsToDelete - * @return Customer|\PHPUnit_Framework_MockObject_MockObject - */ - public function validateDeleteCustomerEntities(array $customerIdsToDelete) - { - $this->assertCount(1, $customerIdsToDelete); - $this->assertEquals($this->_customerIds['delete'], $customerIdsToDelete[0]); - return $this->_model; - } -} diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php index 7b5ddc4b9fa5f..89c84d7c18068 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php @@ -11,6 +11,7 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\NoSuchEntityException; use Magento\ImportExport\Model\Import; +use Magento\Framework\Indexer\StateInterface; /** * Test for class \Magento\CustomerImportExport\Model\Import\Customer which covers validation logic @@ -39,6 +40,11 @@ class CustomerTest extends \PHPUnit\Framework\TestCase */ protected $directoryWrite; + /** + * @var \Magento\Customer\Model\Indexer\Processor + */ + private $indexerProcessor; + /** * Create all necessary data for tests */ @@ -49,6 +55,8 @@ protected function setUp() $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create(\Magento\CustomerImportExport\Model\Import\Customer::class); $this->_model->setParameters(['behavior' => Import::BEHAVIOR_ADD_UPDATE]); + $this->indexerProcessor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Customer\Model\Indexer\Processor::class); $propertyAccessor = new \ReflectionProperty($this->_model, 'errorMessageTemplates'); $propertyAccessor->setAccessible(true); @@ -377,6 +385,23 @@ public function testUpdateExistingCustomers(): void $this->assertEquals(1, $customer->getStoreId()); } + /** + * Test customer indexer gets invalidated after import when Update on Schedule mode is set + * + * @magentoDbIsolation enabled + * @return void + */ + public function testCustomerIndexer(): void + { + $this->indexerProcessor->getIndexer()->reindexAll(); + $statusBeforeImport = $this->indexerProcessor->getIndexer()->getStatus(); + $this->indexerProcessor->getIndexer()->setScheduled(true); + $this->doImport(__DIR__ . '/_files/customers_with_gender_to_import.csv', Import::BEHAVIOR_ADD_UPDATE); + $statusAfterImport = $this->indexerProcessor->getIndexer()->getStatus(); + $this->assertEquals(StateInterface::STATUS_VALID, $statusBeforeImport); + $this->assertEquals(StateInterface::STATUS_INVALID, $statusAfterImport); + } + /** * Gets customer entity. * From 2bdcc11de0bd8d127a3115383ea960577c840920 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Wed, 4 Mar 2020 16:23:26 +0200 Subject: [PATCH 195/369] 14086: Guest cart API ignoring cartId in url for some methods --- .../Magento/Quote/Plugin/UpdateCartId.php | 49 +++++++++++++++++ app/code/Magento/Quote/etc/webapi_rest/di.xml | 3 ++ .../Quote/Api/GuestCartItemRepositoryTest.php | 54 ++++++++++--------- 3 files changed, 81 insertions(+), 25 deletions(-) create mode 100644 app/code/Magento/Quote/Plugin/UpdateCartId.php diff --git a/app/code/Magento/Quote/Plugin/UpdateCartId.php b/app/code/Magento/Quote/Plugin/UpdateCartId.php new file mode 100644 index 0000000000000..3d5dd21f2e494 --- /dev/null +++ b/app/code/Magento/Quote/Plugin/UpdateCartId.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Quote\Plugin; + +use Magento\Framework\Webapi\Rest\Request as RestRequest; +use Magento\Quote\Api\Data\CartItemInterface; +use Magento\Quote\Api\GuestCartItemRepositoryInterface; + +/** + * Update cart id from request param + */ +class UpdateCartId +{ + /** + * @var RestRequest $request + */ + private $request; + + /** + * @param RestRequest $request + */ + public function __construct(RestRequest $request) + { + $this->request = $request; + } + + /** + * Update id from request if param cartId exist + * + * @param GuestCartItemRepositoryInterface $guestCartItemRepository + * @param CartItemInterface $cartItem + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeSave( + GuestCartItemRepositoryInterface $guestCartItemRepository, + CartItemInterface $cartItem + ): void { + if ($cartId = $this->request->getParam('cartId')) { + $cartItem->setQuoteId($cartId); + } + } +} diff --git a/app/code/Magento/Quote/etc/webapi_rest/di.xml b/app/code/Magento/Quote/etc/webapi_rest/di.xml index 27d5ff7753425..a55d2146be156 100644 --- a/app/code/Magento/Quote/etc/webapi_rest/di.xml +++ b/app/code/Magento/Quote/etc/webapi_rest/di.xml @@ -13,4 +13,7 @@ <plugin name="accessControl" type="Magento\Quote\Model\QuoteRepository\Plugin\AccessChangeQuoteControl" /> <plugin name="authorization" type="Magento\Quote\Model\QuoteRepository\Plugin\Authorization" /> </type> + <type name="Magento\Quote\Api\GuestCartItemRepositoryInterface"> + <plugin name="updateCartIdFromRequest" type="Magento\Quote\Plugin\UpdateCartId" /> + </type> </config> diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartItemRepositoryTest.php index e03a54f9463d7..ddd986bdafc60 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartItemRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartItemRepositoryTest.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -8,25 +7,35 @@ use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\CatalogInventory\Model\Stock; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\WebapiAbstract; +/** + * Test for Magento\Quote\Api\GuestCartItemRepositoryInterface. + */ class GuestCartItemRepositoryTest extends WebapiAbstract { - const SERVICE_VERSION = 'V1'; - const SERVICE_NAME = 'quoteGuestCartItemRepositoryV1'; - const RESOURCE_PATH = '/V1/guest-carts/'; + public const SERVICE_NAME = 'quoteGuestCartItemRepositoryV1'; + private const SERVICE_VERSION = 'V1'; + private const RESOURCE_PATH = '/V1/guest-carts/'; /** - * @var \Magento\TestFramework\ObjectManager + * @var ObjectManager */ - protected $objectManager; + private $objectManager; + /** + * @inheritdoc + */ protected function setUp() { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->objectManager = Bootstrap::getObjectManager(); } /** + * Test quote items + * * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php */ public function testGetList() @@ -112,12 +121,16 @@ public function testAddItem() ]; $requestData = [ - "cartItem" => [ - "sku" => $productSku, - "qty" => 7, - "quote_id" => $cartId, + 'cartItem' => [ + 'sku' => $productSku, + 'qty' => 7, ], ]; + + if (TESTS_WEB_API_ADAPTER === self::ADAPTER_SOAP) { + $requestData['cartItem']['quote_id'] = $cartId; + } + $this->_webApiCall($serviceInfo, $requestData); $this->assertTrue($quote->hasProductId(2)); $this->assertEquals(7, $quote->getItemByProduct($product)->getQty()); @@ -205,20 +218,11 @@ public function testUpdateItem(array $stockData, string $errorMessage = null) ], ]; - if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { - $requestData = [ - "cartItem" => [ - "qty" => 5, - "quote_id" => $cartId, - "itemId" => $itemId, - ], - ]; - } else { - $requestData = [ - "cartItem" => [ - "qty" => 5, - "quote_id" => $cartId, - ], + $requestData['cartItem']['qty'] = 5; + if (TESTS_WEB_API_ADAPTER === self::ADAPTER_SOAP) { + $requestData['cartItem'] += [ + 'quote_id' => $cartId, + 'itemId' => $itemId, ]; } if ($errorMessage) { From 820d3a4d7b76c7eb185babef252378ab8383f5de Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Thu, 5 Mar 2020 17:05:19 +0200 Subject: [PATCH 196/369] improve widget name --- .../Magento/Backend/view/adminhtml/web/js/dashboard/totals.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/totals.js b/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/totals.js index 18953140e5b62..2a696fe7bb38c 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/totals.js +++ b/app/code/Magento/Backend/view/adminhtml/web/js/dashboard/totals.js @@ -11,7 +11,7 @@ define([ ], function ($) { 'use strict'; - $.widget('mage.graph', { + $.widget('mage.dashboardTotals', { options: { updateUrl: '', periodSelect: null @@ -56,5 +56,5 @@ define([ } }); - return $.mage.graph; + return $.mage.dashboardTotals; }); From 01cde22ee1442d292802c7c36f97266ccd33c11c Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <ihor-sviziev@users.noreply.github.com> Date: Thu, 5 Mar 2020 18:44:13 +0200 Subject: [PATCH 197/369] Improve code style --- lib/internal/Magento/Framework/View/Page/Config/Renderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php index 3e7000fabfbd3..80f6fcbfc1b54 100644 --- a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php +++ b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php @@ -414,7 +414,7 @@ protected function renderAssetHtml(\Magento\Framework\View\Asset\PropertyGroup $ $attributes = $this->getGroupAttributes($group); $result = ''; - $template= ''; + $template = ''; try { /** @var $asset \Magento\Framework\View\Asset\AssetInterface */ foreach ($assets as $asset) { From a53460655e9c6cdacaaf0550b75b8fc1d63221cb Mon Sep 17 00:00:00 2001 From: Soumya Unnikrishnan <sunnikri@adobe.com> Date: Thu, 5 Mar 2020 11:42:48 -0600 Subject: [PATCH 198/369] MQE-1993: Refactor MFTF tests/actionGroups using <executeInSelenium> Stabilizing builds --- ...ifyCategoryProductAndProductCategoryPartialReindexTest.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml index d1a91369359bd..7ef1619319289 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml @@ -57,6 +57,7 @@ <wait stepKey="waitBeforeRunCronIndex" time="30"/> <magentoCLI stepKey="runCronIndex" command="cron:run --group=index"/> + <wait stepKey="waitAfterRunCronIndex" time="60"/> </before> <after> <!-- Change "Category Products" and "Product Categories" indexers to "Update on Save" mode --> @@ -141,6 +142,7 @@ <!-- Run cron --> <wait stepKey="waitBeforeRunMagentoCron" time="30"/> <magentoCLI stepKey="runMagentoCron" command="cron:run --group=index"/> + <wait stepKey="waitAfterRunMagentoCron" time="60"/> <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> <!-- Category K contains only Products A, C --> @@ -202,7 +204,7 @@ <!-- Run Cron once to reindex product changes --> <wait stepKey="waitBeforeRunCronIndexAfterProductAssignToCategory" time="30"/> <magentoCLI stepKey="runCronIndexAfterProductAssignToCategory" command="cron:run --group=index"/> - <wait stepKey="waitAfterRunCronIndexAfterProductAssignToCategory" time="30"/> + <wait stepKey="waitAfterRunCronIndexAfterProductAssignToCategory" time="60"/> <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> From c0937333ce178e50fada4a27a64477e98b2c121d Mon Sep 17 00:00:00 2001 From: Anton Kaplya <a.kaplya@gmail.com> Date: Thu, 5 Mar 2020 13:23:28 -0600 Subject: [PATCH 199/369] Revert "Make sure image metadata is not fetched from remote storage every time" --- .../Product/Helper/Form/Gallery/Content.php | 82 +- .../Magento/Catalog/Block/Product/Gallery.php | 51 +- .../Adminhtml/Product/Gallery/Upload.php | 20 +- app/code/Magento/Catalog/Helper/Image.php | 8 +- .../Source/Web/CatalogMediaUrlFormat.php | 2 +- .../Magento/Catalog/Model/ImageUploader.php | 15 - app/code/Magento/Catalog/Model/Product.php | 4 - .../Model/Product/Gallery/CreateHandler.php | 40 +- .../Magento/Catalog/Model/Product/Image.php | 69 +- .../ResourceModel/Product/Collection.php | 40 +- .../Model/ResourceModel/Product/Gallery.php | 70 +- .../Model/View/Asset/Image/Context.php | 5 +- .../Config/CatalogClone/Media/ImageTest.php | 153 +++ .../ResourceModel/Product/CollectionTest.php | 40 + .../ResourceModel/Product/GalleryTest.php | 198 ++- app/code/Magento/Catalog/etc/db_schema.xml | 1 - .../Catalog/etc/db_schema_whitelist.json | 3 +- .../Form/Modifier/Data/AssociatedProducts.php | 3 +- .../Console/Command/ImagesResizeCommand.php | 3 +- .../MediaStorage/Service/ImageResize.php | 18 +- .../Test/Unit/Service/ImageResizeTest.php | 60 +- app/code/Magento/MediaStorage/etc/di.xml | 7 - .../Adminhtml/Wysiwyg/Files/ContentTest.php | 220 ++++ app/etc/di.xml | 9 - composer.json | 9 +- composer.lock | 1139 ++++++++--------- .../Catalog/Model/ImageUploaderTest.php | 165 +++ .../Model/Product/Gallery/ReadHandlerTest.php | 6 +- .../SimplePolicyHeaderRendererTest.php | 4 - .../Php/_files/phpstan/blacklist/common.txt | 2 - .../HTTP/PhpEnvironment/Response.php | 9 - .../AdapterFactoryInterface.php | 25 - .../Storage/AdapterFactory/AwsS3Factory.php | 31 - .../Storage/AdapterFactory/AzureFactory.php | 32 - .../Storage/AdapterFactory/LocalFactory.php | 31 - .../Storage/FileNotFoundException.php | 17 - .../InvalidStorageConfigurationException.php | 14 - .../Magento/Framework/Storage/README.md | 121 -- .../Storage/RootViolationException.php | 17 - .../Magento/Framework/Storage/Storage.php | 77 -- .../Storage/StorageAdapterProvider.php | 63 - .../Framework/Storage/StorageInterface.php | 57 - .../Framework/Storage/StorageProvider.php | 102 -- .../Storage/UnsupportedStorageException.php | 13 - .../Test/Unit/Module/I18n/ContextTest.php | 153 +++ 45 files changed, 1637 insertions(+), 1571 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php create mode 100644 app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Wysiwyg/Files/ContentTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Model/ImageUploaderTest.php delete mode 100644 lib/internal/Magento/Framework/Storage/AdapterFactory/AdapterFactoryInterface.php delete mode 100644 lib/internal/Magento/Framework/Storage/AdapterFactory/AwsS3Factory.php delete mode 100644 lib/internal/Magento/Framework/Storage/AdapterFactory/AzureFactory.php delete mode 100644 lib/internal/Magento/Framework/Storage/AdapterFactory/LocalFactory.php delete mode 100644 lib/internal/Magento/Framework/Storage/FileNotFoundException.php delete mode 100644 lib/internal/Magento/Framework/Storage/InvalidStorageConfigurationException.php delete mode 100644 lib/internal/Magento/Framework/Storage/README.md delete mode 100644 lib/internal/Magento/Framework/Storage/RootViolationException.php delete mode 100644 lib/internal/Magento/Framework/Storage/Storage.php delete mode 100644 lib/internal/Magento/Framework/Storage/StorageAdapterProvider.php delete mode 100644 lib/internal/Magento/Framework/Storage/StorageInterface.php delete mode 100644 lib/internal/Magento/Framework/Storage/StorageProvider.php delete mode 100644 lib/internal/Magento/Framework/Storage/UnsupportedStorageException.php create mode 100644 setup/src/Magento/Setup/Test/Unit/Module/I18n/ContextTest.php diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php index 4cf67858fe287..8e6011c09a27f 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php @@ -13,20 +13,16 @@ */ namespace Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery; -use Magento\Backend\Block\DataProviders\ImageUploadConfig as ImageUploadConfigDataProvider; +use Magento\Framework\App\ObjectManager; use Magento\Backend\Block\Media\Uploader; +use Magento\Framework\View\Element\AbstractBlock; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\FileSystemException; -use Magento\Framework\Storage\FileNotFoundException; -use Magento\Framework\Storage\StorageProvider; -use Magento\Framework\View\Element\AbstractBlock; +use Magento\Backend\Block\DataProviders\ImageUploadConfig as ImageUploadConfigDataProvider; use Magento\MediaStorage\Helper\File\Storage\Database; /** * Block for gallery content. - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Content extends \Magento\Backend\Block\Widget { @@ -59,21 +55,11 @@ class Content extends \Magento\Backend\Block\Widget * @var Database */ private $fileStorageDatabase; - /** - * @var StorageProvider - */ - private $storageProvider; - - /** - * @var \Magento\Framework\Filesystem\Directory\ReadInterface - */ - private $mediaDirectory; /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig - * @param StorageProvider $storageProvider * @param array $data * @param ImageUploadConfigDataProvider $imageUploadConfigDataProvider * @param Database $fileStorageDatabase @@ -82,7 +68,6 @@ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\Json\EncoderInterface $jsonEncoder, \Magento\Catalog\Model\Product\Media\Config $mediaConfig, - StorageProvider $storageProvider, array $data = [], ImageUploadConfigDataProvider $imageUploadConfigDataProvider = null, Database $fileStorageDatabase = null @@ -90,12 +75,10 @@ public function __construct( $this->_jsonEncoder = $jsonEncoder; $this->_mediaConfig = $mediaConfig; parent::__construct($context, $data); - $this->mediaDirectory = $this->_filesystem->getDirectoryRead(DirectoryList::MEDIA); $this->imageUploadConfigDataProvider = $imageUploadConfigDataProvider ?: ObjectManager::getInstance()->get(ImageUploadConfigDataProvider::class); $this->fileStorageDatabase = $fileStorageDatabase ?: ObjectManager::getInstance()->get(Database::class); - $this->storageProvider = $storageProvider; } /** @@ -174,49 +157,10 @@ public function getAddImagesButton() ); } - /** - * Sync images to database - * - * @param string $fileName - */ - private function syncImageToDatabase(string $fileName): void - { - if ($this->fileStorageDatabase->checkDbUsage() && - !$this->mediaDirectory->isFile($this->_mediaConfig->getMediaPath($fileName)) - ) { - $this->fileStorageDatabase->saveFileToFilesystem( - $this->_mediaConfig->getMediaPath($fileName) - ); - } - } - - /** - * Returns file metadata as an associative array - * - * @param string $fileName - * @return array - * @throws FileNotFoundException - */ - private function getFileMetadata(string $fileName): array - { - $metadata = []; - try { - $info = $this->storageProvider->get('media') - ->getMetadata($this->_mediaConfig->getMediaPath($fileName)); - $metadata['size'] = $info['size']; - } catch (FileSystemException $e) { - $metadata['url'] = $this->getImageHelper()->getDefaultPlaceholderUrl('small_image'); - $metadata['size'] = 0; - $this->_logger->warning($e); - } - return $metadata; - } - /** * Returns image json * * @return string - * @throws FileNotFoundException */ public function getImagesJson() { @@ -226,14 +170,24 @@ public function getImagesJson() is_array($value['images']) && count($value['images']) ) { + $mediaDir = $this->_filesystem->getDirectoryRead(DirectoryList::MEDIA); $images = $this->sortImagesByPosition($value['images']); foreach ($images as &$image) { $image['url'] = $this->_mediaConfig->getMediaUrl($image['file']); - $this->syncImageToDatabase($image['file']); - if (isset($image['image_metadata']) && is_array($image['image_metadata'])) { - $image = array_replace_recursive($image, $image['image_metadata']); - } else { - $image = array_replace_recursive($image, $this->getFileMetadata($image['file'])); + if ($this->fileStorageDatabase->checkDbUsage() && + !$mediaDir->isFile($this->_mediaConfig->getMediaPath($image['file'])) + ) { + $this->fileStorageDatabase->saveFileToFilesystem( + $this->_mediaConfig->getMediaPath($image['file']) + ); + } + try { + $fileHandler = $mediaDir->stat($this->_mediaConfig->getMediaPath($image['file'])); + $image['size'] = $fileHandler['size']; + } catch (FileSystemException $e) { + $image['url'] = $this->getImageHelper()->getDefaultPlaceholderUrl('small_image'); + $image['size'] = 0; + $this->_logger->warning($e); } } return $this->_jsonEncoder->encode($images); diff --git a/app/code/Magento/Catalog/Block/Product/Gallery.php b/app/code/Magento/Catalog/Block/Product/Gallery.php index 2e9dcd1fe6952..54f848a92e958 100644 --- a/app/code/Magento/Catalog/Block/Product/Gallery.php +++ b/app/code/Magento/Catalog/Block/Product/Gallery.php @@ -11,13 +11,9 @@ */ namespace Magento\Catalog\Block\Product; -use Magento\Framework\Storage\FileNotFoundException; use Magento\Catalog\Model\Product; -use Magento\Catalog\Model\Product\Media\Config; -use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Data\Collection; -use Magento\Framework\Registry; -use Magento\Framework\Storage\StorageProvider; /** * Product gallery block @@ -30,37 +26,22 @@ class Gallery extends \Magento\Framework\View\Element\Template /** * Core registry * - * @var Registry + * @var \Magento\Framework\Registry */ protected $_coreRegistry = null; - /** - * @var StorageProvider - */ - private $storageProvider; - /** - * @var Config - */ - private $mediaConfig; - /** * @param \Magento\Framework\View\Element\Template\Context $context - * @param Registry $registry + * @param \Magento\Framework\Registry $registry * @param array $data - * @param StorageProvider $storageProvider - * @param Config $mediaConfig */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, - Registry $registry, - array $data = [], - StorageProvider $storageProvider = null, - Config $mediaConfig = null + \Magento\Framework\Registry $registry, + array $data = [] ) { $this->_coreRegistry = $registry; parent::__construct($context, $data); - $this->storageProvider = $storageProvider ?? ObjectManager::getInstance()->get(StorageProvider::class); - $this->mediaConfig = $mediaConfig ?? ObjectManager::getInstance()->get(Config::class); } /** @@ -140,24 +121,16 @@ public function getImageFile() */ public function getImageWidth() { - $file = $this->getCurrentImage()->getFile(); - if (!$file) { - return false; - } - $productMediaFile = $this->mediaConfig->getMediaPath($file); - - $mediaStorage = $this->storageProvider->get('media'); - if ($mediaStorage->has($productMediaFile)) { - try { - $meta = $mediaStorage->getMetadata($productMediaFile); - $size = $meta['size']; - if ($size > 600) { + $file = $this->getCurrentImage()->getPath(); + + if ($this->_filesystem->getDirectoryRead(DirectoryList::MEDIA)->isFile($file)) { + $size = getimagesize($file); + if (isset($size[0])) { + if ($size[0] > 600) { return 600; } else { - return (int) $size; + return (int) $size[0]; } - } catch (FileNotFoundException $e) { - return false; } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php index fda3d0abced7f..3e7cc3ee962b9 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php @@ -8,7 +8,7 @@ use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Storage\StorageProvider; +use Magento\Framework\Exception\LocalizedException; /** * Upload product image action controller @@ -52,15 +52,9 @@ class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterf */ private $productMediaConfig; - /** - * @var StorageProvider - */ - private $storageProvider; - /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory - * @param StorageProvider $storageProvider * @param \Magento\Framework\Image\AdapterFactory $adapterFactory * @param \Magento\Framework\Filesystem $filesystem * @param \Magento\Catalog\Model\Product\Media\Config $productMediaConfig @@ -68,7 +62,6 @@ class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterf public function __construct( \Magento\Backend\App\Action\Context $context, \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, - StorageProvider $storageProvider, \Magento\Framework\Image\AdapterFactory $adapterFactory = null, \Magento\Framework\Filesystem $filesystem = null, \Magento\Catalog\Model\Product\Media\Config $productMediaConfig = null @@ -81,7 +74,6 @@ public function __construct( ->get(\Magento\Framework\Filesystem::class); $this->productMediaConfig = $productMediaConfig ?: ObjectManager::getInstance() ->get(\Magento\Catalog\Model\Product\Media\Config::class); - $this->storageProvider = $storageProvider; } /** @@ -92,7 +84,6 @@ public function __construct( public function execute() { try { - /** @var \Magento\MediaStorage\Model\File\Uploader $uploader */ $uploader = $this->_objectManager->create( \Magento\MediaStorage\Model\File\Uploader::class, ['fileId' => 'image'] @@ -102,18 +93,11 @@ public function execute() $uploader->addValidateCallback('catalog_product_image', $imageAdapter, 'validateUploadFile'); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(true); - $mediaDirectory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); - $baseImagePath = $this->productMediaConfig->getBaseTmpMediaPath(); $result = $uploader->save( - $mediaDirectory->getAbsolutePath($baseImagePath) + $mediaDirectory->getAbsolutePath($this->productMediaConfig->getBaseTmpMediaPath()) ); - $origFile = $this->productMediaConfig->getTmpMediaPath($result['file']); - $storage = $this->storageProvider->get('media'); - $content = $mediaDirectory->readFile($origFile); - $storage->put($origFile, $content); - $this->_eventManager->dispatch( 'catalog_product_gallery_upload_image_after', ['result' => $result, 'action' => $this] diff --git a/app/code/Magento/Catalog/Helper/Image.php b/app/code/Magento/Catalog/Helper/Image.php index f220fa0ef0444..5b0aa0c496ecd 100644 --- a/app/code/Magento/Catalog/Helper/Image.php +++ b/app/code/Magento/Catalog/Helper/Image.php @@ -13,7 +13,7 @@ use Magento\Framework\View\Element\Block\ArgumentInterface; /** - * Catalog image helper + * Catalog image helper. * * @api * @SuppressWarnings(PHPMD.TooManyFields) @@ -166,8 +166,7 @@ public function __construct( $this->_assetRepo = $assetRepo; $this->viewConfig = $viewConfig; $this->viewAssetPlaceholderFactory = $placeholderFactory - ?: ObjectManager::getInstance() - ->get(PlaceholderFactory::class); + ?: ObjectManager::getInstance()->get(PlaceholderFactory::class); $this->mediaConfig = $mediaConfig ?: ObjectManager::getInstance()->get(CatalogMediaConfig::class); } @@ -574,9 +573,6 @@ public function save() * Return resized product image information * * @return array - * @deprecated Magento is not responsible for image resizing anymore. This method works with local filesystem only. - * Service that provides resized images should guarantee that the image sizes correspond to requested ones. - * Use `getWidth()` and `getHeight()` instead. */ public function getResizedImageInfo() { diff --git a/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php b/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php index 0ceeeb596655d..f24044fc92c95 100644 --- a/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php +++ b/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php @@ -24,7 +24,7 @@ public function toOptionArray() 'value' => CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS, 'label' => __('Image optimization based on query parameters') ], - ['value' => CatalogMediaConfig::HASH, 'label' => __('Legacy mode (unique hash per image variant)')] + ['value' => CatalogMediaConfig::HASH, 'label' => __('Unique hash per image variant (Legacy mode)')] ]; } } diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php index d333ea589b997..0c3e008fa8bb5 100644 --- a/app/code/Magento/Catalog/Model/ImageUploader.php +++ b/app/code/Magento/Catalog/Model/ImageUploader.php @@ -6,7 +6,6 @@ namespace Magento\Catalog\Model; use Magento\Framework\File\Uploader; -use Magento\Framework\Storage\StorageProvider; /** * Catalog image uploader @@ -74,11 +73,6 @@ class ImageUploader */ private $allowedMimeTypes; - /** - * @var StorageProvider - */ - private $storageProvider; - /** * ImageUploader constructor * @@ -90,9 +84,7 @@ class ImageUploader * @param string $baseTmpPath * @param string $basePath * @param string[] $allowedExtensions - * @param StorageProvider $storageProvider * @param string[] $allowedMimeTypes - * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase, @@ -103,7 +95,6 @@ public function __construct( $baseTmpPath, $basePath, $allowedExtensions, - StorageProvider $storageProvider, $allowedMimeTypes = [] ) { $this->coreFileStorageDatabase = $coreFileStorageDatabase; @@ -115,7 +106,6 @@ public function __construct( $this->basePath = $basePath; $this->allowedExtensions = $allowedExtensions; $this->allowedMimeTypes = $allowedMimeTypes; - $this->storageProvider = $storageProvider; } /** @@ -230,11 +220,6 @@ public function moveFileFromTmp($imageName, $returnRelativePath = false) $baseTmpImagePath, $baseImagePath ); - - $storage = $this->storageProvider->get('media'); - $content = $this->mediaDirectory->readFile($baseImagePath); - $storage->put($baseImagePath, $content); - } catch (\Exception $e) { throw new \Magento\Framework\Exception\LocalizedException( __('Something went wrong while saving the file(s).') diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index c5dce0df14755..a9907c1661bd8 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -1540,11 +1540,7 @@ public function getMediaGalleryImages() } $image['url'] = $this->getMediaConfig()->getMediaUrl($image['file']); $image['id'] = $image['value_id']; - - // @deprecated 'path' should not be used - // The file can be absent in local filesystem if remote storage is used $image['path'] = $directory->getAbsolutePath($this->getMediaConfig()->getMediaPath($image['file'])); - $images->addItem(new \Magento\Framework\DataObject($image)); } $this->setData('media_gallery_images', $images); diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php index e56fb8e59d0e9..225a3a4c44a9b 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php @@ -11,7 +11,6 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; use Magento\Framework\EntityManager\Operation\ExtensionInterface; -use Magento\Framework\Storage\StorageProvider; use Magento\MediaStorage\Model\File\Uploader as FileUploader; use Magento\Store\Model\StoreManagerInterface; @@ -89,10 +88,6 @@ class CreateHandler implements ExtensionInterface * @var \Magento\Store\Model\StoreManagerInterface */ private $storeManager; - /** - * @var StorageProvider - */ - private $storageProvider; /** * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool @@ -103,7 +98,6 @@ class CreateHandler implements ExtensionInterface * @param \Magento\Framework\Filesystem $filesystem * @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb * @param \Magento\Store\Model\StoreManagerInterface|null $storeManager - * @param StorageProvider $storageProvider * @throws \Magento\Framework\Exception\FileSystemException */ public function __construct( @@ -114,8 +108,7 @@ public function __construct( \Magento\Catalog\Model\Product\Media\Config $mediaConfig, \Magento\Framework\Filesystem $filesystem, \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb, - \Magento\Store\Model\StoreManagerInterface $storeManager = null, - StorageProvider $storageProvider = null + \Magento\Store\Model\StoreManagerInterface $storeManager = null ) { $this->metadata = $metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); $this->attributeRepository = $attributeRepository; @@ -125,7 +118,6 @@ public function __construct( $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); $this->fileStorageDb = $fileStorageDb; $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); - $this->storageProvider = $storageProvider ?: ObjectManager::getInstance()->get(StorageProvider::class); } /** @@ -253,6 +245,7 @@ public function getAttribute() 'media_gallery' ); } + return $this->attribute; } @@ -297,8 +290,7 @@ protected function processNewAndExistingImages($product, array &$images) $data['position'] = isset($image['position']) ? (int)$image['position'] : 0; $data['disabled'] = isset($image['disabled']) ? (int)$image['disabled'] : 0; $data['store_id'] = (int)$product->getStoreId(); - $stat = $this->mediaDirectory->stat($this->mediaConfig->getMediaPath($image['file'])); - $data['image_metadata']['size'] = $stat['size']; + $data[$this->metadata->getLinkField()] = (int)$product->getData($this->metadata->getLinkField()); $this->resourceModel->insertGalleryValueInStore($data); @@ -374,20 +366,20 @@ protected function moveImageFromTmp($file) $file = $this->getFilenameFromTmp($this->getSafeFilename($file)); $destinationFile = $this->getUniqueFileName($file); - $tmpMediaPath = $this->mediaConfig->getTmpMediaPath($file); - $mediaPath = $this->mediaConfig->getMediaPath($destinationFile); - $this->mediaDirectory->renameFile( - $tmpMediaPath, - $mediaPath - ); - $this->fileStorageDb->renameFile( - $this->mediaConfig->getTmpMediaShortUrl($file), - $this->mediaConfig->getMediaShortUrl($destinationFile) - ); + if ($this->fileStorageDb->checkDbUsage()) { + $this->fileStorageDb->renameFile( + $this->mediaConfig->getTmpMediaShortUrl($file), + $this->mediaConfig->getMediaShortUrl($destinationFile) + ); - $storage = $this->storageProvider->get('media'); - $content = $this->mediaDirectory->readFile($mediaPath); - $storage->put($mediaPath, $content); + $this->mediaDirectory->delete($this->mediaConfig->getTmpMediaPath($file)); + $this->mediaDirectory->delete($this->mediaConfig->getMediaPath($destinationFile)); + } else { + $this->mediaDirectory->renameFile( + $this->mediaConfig->getTmpMediaPath($file), + $this->mediaConfig->getMediaPath($destinationFile) + ); + } return str_replace('\\', '/', $destinationFile); } diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index 6a0032ca694a5..7c2a53768fd47 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -14,8 +14,7 @@ use Magento\Framework\Image as MagentoImage; use Magento\Framework\Serialize\SerializerInterface; use Magento\Catalog\Model\Product\Image\ParamsBuilder; -use Magento\Framework\Storage\StorageInterface; -use Magento\Framework\Storage\StorageProvider; +use Magento\Framework\Filesystem\Driver\File as FilesystemDriver; /** * Image operations @@ -204,9 +203,9 @@ class Image extends \Magento\Framework\Model\AbstractModel private $serializer; /** - * @var StorageInterface + * @var FilesystemDriver */ - private $storage; + private $filesystemDriver; /** * Constructor @@ -223,12 +222,12 @@ class Image extends \Magento\Framework\Model\AbstractModel * @param ImageFactory $viewAssetImageFactory * @param PlaceholderFactory $viewAssetPlaceholderFactory * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param StorageProvider $storageProvider * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data * @param SerializerInterface $serializer * @param ParamsBuilder $paramsBuilder + * @param FilesystemDriver $filesystemDriver * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedLocalVariable) @@ -246,30 +245,27 @@ public function __construct( ImageFactory $viewAssetImageFactory, PlaceholderFactory $viewAssetPlaceholderFactory, \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - StorageProvider $storageProvider, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], SerializerInterface $serializer = null, - ParamsBuilder $paramsBuilder = null + ParamsBuilder $paramsBuilder = null, + FilesystemDriver $filesystemDriver = null ) { $this->_storeManager = $storeManager; $this->_catalogProductMediaConfig = $catalogProductMediaConfig; - - $this->_mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); $this->_coreFileStorageDatabase = $coreFileStorageDatabase; - $this->_imageFactory = $imageFactory; - $this->viewAssetImageFactory = $viewAssetImageFactory; - - $this->storage = $storageProvider->get('media'); - parent::__construct($context, $registry, $resource, $resourceCollection, $data); + $this->_mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $this->_imageFactory = $imageFactory; $this->_assetRepo = $assetRepo; $this->_viewFileSystem = $viewFileSystem; $this->_scopeConfig = $scopeConfig; + $this->viewAssetImageFactory = $viewAssetImageFactory; $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); $this->paramsBuilder = $paramsBuilder ?: ObjectManager::getInstance()->get(ParamsBuilder::class); + $this->filesystemDriver = $filesystemDriver ?: ObjectManager::getInstance()->get(FilesystemDriver::class); } /** @@ -437,20 +433,19 @@ public function setBaseFile($file) { $this->_isBaseFilePlaceholder = false; - if ($file == 'no_selection' || empty($file)) { + $this->imageAsset = $this->viewAssetImageFactory->create( + [ + 'miscParams' => $this->getMiscParams(), + 'filePath' => $file, + ] + ); + if ($file == 'no_selection' || !$this->_fileExists($this->imageAsset->getSourceFile())) { $this->_isBaseFilePlaceholder = true; $this->imageAsset = $this->viewAssetPlaceholderFactory->create( [ 'type' => $this->getDestinationSubdir(), ] ); - } else { - $this->imageAsset = $this->viewAssetImageFactory->create( - [ - 'miscParams' => $this->getMiscParams(), - 'filePath' => $file, - ] - ); } $this->_baseFile = $this->imageAsset->getSourceFile(); @@ -682,7 +677,12 @@ public function getDestinationSubdir() public function isCached() { $path = $this->imageAsset->getPath(); - return is_array($this->loadImageInfoFromCache($path)) || $this->_mediaDirectory->isExist($path); + try { + $isCached = is_array($this->loadImageInfoFromCache($path)) || $this->filesystemDriver->isExists($path); + } catch (FileSystemException $e) { + $isCached = false; + } + return $isCached; } /** @@ -854,20 +854,35 @@ public function clearCache() { $directory = $this->_catalogProductMediaConfig->getBaseMediaPath() . '/cache'; $this->_mediaDirectory->delete($directory); - $this->storage->deleteDir($directory); $this->_coreFileStorageDatabase->deleteFolder($this->_mediaDirectory->getAbsolutePath($directory)); $this->clearImageInfoFromCache(); } + /** + * First check this file on FS + * + * If it doesn't exist - try to download it from DB + * + * @param string $filename + * @return bool + */ + protected function _fileExists($filename) + { + if ($this->_mediaDirectory->isFile($filename)) { + return true; + } else { + return $this->_coreFileStorageDatabase->saveFileToFilesystem( + $this->_mediaDirectory->getAbsolutePath($filename) + ); + } + } + /** * Return resized product image information * * @return array * @throws NotLoadInfoImageException - * @deprecated Magento is not responsible for image resizing anymore. This method works with local filesystem only. - * Service that provides resized images should guarantee that the image sizes correspond to requested ones. - * Use `getWidth()` and `getHeight()` instead. */ public function getResizedImageInfo() { diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 12e3c6b53d701..14daac0147abf 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -12,7 +12,6 @@ use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus; use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler; -use Magento\Catalog\Model\ResourceModel\Category; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\CatalogUrlRewrite\Model\Storage\DbStorage; @@ -24,6 +23,7 @@ use Magento\Framework\Indexer\DimensionFactory; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\Store; +use Magento\Catalog\Model\ResourceModel\Category; /** * Product collection @@ -2337,35 +2337,49 @@ public function addPriceDataFieldFilter($comparisonFormat, $fields) * @SuppressWarnings(PHPMD.NPathComplexity) * @since 101.0.1 * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Zend_Db_Statement_Exception */ public function addMediaGalleryData() { if ($this->getFlag('media_gallery_added')) { return $this; } + if (!$this->getSize()) { return $this; } - if (!$this->isLoaded()) { - $this->load(); - } - $records = $this->getMediaGalleryResource()->getMediaRecords( - $this->getStoreId(), - $this->getLoadedIds() - ); + + $items = $this->getItems(); + $linkField = $this->getProductEntityMetadata()->getLinkField(); + + $select = $this->getMediaGalleryResource() + ->createBatchBaseSelect( + $this->getStoreId(), + $this->getAttribute('media_gallery')->getAttributeId() + )->reset( + Select::ORDER // we don't care what order is in current scenario + )->where( + 'entity.' . $linkField . ' IN (?)', + array_map( + function ($item) use ($linkField) { + return (int) $item->getOrigData($linkField); + }, + $items + ) + ); + $mediaGalleries = []; - foreach ($records as $record) { - $mediaGalleries[$record['entity_id']][] = $record; + foreach ($this->getConnection()->fetchAll($select) as $row) { + $mediaGalleries[$row[$linkField]][] = $row; } - foreach ($this->getItems() as $item) { + foreach ($items as $item) { $this->getGalleryReadHandler() ->addMediaDataToProduct( $item, - $mediaGalleries[$item->getId()] ?? [] + $mediaGalleries[$item->getOrigData($linkField)] ?? [] ); } + $this->setFlag('media_gallery_added', true); return $this; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php index 6acda0e574828..a9741cd8e1ec7 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php @@ -6,8 +6,6 @@ namespace Magento\Catalog\Model\ResourceModel\Product; -use Magento\Framework\DB\Select; -use Magento\Framework\DB\Sql\ColumnValueExpression; use Magento\Store\Model\Store; /** @@ -35,12 +33,9 @@ class Gallery extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $metadata; /** - * Gallery constructor. - * * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param string $connectionName - * @throws \Exception */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, @@ -50,6 +45,7 @@ public function __construct( $this->metadata = $metadataPool->getMetadata( \Magento\Catalog\Api\Data\ProductInterface::class ); + parent::__construct($context, $connectionName); } @@ -126,14 +122,19 @@ public function loadDataFromTableByValueId( * @param int $attributeId * @return array * @since 101.0.0 - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Zend_Db_Statement_Exception */ - public function loadProductGalleryByAttributeId($product, $attributeId = null) + public function loadProductGalleryByAttributeId($product, $attributeId) { - $result = $this->getMediaRecords($product->getStoreId(), [$product->getId()], true); + $select = $this->createBaseLoadSelect( + $product->getData($this->metadata->getLinkField()), + $product->getStoreId(), + $attributeId + ); + + $result = $this->getConnection()->fetchAll($select); + $this->removeDuplicates($result); + return $result; } @@ -143,7 +144,6 @@ public function loadProductGalleryByAttributeId($product, $attributeId = null) * @param int $entityId * @param int $storeId * @param int $attributeId - * @deprecated Misleading method, methods relies on autoincrement field instead of entity ID * @return \Magento\Framework\DB\Select * @throws \Magento\Framework\Exception\LocalizedException * @since 101.0.0 @@ -159,35 +159,6 @@ protected function createBaseLoadSelect($entityId, $storeId, $attributeId) return $select; } - /** - * Returns media entries from database - * - * @param int $storeId - * @param array $entityIds - * @param bool $preserveSortOrder - * @return array - * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Zend_Db_Statement_Exception - */ - public function getMediaRecords(int $storeId, array $entityIds, bool $preserveSortOrder = false) : array - { - $output = []; - $select = $this->createBatchBaseSelect($storeId) - ->where('cpe.entity_id IN (?)', $entityIds); - if (!$preserveSortOrder) { - // due to performance consideration it is better to do not use sorting for this query - $select->reset(Select::ORDER); - } - $cursor = $this->getConnection()->query($select); - while ($row = $cursor->fetch()) { - if (!empty($row['image_metadata'])) { - $row['image_metadata'] = $this->getSerializer()->unserialize($row['image_metadata']); - } - $output[] = $row; - } - return $output; - } - /** * Create batch base select * @@ -195,10 +166,9 @@ public function getMediaRecords(int $storeId, array $entityIds, bool $preserveSo * @param int $attributeId * @return \Magento\Framework\DB\Select * @throws \Magento\Framework\Exception\LocalizedException - * @SuppressWarnings(PHPMD.UnusedFormalParameter) Media gallery doesn't support other attributes than media_galley * @since 101.0.1 */ - public function createBatchBaseSelect($storeId, $attributeId = null) + public function createBatchBaseSelect($storeId, $attributeId) { $linkField = $this->metadata->getLinkField(); @@ -221,10 +191,6 @@ public function createBatchBaseSelect($storeId, $attributeId = null) ['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)], $mainTableAlias . '.value_id = entity.value_id', [$linkField] - )->joinInner( - ['cpe' => $this->getTable('catalog_product_entity')], - sprintf('cpe.%1$s = entity.%1$s', $linkField), - ['entity_id' => 'cpe.entity_id'] )->joinLeft( ['value' => $this->getTable(self::GALLERY_VALUE_TABLE)], implode( @@ -253,15 +219,16 @@ public function createBatchBaseSelect($storeId, $attributeId = null) 'disabled' => $this->getConnection()->getIfNullSql('`value`.`disabled`', '`default_value`.`disabled`'), 'label_default' => 'default_value.label', 'position_default' => 'default_value.position', - 'disabled_default' => 'default_value.disabled', - 'image_metadata' => new ColumnValueExpression( - 'JSON_MERGE_PATCH(default_value.image_metadata, value.image_metadata)' - ) + 'disabled_default' => 'default_value.disabled' ])->where( + $mainTableAlias . '.attribute_id = ?', + $attributeId + )->where( $mainTableAlias . '.disabled = 0' )->order( $positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC ); + return $select; } @@ -390,9 +357,6 @@ public function insertGalleryValueInStore($data) $this->getTable(self::GALLERY_VALUE_TABLE) ); - if (!empty($data['image_metadata'])) { - $data['image_metadata'] = $this->getSerializer()->serialize($data['image_metadata']); - } $this->getConnection()->insert( $this->getTable(self::GALLERY_VALUE_TABLE), $data diff --git a/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php b/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php index ead68c897f95f..49d150a31750c 100644 --- a/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php +++ b/app/code/Magento/Catalog/Model/View/Asset/Image/Context.php @@ -47,10 +47,11 @@ public function __construct( $this->mediaConfig = $mediaConfig; $this->filesystem = $filesystem; $this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $this->mediaDirectory->create($this->mediaConfig->getBaseMediaPath()); } /** - * @inheritdoc + * {@inheritdoc} */ public function getPath() { @@ -58,7 +59,7 @@ public function getPath() } /** - * @inheritdoc + * {@inheritdoc} */ public function getBaseUrl() { diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php new file mode 100644 index 0000000000000..23f0aec5b69a2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php @@ -0,0 +1,153 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\Config\CatalogClone\Media; + +use Magento\Catalog\Model\Product; +use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +/** + * Tests \Magento\Catalog\Model\Config\CatalogClone\Media\Image. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ImageTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Catalog\Model\Config\CatalogClone\Media\Image + */ + private $model; + + /** + * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $eavConfig; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $attributeCollectionFactory; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeCollection; + + /** + * @var \Magento\Eav\Model\Entity\Attribute|\PHPUnit_Framework_MockObject_MockObject + */ + private $attribute; + + /** + * @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject + */ + private $escaperMock; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->attributeCollection = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection::class + ) + ->disableOriginalConstructor() + ->getMock(); + + $this->attributeCollectionFactory = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory::class + ) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeCollectionFactory->expects($this->any())->method('create')->will( + $this->returnValue($this->attributeCollection) + ); + + $this->attribute = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->escaperMock = $this->getMockBuilder( + \Magento\Framework\Escaper::class + ) + ->disableOriginalConstructor() + ->setMethods(['escapeHtml']) + ->getMock(); + + $helper = new ObjectManager($this); + $this->model = $helper->getObject( + \Magento\Catalog\Model\Config\CatalogClone\Media\Image::class, + [ + 'eavConfig' => $this->eavConfig, + 'attributeCollectionFactory' => $this->attributeCollectionFactory, + 'escaper' => $this->escaperMock, + ] + ); + } + + /** + * @param string $actualLabel + * @param string $expectedLabel + * @return void + * + * @dataProvider getPrefixesDataProvider + */ + public function testGetPrefixes(string $actualLabel, string $expectedLabel): void + { + $entityTypeId = 3; + /** @var \Magento\Eav\Model\Entity\Type|\PHPUnit_Framework_MockObject_MockObject $entityType */ + $entityType = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) + ->disableOriginalConstructor() + ->getMock(); + $entityType->expects($this->once())->method('getId')->willReturn($entityTypeId); + + /** @var AbstractFrontend|\PHPUnit_Framework_MockObject_MockObject $frontend */ + $frontend = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend::class) + ->setMethods(['getLabel']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $frontend->expects($this->once())->method('getLabel')->willReturn($actualLabel); + + $this->attributeCollection->expects($this->once())->method('setEntityTypeFilter')->with($entityTypeId); + $this->attributeCollection->expects($this->once())->method('setFrontendInputTypeFilter')->with('media_image'); + + $this->attribute->expects($this->once())->method('getAttributeCode')->willReturn('attributeCode'); + $this->attribute->expects($this->once())->method('getFrontend')->willReturn($frontend); + + $this->attributeCollection->expects($this->any())->method('getIterator') + ->willReturn(new \ArrayIterator([$this->attribute])); + + $this->eavConfig->expects($this->any())->method('getEntityType')->with(Product::ENTITY) + ->willReturn($entityType); + + $this->escaperMock->expects($this->once())->method('escapeHtml')->with($actualLabel) + ->willReturn($expectedLabel); + + $this->assertEquals([['field' => 'attributeCode_', 'label' => $expectedLabel]], $this->model->getPrefixes()); + } + + /** + * @return array + */ + public function getPrefixesDataProvider(): array + { + return [ + [ + 'actual_label' => 'testLabel', + 'expected_label' => 'testLabel', + ], + [ + 'actual_label' => '<media-image-attributelabel', + 'expected_label' => '<media-image-attributelabel', + ], + ]; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php index b49decf96452d..0316b2e374d2f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php @@ -230,6 +230,46 @@ public function testAddProductCategoriesFilter() $this->collection->addCategoriesFilter([$conditionType => $values]); } + public function testAddMediaGalleryData() + { + $attributeId = 42; + $rowId = 4; + $linkField = 'row_id'; + $mediaGalleriesMock = [[$linkField => $rowId]]; + $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['getOrigData']) + ->getMock(); + $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->getMock(); + $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $metadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->collection->addItem($itemMock); + $this->galleryResourceMock->expects($this->once())->method('createBatchBaseSelect')->willReturn($selectMock); + $attributeMock->expects($this->once())->method('getAttributeId')->willReturn($attributeId); + $this->entityMock->expects($this->once())->method('getAttribute')->willReturn($attributeMock); + $itemMock->expects($this->atLeastOnce())->method('getOrigData')->willReturn($rowId); + $selectMock->expects($this->once())->method('reset')->with(Select::ORDER)->willReturnSelf(); + $selectMock->expects($this->once())->method('where')->with('entity.' . $linkField . ' IN (?)', [$rowId]) + ->willReturnSelf(); + $this->metadataPoolMock->expects($this->once())->method('getMetadata')->willReturn($metadataMock); + $metadataMock->expects($this->once())->method('getLinkField')->willReturn($linkField); + + $this->connectionMock->expects($this->once())->method('fetchOne')->with($selectMock)->willReturn(42); + $this->connectionMock->expects($this->once())->method('fetchAll')->with($selectMock)->willReturn( + [['row_id' => $rowId]] + ); + $this->galleryReadHandlerMock->expects($this->once())->method('addMediaDataToProduct') + ->with($itemMock, $mediaGalleriesMock); + + $this->assertSame($this->collection, $this->collection->addMediaGalleryData()); + } + /** * Test addTierPriceDataByGroupId method. * diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php index 43c1abc91a1a9..47ef3c999125f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php @@ -5,8 +5,6 @@ */ namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product; -use Magento\Framework\DB\Sql\ColumnValueExpression; - /** * Unit test for product media gallery resource. */ @@ -282,4 +280,200 @@ public function testBindValueToEntityRecordExists() $entityId = 1; $this->resource->bindValueToEntity($valueId, $entityId); } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testLoadGallery() + { + $productId = 5; + $storeId = 1; + $attributeId = 6; + $getTableReturnValue = 'table'; + $quoteInfoReturnValue = + 'main.value_id = value.value_id AND value.store_id = ' . $storeId + . ' AND value.entity_id = entity.entity_id'; + $quoteDefaultInfoReturnValue = + 'main.value_id = default_value.value_id AND default_value.store_id = 0' + . ' AND default_value.entity_id = entity.entity_id'; + + $positionCheckSql = 'testchecksql'; + $resultRow = [ + [ + 'value_id' => '1', + 'file' => '/d/o/download_7.jpg', + 'label' => null, + 'position' => '1', + 'disabled' => '0', + 'label_default' => null, + 'position_default' => '1', + 'disabled_default' => '0', + ], + ]; + + $this->connection->expects($this->once())->method('getCheckSql')->with( + 'value.position IS NULL', + 'default_value.position', + 'value.position' + )->will($this->returnValue($positionCheckSql)); + $this->connection->expects($this->once())->method('select')->will($this->returnValue($this->select)); + $this->select->expects($this->at(0))->method('from')->with( + [ + 'main' => $getTableReturnValue, + ], + [ + 'value_id', + 'file' => 'value', + 'media_type' + ] + )->willReturnSelf(); + $this->select->expects($this->at(1))->method('joinInner')->with( + ['entity' => $getTableReturnValue], + 'main.value_id = entity.value_id', + ['entity_id'] + )->willReturnSelf(); + $this->product->expects($this->at(0))->method('getData') + ->with('entity_id')->willReturn($productId); + $this->product->expects($this->at(1))->method('getStoreId')->will($this->returnValue($storeId)); + $this->connection->expects($this->exactly(2))->method('quoteInto')->withConsecutive( + ['value.store_id = ?'], + ['default_value.store_id = ?'] + )->willReturnOnConsecutiveCalls( + 'value.store_id = ' . $storeId, + 'default_value.store_id = ' . 0 + ); + $this->connection->expects($this->any())->method('getIfNullSql')->will( + $this->returnValueMap([ + [ + '`value`.`label`', + '`default_value`.`label`', + 'IFNULL(`value`.`label`, `default_value`.`label`)' + ], + [ + '`value`.`position`', + '`default_value`.`position`', + 'IFNULL(`value`.`position`, `default_value`.`position`)' + ], + [ + '`value`.`disabled`', + '`default_value`.`disabled`', + 'IFNULL(`value`.`disabled`, `default_value`.`disabled`)' + ] + ]) + ); + $this->select->expects($this->at(2))->method('joinLeft')->with( + ['value' => $getTableReturnValue], + $quoteInfoReturnValue, + [] + )->willReturnSelf(); + $this->select->expects($this->at(3))->method('joinLeft')->with( + ['default_value' => $getTableReturnValue], + $quoteDefaultInfoReturnValue, + [] + )->willReturnSelf(); + $this->select->expects($this->at(4))->method('columns')->with([ + 'label' => 'IFNULL(`value`.`label`, `default_value`.`label`)', + 'position' => 'IFNULL(`value`.`position`, `default_value`.`position`)', + 'disabled' => 'IFNULL(`value`.`disabled`, `default_value`.`disabled`)', + 'label_default' => 'default_value.label', + 'position_default' => 'default_value.position', + 'disabled_default' => 'default_value.disabled' + ])->willReturnSelf(); + $this->select->expects($this->at(5))->method('where')->with( + 'main.attribute_id = ?', + $attributeId + )->willReturnSelf(); + $this->select->expects($this->at(6))->method('where') + ->with('main.disabled = 0')->willReturnSelf(); + $this->select->expects($this->at(8))->method('where') + ->with('entity.entity_id = ?', $productId) + ->willReturnSelf(); + $this->select->expects($this->once())->method('order') + ->with($positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC) + ->willReturnSelf(); + $this->connection->expects($this->once())->method('fetchAll') + ->with($this->select) + ->willReturn($resultRow); + + $this->assertEquals($resultRow, $this->resource->loadProductGalleryByAttributeId($this->product, $attributeId)); + } + + public function testInsertGalleryValueInStore() + { + $data = [ + 'value_id' => '8', + 'store_id' => 0, + 'provider' => '', + 'url' => 'https://www.youtube.com/watch?v=abcdfghijk', + 'title' => 'New Title', + 'description' => 'New Description', + 'metadata' => 'New metadata', + ]; + + $this->connection->expects($this->once())->method('describeTable')->willReturn($this->fields); + $this->connection->expects($this->any())->method('prepareColumnValue')->willReturnOnConsecutiveCalls( + '8', + 0, + '', + 'https://www.youtube.com/watch?v=abcdfghijk', + 'New Title', + 'New Description', + 'New metadata' + ); + + $this->resource->insertGalleryValueInStore($data); + } + + public function testDeleteGalleryValueInStore() + { + $valueId = 4; + $entityId = 6; + $storeId = 1; + + $this->connection->expects($this->exactly(3))->method('quoteInto')->withConsecutive( + ['value_id = ?', (int)$valueId], + ['entity_id = ?', (int)$entityId], + ['store_id = ?', (int)$storeId] + )->willReturnOnConsecutiveCalls( + 'value_id = ' . $valueId, + 'entity_id = ' . $entityId, + 'store_id = ' . $storeId + ); + + $this->connection->expects($this->once())->method('delete')->with( + 'table', + 'value_id = 4 AND entity_id = 6 AND store_id = 1' + )->willReturnSelf(); + + $this->resource->deleteGalleryValueInStore($valueId, $entityId, $storeId); + } + + public function testCountImageUses() + { + $results = [ + [ + 'value_id' => '1', + 'attribute_id' => 90, + 'value' => '/d/o/download_7.jpg', + 'media_type' => 'image', + 'disabled' => '0', + ], + ]; + + $this->connection->expects($this->once())->method('select')->will($this->returnValue($this->select)); + $this->select->expects($this->at(0))->method('from')->with( + [ + 'main' => 'table', + ], + '*' + )->willReturnSelf(); + $this->select->expects($this->at(1))->method('where')->with( + 'value = ?', + 1 + )->willReturnSelf(); + $this->connection->expects($this->once())->method('fetchAll') + ->with($this->select) + ->willReturn($results); + $this->assertEquals($this->resource->countImageUses(1), count($results)); + } } diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index 9f43c8a69b5e5..d5b318f671726 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -813,7 +813,6 @@ default="0" comment="Is Disabled"/> <column xsi:type="int" name="record_id" padding="10" unsigned="true" nullable="false" identity="true" comment="Record ID"/> - <column xsi:type="json" name="image_metadata" comment="Image metadata"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="record_id"/> </constraint> diff --git a/app/code/Magento/Catalog/etc/db_schema_whitelist.json b/app/code/Magento/Catalog/etc/db_schema_whitelist.json index a9b5dd2084c35..d4bd6927d4345 100644 --- a/app/code/Magento/Catalog/etc/db_schema_whitelist.json +++ b/app/code/Magento/Catalog/etc/db_schema_whitelist.json @@ -479,8 +479,7 @@ "label": true, "position": true, "disabled": true, - "record_id": true, - "image_metadata": true + "record_id": true }, "index": { "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_STORE_ID": true, diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php index 034f68bdab9d4..ec69baeb92cb9 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProducts.php @@ -314,8 +314,7 @@ protected function prepareVariations() 'canEdit' => 0, 'newProduct' => 0, 'attributes' => $this->getTextAttributes($variationOptions), - 'thumbnail_image' => $this->imageHelper->init($product, 'product_thumbnail_image') - ->getUrl(), + 'thumbnail_image' => $this->imageHelper->init($product, 'product_thumbnail_image')->getUrl(), '__disableTmpl' => true ]; $productIds[] = $product->getId(); diff --git a/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php b/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php index 526774a3e6bcd..d592a004e111a 100644 --- a/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php +++ b/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php @@ -86,7 +86,8 @@ protected function configure() $this->setName('catalog:images:resize') ->setDescription( 'Creates resized product images ' . - '(Deprecated: see https://docs.magento.com/m2/ee/user_guide/configuration/general/web.html#url-options' + '(Not relevant when image resizing is offloaded from Magento. ' . + 'See https://docs.magento.com/m2/ee/user_guide/configuration/general/web.html#url-options )' ) ->setDefinition($this->getOptionsList()); } diff --git a/app/code/Magento/MediaStorage/Service/ImageResize.php b/app/code/Magento/MediaStorage/Service/ImageResize.php index 145fcd1b85f4f..d061ddbd3dc46 100644 --- a/app/code/Magento/MediaStorage/Service/ImageResize.php +++ b/app/code/Magento/MediaStorage/Service/ImageResize.php @@ -19,7 +19,6 @@ use Magento\Framework\Image\Factory as ImageFactory; use Magento\Catalog\Model\Product\Media\ConfigInterface as MediaConfig; use Magento\Framework\App\State; -use Magento\Framework\Storage\StorageProvider; use Magento\Framework\View\ConfigInterface as ViewConfig; use \Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage; use Magento\Store\Model\StoreManagerInterface; @@ -100,11 +99,6 @@ class ImageResize */ private $storeManager; - /** - * @var StorageProvider - */ - private $storageProvider; - /** * @param State $appState * @param MediaConfig $imageConfig @@ -118,7 +112,6 @@ class ImageResize * @param Filesystem $filesystem * @param Database $fileStorageDatabase * @param StoreManagerInterface $storeManager - * @param StorageProvider $storageProvider * @throws \Magento\Framework\Exception\FileSystemException * @internal param ProductImage $gallery * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -135,8 +128,7 @@ public function __construct( Collection $themeCollection, Filesystem $filesystem, Database $fileStorageDatabase = null, - StoreManagerInterface $storeManager = null, - StorageProvider $storageProvider = null + StoreManagerInterface $storeManager = null ) { $this->appState = $appState; $this->imageConfig = $imageConfig; @@ -152,7 +144,6 @@ public function __construct( $this->fileStorageDatabase = $fileStorageDatabase ?: ObjectManager::getInstance()->get(Database::class); $this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class); - $this->storageProvider = $storageProvider ?? ObjectManager::getInstance()->get(StorageProvider::class); } /** @@ -346,15 +337,10 @@ private function resize(array $imageParams, string $originalImagePath, string $o $image->save($imageAsset->getPath()); - $mediastoragefilename = $this->mediaDirectory->getRelativePath($imageAsset->getPath()); if ($this->fileStorageDatabase->checkDbUsage()) { + $mediastoragefilename = $this->mediaDirectory->getRelativePath($imageAsset->getPath()); $this->fileStorageDatabase->saveFile($mediastoragefilename); } - - $this->storageProvider->get('media')->put( - $mediastoragefilename, - $this->mediaDirectory->readFile($mediastoragefilename) - ); } /** diff --git a/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php b/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php index 74913b444e63a..f0e1efa7806e4 100644 --- a/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php +++ b/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php @@ -6,27 +6,25 @@ namespace Magento\MediaStorage\Test\Unit\Service; use Magento\Catalog\Model\Product\Image\ParamsBuilder; -use Magento\Catalog\Model\Product\Media\ConfigInterface as MediaConfig; -use Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage; -use Magento\Catalog\Model\View\Asset\Image as AssetImage; use Magento\Catalog\Model\View\Asset\ImageFactory as AssetImageFactory; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\App\State; -use Magento\Framework\Config\View; +use Magento\Catalog\Model\View\Asset\Image as AssetImage; use Magento\Framework\DataObject; use Magento\Framework\Filesystem; -use Magento\Framework\Image; use Magento\Framework\Image\Factory as ImageFactory; -use Magento\Framework\Storage\StorageInterface; +use Magento\Framework\Image; +use Magento\Catalog\Model\Product\Media\ConfigInterface as MediaConfig; +use Magento\Framework\App\State; use Magento\Framework\View\ConfigInterface as ViewConfig; -use Magento\MediaStorage\Helper\File\Storage\Database; -use Magento\MediaStorage\Service\ImageResize; +use Magento\Framework\Config\View; +use Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage; use Magento\Store\Model\StoreManagerInterface; use Magento\Theme\Model\Config\Customization as ThemeCustomizationConfig; use Magento\Theme\Model\ResourceModel\Theme\Collection; +use Magento\MediaStorage\Helper\File\Storage\Database; +use Magento\Framework\App\Filesystem\DirectoryList; /** - * Class ImageResizeTest test for \Magento\MediaStorage\Service\ImageResize + * Class ImageResizeTest * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -34,7 +32,7 @@ class ImageResizeTest extends \PHPUnit\Framework\TestCase { /** - * @var ImageResize + * @var \Magento\MediaStorage\Service\ImageResize */ protected $service; @@ -122,17 +120,11 @@ class ImageResizeTest extends \PHPUnit\Framework\TestCase * @var string */ private $testfilepath; - /** * @var \PHPUnit\Framework\MockObject\MockObject|StoreManagerInterface */ private $storeManager; - /** - * @var StorageInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $storage; - /** * @inheritDoc * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -157,16 +149,10 @@ protected function setUp() $this->filesystemMock = $this->createMock(Filesystem::class); $this->databaseMock = $this->createMock(Database::class); $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); - $storageProvider = $this->createMock(\Magento\Framework\Storage\StorageProvider::class); - $this->storage = $this->getMockForAbstractClass(StorageInterface::class); - $storageProvider->expects($this->any()) - ->method('get') - ->with('media') - ->willReturn($this->storage); $this->mediaDirectoryMock = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() - ->setMethods(['getAbsolutePath','isFile','getRelativePath', 'readFile']) + ->setMethods(['getAbsolutePath','isFile','getRelativePath']) ->getMock(); $this->filesystemMock->expects($this->any()) @@ -215,7 +201,8 @@ protected function setUp() $this->viewMock->expects($this->any()) ->method('getMediaEntities') ->willReturn( - ['product_small_image' => [ + ['product_small_image' => + [ 'type' => 'small_image', 'width' => 75, 'height' => 75 @@ -236,7 +223,7 @@ protected function setUp() ->method('getStores') ->willReturn([$store]); - $this->service = new ImageResize( + $this->service = new \Magento\MediaStorage\Service\ImageResize( $this->appStateMock, $this->imageConfigMock, $this->productImageMock, @@ -248,8 +235,7 @@ protected function setUp() $this->themeCollectionMock, $this->filesystemMock, $this->databaseMock, - $this->storeManager, - $storageProvider + $this->storeManager ); } @@ -292,14 +278,6 @@ function () { ->method('saveFile') ->with($this->testfilepath); - $this->mediaDirectoryMock->expects($this->any()) - ->method('readFile') - ->with($this->testfilepath) - ->willReturn('image data'); - $this->storage->expects($this->once()) - ->method('put') - ->with($this->testfilepath, 'image data'); - $generator = $this->service->resizeFromThemes(['test-theme']); while ($generator->valid()) { $generator->next(); @@ -338,14 +316,6 @@ public function testResizeFromImageNameMediaStorageDatabase() ->method('saveFile') ->with($this->testfilepath); - $this->mediaDirectoryMock->expects($this->any()) - ->method('readFile') - ->with($this->testfilepath) - ->willReturn('image data'); - $this->storage->expects($this->once()) - ->method('put') - ->with($this->testfilepath, 'image data'); - $this->service->resizeFromImageName($this->testfilename); } } diff --git a/app/code/Magento/MediaStorage/etc/di.xml b/app/code/Magento/MediaStorage/etc/di.xml index 061c3be1bbe4a..5cdcbb3b2b9a9 100644 --- a/app/code/Magento/MediaStorage/etc/di.xml +++ b/app/code/Magento/MediaStorage/etc/di.xml @@ -31,11 +31,4 @@ <argument name="imageResizeScheduler" xsi:type="object">Magento\MediaStorage\Service\ImageResizeScheduler\Proxy</argument> </arguments> </type> - <type name="Magento\Framework\Storage\StorageProvider"> - <arguments> - <argument name="storage" xsi:type="array"> - <item name="media" xsi:type="string">pub/media</item> - </argument> - </arguments> - </type> </config> diff --git a/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Wysiwyg/Files/ContentTest.php b/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Wysiwyg/Files/ContentTest.php new file mode 100644 index 0000000000000..7fe3b25cf97b2 --- /dev/null +++ b/app/code/Magento/Theme/Test/Unit/Block/Adminhtml/Wysiwyg/Files/ContentTest.php @@ -0,0 +1,220 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Theme\Test\Unit\Block\Adminhtml\Wysiwyg\Files; + +use Magento\Theme\Model\Wysiwyg\Storage; + +class ContentTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Backend\Model\Url|PHPUnit_Framework_MockObject_MockObject + */ + protected $_urlBuilder; + + /** + * @var \Magento\Theme\Helper\Storage|PHPUnit_Framework_MockObject_MockObject + */ + protected $_helperStorage; + + /** + * @var \Magento\Theme\Block\Adminhtml\Wysiwyg\Files\Content|PHPUnit_Framework_MockObject_MockObject + */ + protected $_filesContent; + + /** + * @var \Magento\Framework\App\RequestInterface|PHPUnit_Framework_MockObject_MockObject + */ + protected $_request; + + protected function setUp() + { + $this->_helperStorage = $this->createMock(\Magento\Theme\Helper\Storage::class); + $this->_urlBuilder = $this->createMock(\Magento\Backend\Model\Url::class); + $this->_request = $this->createMock(\Magento\Framework\App\RequestInterface::class); + + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $constructArguments = $objectManagerHelper->getConstructArguments( + \Magento\Theme\Block\Adminhtml\Wysiwyg\Files\Content::class, + [ + 'urlBuilder' => $this->_urlBuilder, + 'request' => $this->_request, + 'storageHelper' => $this->_helperStorage + ] + ); + $this->_filesContent = $objectManagerHelper->getObject( + \Magento\Theme\Block\Adminhtml\Wysiwyg\Files\Content::class, + $constructArguments + ); + } + + /** + * @dataProvider requestParamsProvider + * @param array $requestParams + */ + public function testGetNewFolderUrl($requestParams) + { + $expectedUrl = 'some_url'; + + $this->_helperStorage->expects( + $this->once() + )->method( + 'getRequestParams' + )->will( + $this->returnValue($requestParams) + ); + + $this->_urlBuilder->expects( + $this->once() + )->method( + 'getUrl' + )->with( + 'adminhtml/*/newFolder', + $requestParams + )->will( + $this->returnValue($expectedUrl) + ); + + $this->assertEquals($expectedUrl, $this->_filesContent->getNewfolderUrl()); + } + + /** + * @dataProvider requestParamsProvider + * @param array $requestParams + */ + public function testGetDeleteFilesUrl($requestParams) + { + $expectedUrl = 'some_url'; + + $this->_helperStorage->expects( + $this->once() + )->method( + 'getRequestParams' + )->will( + $this->returnValue($requestParams) + ); + + $this->_urlBuilder->expects( + $this->once() + )->method( + 'getUrl' + )->with( + 'adminhtml/*/deleteFiles', + $requestParams + )->will( + $this->returnValue($expectedUrl) + ); + + $this->assertEquals($expectedUrl, $this->_filesContent->getDeleteFilesUrl()); + } + + /** + * @dataProvider requestParamsProvider + * @param array $requestParams + */ + public function testGetOnInsertUrl($requestParams) + { + $expectedUrl = 'some_url'; + + $this->_helperStorage->expects( + $this->once() + )->method( + 'getRequestParams' + )->will( + $this->returnValue($requestParams) + ); + + $this->_urlBuilder->expects( + $this->once() + )->method( + 'getUrl' + )->with( + 'adminhtml/*/onInsert', + $requestParams + )->will( + $this->returnValue($expectedUrl) + ); + + $this->assertEquals($expectedUrl, $this->_filesContent->getOnInsertUrl()); + } + + /** + * Data provider for requestParams + * @return array + */ + public function requestParamsProvider() + { + return [ + [ + 'requestParams' => [ + \Magento\Theme\Helper\Storage::PARAM_THEME_ID => 1, + \Magento\Theme\Helper\Storage::PARAM_CONTENT_TYPE => Storage::TYPE_IMAGE, + \Magento\Theme\Helper\Storage::PARAM_NODE => 'root', + ] + ] + ]; + } + + public function testGetTargetElementId() + { + $expectedRequest = 'some_request'; + + $this->_request->expects( + $this->once() + )->method( + 'getParam' + )->with( + 'target_element_id' + )->will( + $this->returnValue($expectedRequest) + ); + + $this->assertEquals($expectedRequest, $this->_filesContent->getTargetElementId()); + } + + public function testGetContentsUrl() + { + $expectedUrl = 'some_url'; + + $expectedRequest = 'some_request'; + + $requestParams = [ + \Magento\Theme\Helper\Storage::PARAM_THEME_ID => 1, + \Magento\Theme\Helper\Storage::PARAM_CONTENT_TYPE => Storage::TYPE_IMAGE, + \Magento\Theme\Helper\Storage::PARAM_NODE => 'root', + ]; + + $this->_urlBuilder->expects( + $this->once() + )->method( + 'getUrl' + )->with( + 'adminhtml/*/contents', + ['type' => $expectedRequest] + $requestParams + )->will( + $this->returnValue($expectedUrl) + ); + + $this->_request->expects( + $this->once() + )->method( + 'getParam' + )->with( + 'type' + )->will( + $this->returnValue($expectedRequest) + ); + + $this->_helperStorage->expects( + $this->once() + )->method( + 'getRequestParams' + )->will( + $this->returnValue($requestParams) + ); + + $this->assertEquals($expectedUrl, $this->_filesContent->getContentsUrl()); + } +} diff --git a/app/etc/di.xml b/app/etc/di.xml index 85d05b6be38fa..a11b8fd5a2506 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1814,13 +1814,4 @@ <argument name="cache" xsi:type="object">configured_block_cache</argument> </arguments> </type> - <type name="Magento\Framework\Storage\StorageAdapterProvider"> - <arguments> - <argument name="config" xsi:type="array"> - <item name="local" xsi:type="string">Magento\Framework\Storage\AdapterFactory\LocalFactory</item> - <item name="aws_s3" xsi:type="string">Magento\Framework\Storage\AdapterFactory\AwsS3Factory</item> - <item name="ms_azure" xsi:type="string">Magento\Framework\Storage\AdapterFactory\AzureFactory</item> - </argument> - </arguments> - </type> </config> diff --git a/composer.json b/composer.json index ac005f9da6a1e..db34b0a9c2fd0 100644 --- a/composer.json +++ b/composer.json @@ -35,14 +35,11 @@ "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.6", "elasticsearch/elasticsearch": "~2.0||~5.1||~6.1", - "guzzlehttp/guzzle": "^6.3.3", - "league/flysystem": "^1.0", - "league/flysystem-aws-s3-v3": "^1.0", - "league/flysystem-azure-blob-storage": "^0.1.6", "magento/composer": "1.6.x-dev", "magento/magento-composer-installer": ">=0.1.11", "magento/zendframework1": "~1.14.2", "monolog/monolog": "^1.17", + "wikimedia/less.php": "~1.8.0", "paragonie/sodium_compat": "^1.6", "pelago/emogrifier": "^2.0.0", "php-amqplib/php-amqplib": "~2.7.0||~2.10.0", @@ -55,7 +52,6 @@ "tedivm/jshrink": "~1.3.0", "tubalmartin/cssmin": "4.1.1", "webonyx/graphql-php": "^0.13.8", - "wikimedia/less.php": "~1.8.0", "zendframework/zend-captcha": "^2.7.1", "zendframework/zend-code": "~3.3.0", "zendframework/zend-config": "^2.6.0", @@ -83,7 +79,8 @@ "zendframework/zend-text": "^2.6.0", "zendframework/zend-uri": "^2.5.1", "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-view": "~2.11.2" + "zendframework/zend-view": "~2.11.2", + "guzzlehttp/guzzle": "^6.3.3" }, "require-dev": { "allure-framework/allure-phpunit": "~1.2.0", diff --git a/composer.lock b/composer.lock index 1236d2b0ae82f..144614ba2279d 100644 --- a/composer.lock +++ b/composer.lock @@ -1,95 +1,11 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "522d676db5baf5864a824409c54948fc", + "content-hash": "d9bed7b45c83f9133bdec76acac8b796", "packages": [ - { - "name": "aws/aws-sdk-php", - "version": "3.133.24", - "source": { - "type": "git", - "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "726426e1514be5220d55ecf02eb1f938a3b4a105" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/726426e1514be5220d55ecf02eb1f938a3b4a105", - "reference": "726426e1514be5220d55ecf02eb1f938a3b4a105", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4.1", - "mtdowling/jmespath.php": "^2.5", - "php": ">=5.5" - }, - "require-dev": { - "andrewsville/php-token-reflection": "^1.4", - "aws/aws-php-sns-message-validator": "~1.0", - "behat/behat": "~3.0", - "doctrine/cache": "~1.4", - "ext-dom": "*", - "ext-openssl": "*", - "ext-pcntl": "*", - "ext-sockets": "*", - "nette/neon": "^2.3", - "phpunit/phpunit": "^4.8.35|^5.4.3", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", - "sebastian/comparator": "^1.2.3" - }, - "suggest": { - "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", - "doctrine/cache": "To use the DoctrineCacheAdapter", - "ext-curl": "To send requests using cURL", - "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", - "ext-sockets": "To use client-side monitoring" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Aws\\": "src/" - }, - "files": [ - "src/functions.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" - } - ], - "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", - "homepage": "http://aws.amazon.com/sdkforphp", - "keywords": [ - "amazon", - "aws", - "cloud", - "dynamodb", - "ec2", - "glacier", - "s3", - "sdk" - ], - "time": "2020-02-27T19:13:45+00:00" - }, { "name": "braintree/braintree_php", "version": "3.35.0", @@ -341,16 +257,16 @@ }, { "name": "composer/composer", - "version": "1.9.3", + "version": "1.9.2", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "1291a16ce3f48bfdeca39d64fca4875098af4d7b" + "reference": "7a04aa0201ddaa0b3cf64d41022bd8cdcd7fafeb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/1291a16ce3f48bfdeca39d64fca4875098af4d7b", - "reference": "1291a16ce3f48bfdeca39d64fca4875098af4d7b", + "url": "https://api.github.com/repos/composer/composer/zipball/7a04aa0201ddaa0b3cf64d41022bd8cdcd7fafeb", + "reference": "7a04aa0201ddaa0b3cf64d41022bd8cdcd7fafeb", "shasum": "" }, "require": { @@ -417,7 +333,7 @@ "dependency", "package" ], - "time": "2020-02-04T11:58:49+00:00" + "time": "2020-01-14T15:30:32+00:00" }, { "name": "composer/semver", @@ -482,16 +398,16 @@ }, { "name": "composer/spdx-licenses", - "version": "1.5.3", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "0c3e51e1880ca149682332770e25977c70cf9dae" + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/0c3e51e1880ca149682332770e25977c70cf9dae", - "reference": "0c3e51e1880ca149682332770e25977c70cf9dae", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5", + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5", "shasum": "" }, "require": { @@ -538,7 +454,7 @@ "spdx", "validator" ], - "time": "2020-02-14T07:44:31+00:00" + "time": "2019-07-29T10:31:59+00:00" }, { "name": "composer/xdebug-handler", @@ -1034,178 +950,6 @@ ], "time": "2019-09-25T14:49:45+00:00" }, - { - "name": "league/flysystem", - "version": "1.0.64", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "d13c43dbd4b791f815215959105a008515d1a2e0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/d13c43dbd4b791f815215959105a008515d1a2e0", - "reference": "d13c43dbd4b791f815215959105a008515d1a2e0", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "php": ">=5.5.9" - }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" - }, - "require-dev": { - "phpspec/phpspec": "^3.4", - "phpunit/phpunit": "^5.7.26" - }, - "suggest": { - "ext-fileinfo": "Required for MimeType", - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "description": "Filesystem abstraction: Many filesystems, one API.", - "keywords": [ - "Cloud Files", - "WebDAV", - "abstraction", - "aws", - "cloud", - "copy.com", - "dropbox", - "file systems", - "files", - "filesystem", - "filesystems", - "ftp", - "rackspace", - "remote", - "s3", - "sftp", - "storage" - ], - "time": "2020-02-05T18:14:17+00:00" - }, - { - "name": "league/flysystem-aws-s3-v3", - "version": "1.0.24", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4382036bde5dc926f9b8b337e5bdb15e5ec7b570", - "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570", - "shasum": "" - }, - "require": { - "aws/aws-sdk-php": "^3.0.0", - "league/flysystem": "^1.0.40", - "php": ">=5.5.0" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "^2.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\AwsS3v3\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "description": "Flysystem adapter for the AWS S3 SDK v3.x", - "time": "2020-02-23T13:31:58+00:00" - }, - { - "name": "league/flysystem-azure-blob-storage", - "version": "0.1.6", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem-azure-blob-storage.git", - "reference": "97215345f3c42679299ba556a4d16d4847ee7f6d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-azure-blob-storage/zipball/97215345f3c42679299ba556a4d16d4847ee7f6d", - "reference": "97215345f3c42679299ba556a4d16d4847ee7f6d", - "shasum": "" - }, - "require": { - "guzzlehttp/psr7": "^1.5", - "league/flysystem": "^1.0", - "microsoft/azure-storage-blob": "^1.1", - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "^5.7" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\Flysystem\\AzureBlobStorage\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "time": "2019-06-07T20:42:16+00:00" - }, { "name": "magento/composer", "version": "1.6.x-dev", @@ -1368,94 +1112,6 @@ ], "time": "2019-11-26T15:09:40+00:00" }, - { - "name": "microsoft/azure-storage-blob", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/Azure/azure-storage-blob-php.git", - "reference": "6a333cd28a3742c3e99e79042dc6510f9f917919" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Azure/azure-storage-blob-php/zipball/6a333cd28a3742c3e99e79042dc6510f9f917919", - "reference": "6a333cd28a3742c3e99e79042dc6510f9f917919", - "shasum": "" - }, - "require": { - "microsoft/azure-storage-common": "~1.4", - "php": ">=5.6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "MicrosoftAzure\\Storage\\Blob\\": "src/Blob" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Azure Storage PHP Client Library", - "email": "dmsh@microsoft.com" - } - ], - "description": "This project provides a set of PHP client libraries that make it easy to access Microsoft Azure Storage Blob APIs.", - "keywords": [ - "azure", - "blob", - "php", - "sdk", - "storage" - ], - "time": "2020-01-02T07:18:59+00:00" - }, - { - "name": "microsoft/azure-storage-common", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/Azure/azure-storage-common-php.git", - "reference": "be4df800761d0d0fa91a9460c7f42517197d57a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Azure/azure-storage-common-php/zipball/be4df800761d0d0fa91a9460c7f42517197d57a0", - "reference": "be4df800761d0d0fa91a9460c7f42517197d57a0", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "~6.0", - "php": ">=5.6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "MicrosoftAzure\\Storage\\Common\\": "src/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Azure Storage PHP Client Library", - "email": "dmsh@microsoft.com" - } - ], - "description": "This project provides a set of common code shared by Azure Storage Blob, Table, Queue and File PHP client libraries.", - "keywords": [ - "azure", - "common", - "php", - "sdk", - "storage" - ], - "time": "2020-01-02T07:15:54+00:00" - }, { "name": "monolog/monolog", "version": "1.25.3", @@ -1534,63 +1190,6 @@ ], "time": "2019-12-20T14:15:16+00:00" }, - { - "name": "mtdowling/jmespath.php", - "version": "2.5.0", - "source": { - "type": "git", - "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "52168cb9472de06979613d365c7f1ab8798be895" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", - "reference": "52168cb9472de06979613d365c7f1ab8798be895", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "symfony/polyfill-mbstring": "^1.4" - }, - "require-dev": { - "composer/xdebug-handler": "^1.2", - "phpunit/phpunit": "^4.8.36|^7.5.15" - }, - "bin": [ - "bin/jp.php" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.5-dev" - } - }, - "autoload": { - "psr-4": { - "JmesPath\\": "src/" - }, - "files": [ - "src/JmesPath.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Declaratively specify how to extract elements from a JSON document", - "keywords": [ - "json", - "jsonpath" - ], - "time": "2019-12-30T18:03:34+00:00" - }, { "name": "paragonie/random_compat", "version": "v9.99.99", @@ -1916,16 +1515,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.25", + "version": "2.0.23", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "c18159618ed7cd7ff721ac1a8fec7860a475d2f0" + "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c18159618ed7cd7ff721ac1a8fec7860a475d2f0", - "reference": "c18159618ed7cd7ff721ac1a8fec7860a475d2f0", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c78eb5058d5bb1a183133c36d4ba5b6675dfa099", + "reference": "c78eb5058d5bb1a183133c36d4ba5b6675dfa099", "shasum": "" }, "require": { @@ -2004,7 +1603,7 @@ "x.509", "x509" ], - "time": "2020-02-25T04:16:50+00:00" + "time": "2019-09-17T03:41:22+00:00" }, { "name": "psr/container", @@ -2371,16 +1970,16 @@ }, { "name": "seld/phar-utils", - "version": "1.1.0", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0" + "reference": "84715761c35808076b00908a20317a3a8a67d17e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8800503d56b9867d43d9c303b9cbcc26016e82f0", - "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/84715761c35808076b00908a20317a3a8a67d17e", + "reference": "84715761c35808076b00908a20317a3a8a67d17e", "shasum": "" }, "require": { @@ -2409,22 +2008,22 @@ ], "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ - "phar" + "phra" ], - "time": "2020-02-14T15:25:33+00:00" + "time": "2020-01-13T10:41:09+00:00" }, { "name": "symfony/console", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f512001679f37e6a042b51897ed24a2f05eba656" + "reference": "e9ee09d087e2c88eaf6e5fc0f5c574f64d100e4f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f512001679f37e6a042b51897ed24a2f05eba656", - "reference": "f512001679f37e6a042b51897ed24a2f05eba656", + "url": "https://api.github.com/repos/symfony/console/zipball/e9ee09d087e2c88eaf6e5fc0f5c574f64d100e4f", + "reference": "e9ee09d087e2c88eaf6e5fc0f5c574f64d100e4f", "shasum": "" }, "require": { @@ -2487,11 +2086,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2020-01-25T12:44:29+00:00" + "time": "2020-01-10T21:54:01+00:00" }, { "name": "symfony/css-selector", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -2544,7 +2143,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -2672,7 +2271,7 @@ }, { "name": "symfony/filesystem", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -2722,7 +2321,7 @@ }, { "name": "symfony/finder", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -2771,16 +2370,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.14.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", - "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", + "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3", "shasum": "" }, "require": { @@ -2792,7 +2391,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -2825,20 +2424,20 @@ "polyfill", "portable" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.14.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2" + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2", - "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7b4aab9743c30be783b73de055d24a39cf4b954f", + "reference": "7b4aab9743c30be783b73de055d24a39cf4b954f", "shasum": "" }, "require": { @@ -2850,7 +2449,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -2884,20 +2483,20 @@ "portable", "shim" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2019-11-27T14:18:11+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.14.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675" + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/5e66a0fa1070bf46bec4bea7962d285108edd675", - "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/4b0e2222c55a25b4541305a053013d5647d3a25f", + "reference": "4b0e2222c55a25b4541305a053013d5647d3a25f", "shasum": "" }, "require": { @@ -2906,7 +2505,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -2942,11 +2541,11 @@ "portable", "shim" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2019-11-27T16:25:15+00:00" }, { "name": "symfony/process", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -5499,16 +5098,16 @@ }, { "name": "allure-framework/allure-php-api", - "version": "1.1.7", + "version": "1.1.6", "source": { "type": "git", "url": "https://github.com/allure-framework/allure-php-commons.git", - "reference": "243c9a2259b60c1b7a9d298d4fb3fc72b4f64c18" + "reference": "2c62a70d60eca190253a6b80e9aa4c2ebc2e6e7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-php-commons/zipball/243c9a2259b60c1b7a9d298d4fb3fc72b4f64c18", - "reference": "243c9a2259b60c1b7a9d298d4fb3fc72b4f64c18", + "url": "https://api.github.com/repos/allure-framework/allure-php-commons/zipball/2c62a70d60eca190253a6b80e9aa4c2ebc2e6e7f", + "reference": "2c62a70d60eca190253a6b80e9aa4c2ebc2e6e7f", "shasum": "" }, "require": { @@ -5548,7 +5147,7 @@ "php", "report" ], - "time": "2020-02-05T16:43:19+00:00" + "time": "2020-01-09T10:26:09+00:00" }, { "name": "allure-framework/allure-phpunit", @@ -5600,18 +5199,102 @@ ], "time": "2017-11-03T13:08:21+00:00" }, + { + "name": "aws/aws-sdk-php", + "version": "3.133.8", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "c564fcccd5fc7b5e8514d1cbe35558be1e3a11cd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c564fcccd5fc7b5e8514d1cbe35558be1e3a11cd", + "reference": "c564fcccd5fc7b5e8514d1cbe35558be1e3a11cd", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4.1", + "mtdowling/jmespath.php": "^2.5", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2020-02-05T19:12:47+00:00" + }, { "name": "behat/gherkin", - "version": "v4.6.1", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "25bdcaf37898b4a939fa3031d5d753ced97e4759" + "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/25bdcaf37898b4a939fa3031d5d753ced97e4759", - "reference": "25bdcaf37898b4a939fa3031d5d753ced97e4759", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/ab0a02ea14893860bca00f225f5621d351a3ad07", + "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07", "shasum": "" }, "require": { @@ -5657,7 +5340,7 @@ "gherkin", "parser" ], - "time": "2020-02-27T11:29:57+00:00" + "time": "2019-01-16T14:22:17+00:00" }, { "name": "cache/cache", @@ -5921,31 +5604,31 @@ }, { "name": "consolidation/annotated-command", - "version": "4.1.0", + "version": "2.12.0", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "33e472d3cceb0f22a527d13ccfa3f76c4d21c178" + "reference": "512a2e54c98f3af377589de76c43b24652bcb789" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/33e472d3cceb0f22a527d13ccfa3f76c4d21c178", - "reference": "33e472d3cceb0f22a527d13ccfa3f76c4d21c178", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/512a2e54c98f3af377589de76c43b24652bcb789", + "reference": "512a2e54c98f3af377589de76c43b24652bcb789", "shasum": "" }, "require": { - "consolidation/output-formatters": "^4.1", - "php": ">=7.1.3", - "psr/log": "^1|^2", - "symfony/console": "^4|^5", - "symfony/event-dispatcher": "^4|^5", - "symfony/finder": "^4|^5" + "consolidation/output-formatters": "^3.4", + "php": ">=5.4.5", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4" }, "require-dev": { "g1a/composer-test-scenarios": "^3", "php-coveralls/php-coveralls": "^1", "phpunit/phpunit": "^6", - "squizlabs/php_codesniffer": "^3" + "squizlabs/php_codesniffer": "^2.7" }, "type": "library", "extra": { @@ -5953,11 +5636,48 @@ "symfony4": { "require": { "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + }, + "phpunit4": { + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } } } }, "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -5976,7 +5696,7 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2020-02-07T03:35:30+00:00" + "time": "2019-03-08T16:55:03+00:00" }, { "name": "consolidation/config", @@ -6061,33 +5781,74 @@ }, { "name": "consolidation/log", - "version": "2.0.0", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/consolidation/log.git", - "reference": "446f804476db4f73957fa4bcb66ab2facf5397ff" + "reference": "b2e887325ee90abc96b0a8b7b474cd9e7c896e3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/log/zipball/446f804476db4f73957fa4bcb66ab2facf5397ff", - "reference": "446f804476db4f73957fa4bcb66ab2facf5397ff", + "url": "https://api.github.com/repos/consolidation/log/zipball/b2e887325ee90abc96b0a8b7b474cd9e7c896e3a", + "reference": "b2e887325ee90abc96b0a8b7b474cd9e7c896e3a", "shasum": "" }, "require": { "php": ">=5.4.5", "psr/log": "^1.0", - "symfony/console": "^4|^5" + "symfony/console": "^2.8|^3|^4" }, "require-dev": { "g1a/composer-test-scenarios": "^3", "php-coveralls/php-coveralls": "^1", "phpunit/phpunit": "^6", - "squizlabs/php_codesniffer": "^3" + "squizlabs/php_codesniffer": "^2" }, "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + }, + "phpunit4": { + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + } + }, "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "1.x-dev" } }, "autoload": { @@ -6106,35 +5867,34 @@ } ], "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", - "time": "2020-02-07T01:22:27+00:00" + "time": "2019-01-01T17:30:51+00:00" }, { "name": "consolidation/output-formatters", - "version": "4.1.0", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/consolidation/output-formatters.git", - "reference": "eae721c3a916707c40d4390efbf48d4c799709cc" + "reference": "99ec998ffb697e0eada5aacf81feebfb13023605" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/eae721c3a916707c40d4390efbf48d4c799709cc", - "reference": "eae721c3a916707c40d4390efbf48d4c799709cc", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/99ec998ffb697e0eada5aacf81feebfb13023605", + "reference": "99ec998ffb697e0eada5aacf81feebfb13023605", "shasum": "" }, "require": { "dflydev/dot-access-data": "^1.1.0", - "php": ">=7.1.3", - "symfony/console": "^4|^5", - "symfony/finder": "^4|^5" + "php": ">=5.4.0", + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4" }, "require-dev": { "g1a/composer-test-scenarios": "^3", "php-coveralls/php-coveralls": "^1", - "phpunit/phpunit": "^6", - "squizlabs/php_codesniffer": "^3", - "symfony/var-dumper": "^4", - "symfony/yaml": "^4", + "phpunit/phpunit": "^5.7.27", + "squizlabs/php_codesniffer": "^2.7", + "symfony/var-dumper": "^2.8|^3|^4", "victorjonsson/markdowndocs": "^1.3" }, "suggest": { @@ -6146,11 +5906,50 @@ "symfony4": { "require": { "symfony/console": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^6" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony3": { + "require": { + "symfony/console": "^3.4", + "symfony/finder": "^3.4", + "symfony/var-dumper": "^3.4" + }, + "config": { + "platform": { + "php": "5.6.32" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" } } }, "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { @@ -6169,30 +5968,30 @@ } ], "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2020-02-07T03:22:30+00:00" + "time": "2019-05-30T23:16:01+00:00" }, { "name": "consolidation/robo", - "version": "1.4.12", + "version": "1.4.11", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "eb45606f498b3426b9a98b7c85e300666a968e51" + "reference": "5fa1d901776a628167a325baa9db95d8edf13a80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/eb45606f498b3426b9a98b7c85e300666a968e51", - "reference": "eb45606f498b3426b9a98b7c85e300666a968e51", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/5fa1d901776a628167a325baa9db95d8edf13a80", + "reference": "5fa1d901776a628167a325baa9db95d8edf13a80", "shasum": "" }, "require": { - "consolidation/annotated-command": "^2.11.0|^4.1", - "consolidation/config": "^1.2.1", - "consolidation/log": "^1.1.1|^2", - "consolidation/output-formatters": "^3.1.13|^4.1", - "consolidation/self-update": "^1.1.5", - "grasmash/yaml-expander": "^1.4", - "league/container": "^2.4.1", + "consolidation/annotated-command": "^2.11.0", + "consolidation/config": "^1.2", + "consolidation/log": "~1", + "consolidation/output-formatters": "^3.1.13", + "consolidation/self-update": "^1", + "grasmash/yaml-expander": "^1.3", + "league/container": "^2.2", "php": ">=5.5.0", "symfony/console": "^2.8|^3|^4", "symfony/event-dispatcher": "^2.5|^3|^4", @@ -6204,13 +6003,20 @@ "codegyre/robo": "< 1.0" }, "require-dev": { + "codeception/aspect-mock": "^1|^2.1.1", + "codeception/base": "^2.3.7", + "codeception/verify": "^0.3.2", "g1a/composer-test-scenarios": "^3", + "goaop/framework": "~2.1.2", + "goaop/parser-reflection": "^1.1.0", "natxet/cssmin": "3.0.4", - "patchwork/jsqueeze": "^2", + "nikic/php-parser": "^3.1.5", + "patchwork/jsqueeze": "~2", "pear/archive_tar": "^1.4.4", "php-coveralls/php-coveralls": "^1", - "phpunit/phpunit": "^5.7.27", - "squizlabs/php_codesniffer": "^3" + "phpunit/php-code-coverage": "~2|~4", + "sebastian/comparator": "^1.2.4", + "squizlabs/php_codesniffer": "^2.8" }, "suggest": { "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch", @@ -6238,11 +6044,8 @@ "require": { "symfony/console": "^2.8" }, - "require-dev": { - "phpunit/phpunit": "^4.8.36" - }, "remove": [ - "php-coveralls/php-coveralls" + "goaop/framework" ], "config": { "platform": { @@ -6255,7 +6058,7 @@ } }, "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -6274,7 +6077,7 @@ } ], "description": "Modern task runner", - "time": "2020-02-18T17:31:26+00:00" + "time": "2019-10-29T15:50:02+00:00" }, { "name": "consolidation/self-update", @@ -7239,16 +7042,16 @@ }, { "name": "jms/serializer", - "version": "1.14.1", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "ba908d278fff27ec01fb4349f372634ffcd697c0" + "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/ba908d278fff27ec01fb4349f372634ffcd697c0", - "reference": "ba908d278fff27ec01fb4349f372634ffcd697c0", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", + "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", "shasum": "" }, "require": { @@ -7301,13 +7104,13 @@ "MIT" ], "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - }, { "name": "Asmir Mustafic", "email": "goetas@gmail.com" + }, + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" } ], "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", @@ -7319,7 +7122,7 @@ "serialization", "xml" ], - "time": "2020-02-22T20:59:37+00:00" + "time": "2019-04-17T08:12:16+00:00" }, { "name": "league/container", @@ -7386,6 +7189,90 @@ ], "time": "2017-05-10T09:20:27+00:00" }, + { + "name": "league/flysystem", + "version": "1.0.63", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "8132daec326565036bc8e8d1876f77ec183a7bd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8132daec326565036bc8e8d1876f77ec183a7bd6", + "reference": "8132daec326565036bc8e8d1876f77ec183a7bd6", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.10" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2020-01-04T16:30:31+00:00" + }, { "name": "lusitanian/oauth", "version": "v0.8.11", @@ -7623,6 +7510,63 @@ "homepage": "http://vfs.bovigo.org/", "time": "2019-10-30T15:31:00+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "52168cb9472de06979613d365c7f1ab8798be895" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", + "reference": "52168cb9472de06979613d365c7f1ab8798be895", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "^1.4" + }, + "require-dev": { + "composer/xdebug-handler": "^1.2", + "phpunit/phpunit": "^4.8.36|^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2019-12-30T18:03:34+00:00" + }, { "name": "mustache/mustache", "version": "v2.13.0", @@ -7912,16 +7856,16 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.8.1", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "262ea0d209c292e0330be1041424887bbbffef04" + "reference": "3e33ee3b8a688d719c55acdd7c6788e3006e1d3e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/262ea0d209c292e0330be1041424887bbbffef04", - "reference": "262ea0d209c292e0330be1041424887bbbffef04", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3e33ee3b8a688d719c55acdd7c6788e3006e1d3e", + "reference": "3e33ee3b8a688d719c55acdd7c6788e3006e1d3e", "shasum": "" }, "require": { @@ -7953,12 +7897,12 @@ } }, "autoload": { - "psr-4": { - "Facebook\\WebDriver\\": "lib/" - }, "files": [ "lib/Exception/TimeoutException.php" - ] + ], + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -7973,7 +7917,7 @@ "selenium", "webdriver" ], - "time": "2020-02-17T08:14:38+00:00" + "time": "2020-02-10T15:04:25+00:00" }, { "name": "phpcollection/phpcollection", @@ -8135,38 +8079,41 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.1.0", + "version": "4.3.4", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", - "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", + "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", "shasum": "" }, "require": { - "ext-filter": "^7.1", - "php": "^7.2", - "phpdocumentor/reflection-common": "^2.0", - "phpdocumentor/type-resolver": "^1.0", - "webmozart/assert": "^1" + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", + "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", + "webmozart/assert": "^1.0" }, "require-dev": { - "doctrine/instantiator": "^1", - "mockery/mockery": "^1" + "doctrine/instantiator": "^1.0.5", + "mockery/mockery": "^1.0", + "phpdocumentor/type-resolver": "0.4.*", + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.x-dev" + "dev-master": "4.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": "src" + "phpDocumentor\\Reflection\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -8177,14 +8124,10 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2020-02-22T12:28:44+00:00" + "time": "2019-12-28T18:55:12+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -8421,16 +8364,16 @@ }, { "name": "phpstan/phpstan", - "version": "0.12.11", + "version": "0.12.7", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ca5f2b7cf81c6d8fba74f9576970399c5817e03b" + "reference": "07fa7958027fd98c567099bbcda5d6a0f2ec5197" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ca5f2b7cf81c6d8fba74f9576970399c5817e03b", - "reference": "ca5f2b7cf81c6d8fba74f9576970399c5817e03b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/07fa7958027fd98c567099bbcda5d6a0f2ec5197", + "reference": "07fa7958027fd98c567099bbcda5d6a0f2ec5197", "shasum": "" }, "require": { @@ -8456,7 +8399,7 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2020-02-16T14:00:29+00:00" + "time": "2020-01-20T21:59:06+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9650,7 +9593,7 @@ }, { "name": "symfony/browser-kit", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", @@ -9709,7 +9652,7 @@ }, { "name": "symfony/config", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", @@ -9773,16 +9716,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "ec60a7d12f5e8ab0f99456adce724717d9c1784a" + "reference": "6faf589e1f6af78692aed3ab6b3c336c58d5d83c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ec60a7d12f5e8ab0f99456adce724717d9c1784a", - "reference": "ec60a7d12f5e8ab0f99456adce724717d9c1784a", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/6faf589e1f6af78692aed3ab6b3c336c58d5d83c", + "reference": "6faf589e1f6af78692aed3ab6b3c336c58d5d83c", "shasum": "" }, "require": { @@ -9842,11 +9785,11 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2020-01-31T09:49:27+00:00" + "time": "2020-01-21T07:39:36+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", @@ -9907,16 +9850,16 @@ }, { "name": "symfony/http-foundation", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "491a20dfa87e0b3990170593bc2de0bb34d828a5" + "reference": "c33998709f3fe9b8e27e0277535b07fbf6fde37a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/491a20dfa87e0b3990170593bc2de0bb34d828a5", - "reference": "491a20dfa87e0b3990170593bc2de0bb34d828a5", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c33998709f3fe9b8e27e0277535b07fbf6fde37a", + "reference": "c33998709f3fe9b8e27e0277535b07fbf6fde37a", "shasum": "" }, "require": { @@ -9958,11 +9901,11 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2020-01-31T09:11:17+00:00" + "time": "2020-01-04T13:00:46+00:00" }, { "name": "symfony/mime", - "version": "v5.0.4", + "version": "v5.0.3", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", @@ -10024,7 +9967,7 @@ }, { "name": "symfony/options-resolver", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", @@ -10078,22 +10021,22 @@ }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.14.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "6842f1a39cf7d580655688069a03dd7cd83d244a" + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6842f1a39cf7d580655688069a03dd7cd83d244a", - "reference": "6842f1a39cf7d580655688069a03dd7cd83d244a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6f9c239e61e1b0c9229a28ff89a812dc449c3d46", + "reference": "6f9c239e61e1b0c9229a28ff89a812dc449c3d46", "shasum": "" }, "require": { "php": ">=5.3.3", "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.10" + "symfony/polyfill-php72": "^1.9" }, "suggest": { "ext-intl": "For best performance" @@ -10101,7 +10044,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -10136,20 +10079,20 @@ "portable", "shim" ], - "time": "2020-01-17T12:01:36+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.14.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "419c4940024c30ccc033650373a1fe13890d3255" + "reference": "af23c7bb26a73b850840823662dda371484926c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/419c4940024c30ccc033650373a1fe13890d3255", - "reference": "419c4940024c30ccc033650373a1fe13890d3255", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/af23c7bb26a73b850840823662dda371484926c4", + "reference": "af23c7bb26a73b850840823662dda371484926c4", "shasum": "" }, "require": { @@ -10159,7 +10102,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -10195,20 +10138,20 @@ "portable", "shim" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.14.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf" + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf", - "reference": "46ecacf4751dd0dc81e4f6bf01dbf9da1dc1dadf", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/66fea50f6cb37a35eea048d75a7d99a45b586038", + "reference": "66fea50f6cb37a35eea048d75a7d99a45b586038", "shasum": "" }, "require": { @@ -10217,7 +10160,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "1.13-dev" } }, "autoload": { @@ -10250,11 +10193,11 @@ "portable", "shim" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2019-11-27T13:56:44+00:00" }, { "name": "symfony/stopwatch", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -10304,7 +10247,7 @@ }, { "name": "symfony/yaml", - "version": "v4.4.4", + "version": "v4.4.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", @@ -10494,16 +10437,16 @@ }, { "name": "webmozart/assert", - "version": "1.7.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925", + "reference": "573381c0a64f155a0d9a23f4b0c797194805b925", "shasum": "" }, "require": { @@ -10538,7 +10481,7 @@ "check", "validate" ], - "time": "2020-02-14T12:15:55+00:00" + "time": "2019-11-24T13:36:37+00:00" }, { "name": "weew/helpers-array", diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ImageUploaderTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ImageUploaderTest.php new file mode 100644 index 0000000000000..569cf2357675c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ImageUploaderTest.php @@ -0,0 +1,165 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model; + +use Magento\Framework\App\Filesystem\DirectoryList; + +/** + * Tests for the \Magento\Catalog\Model\ImageUploader class + */ +class ImageUploaderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var \Magento\Catalog\Model\ImageUploader + */ + private $imageUploader; + + /** + * @var \Magento\Framework\Filesystem + */ + private $filesystem; + + /** + * @var \Magento\Framework\Filesystem\Directory\WriteInterface + */ + private $mediaDirectory; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var \Magento\Framework\Filesystem $filesystem */ + $this->filesystem = $this->objectManager->get(\Magento\Framework\Filesystem::class); + $this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); + /** @var $uploader \Magento\MediaStorage\Model\File\Uploader */ + $this->imageUploader = $this->objectManager->create( + \Magento\Catalog\Model\ImageUploader::class, + [ + 'baseTmpPath' => 'catalog/tmp/category', + 'basePath' => 'catalog/category', + 'allowedExtensions' => ['jpg', 'jpeg', 'gif', 'png'], + 'allowedMimeTypes' => ['image/jpg', 'image/jpeg', 'image/gif', 'image/png'] + ] + ); + } + + /** + * @return void + */ + public function testSaveFileToTmpDir(): void + { + $fileName = 'magento_small_image.jpg'; + $tmpDirectory = $this->filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::SYS_TMP); + $fixtureDir = realpath(__DIR__ . '/../_files'); + $filePath = $tmpDirectory->getAbsolutePath($fileName); + copy($fixtureDir . DIRECTORY_SEPARATOR . $fileName, $filePath); + + $_FILES['image'] = [ + 'name' => $fileName, + 'type' => 'image/jpeg', + 'tmp_name' => $filePath, + 'error' => 0, + 'size' => 12500, + ]; + + $this->imageUploader->saveFileToTmpDir('image'); + $filePath = $this->imageUploader->getBaseTmpPath() . DIRECTORY_SEPARATOR. $fileName; + $this->assertTrue(is_file($this->mediaDirectory->getAbsolutePath($filePath))); + } + + /** + * Test that method rename files when move it with the same name into base directory. + * + * @return void + * @magentoDataFixture Magento/Catalog/_files/catalog_category_image.php + * @magentoDataFixture Magento/Catalog/_files/catalog_tmp_category_image.php + */ + public function testMoveFileFromTmp(): void + { + $expectedFilePath = $this->imageUploader->getBasePath() . DIRECTORY_SEPARATOR . 'magento_small_image_1.jpg'; + + $this->assertFileNotExists($this->mediaDirectory->getAbsolutePath($expectedFilePath)); + + $this->imageUploader->moveFileFromTmp('magento_small_image.jpg'); + + $this->assertFileExists($this->mediaDirectory->getAbsolutePath($expectedFilePath)); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage File validation failed. + * @return void + */ + public function testSaveFileToTmpDirWithWrongExtension(): void + { + $fileName = 'text.txt'; + $tmpDirectory = $this->filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::SYS_TMP); + $filePath = $tmpDirectory->getAbsolutePath($fileName); + $file = fopen($filePath, "wb"); + fwrite($file, 'just a text'); + + $_FILES['image'] = [ + 'name' => $fileName, + 'type' => 'text/plain', + 'tmp_name' => $filePath, + 'error' => 0, + 'size' => 12500, + ]; + + $this->imageUploader->saveFileToTmpDir('image'); + $filePath = $this->imageUploader->getBaseTmpPath() . DIRECTORY_SEPARATOR. $fileName; + $this->assertFalse(is_file($this->mediaDirectory->getAbsolutePath($filePath))); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage File validation failed. + * @return void + */ + public function testSaveFileToTmpDirWithWrongFile(): void + { + $fileName = 'file.gif'; + $tmpDirectory = $this->filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::SYS_TMP); + $filePath = $tmpDirectory->getAbsolutePath($fileName); + $file = fopen($filePath, "wb"); + fwrite($file, 'just a text'); + + $_FILES['image'] = [ + 'name' => $fileName, + 'type' => 'image/gif', + 'tmp_name' => $filePath, + 'error' => 0, + 'size' => 12500, + ]; + + $this->imageUploader->saveFileToTmpDir('image'); + $filePath = $this->imageUploader->getBaseTmpPath() . DIRECTORY_SEPARATOR. $fileName; + $this->assertFalse(is_file($this->mediaDirectory->getAbsolutePath($filePath))); + } + + /** + * @inheritdoc + */ + public static function tearDownAfterClass() + { + parent::tearDownAfterClass(); + $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\Framework\Filesystem::class + ); + /** @var \Magento\Framework\Filesystem\Directory\WriteInterface $mediaDirectory */ + $mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); + $mediaDirectory->delete('tmp'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/ReadHandlerTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/ReadHandlerTest.php index f348372f2029a..89b91ab57e51a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/ReadHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Gallery/ReadHandlerTest.php @@ -338,7 +338,11 @@ private function getProductInstance(?int $storeId = null): ProductInterface { /** @var ProductInterface $product */ $product = $this->productFactory->create(); - $product->setId($this->getProduct()->getId()); + $product->setData( + $this->productLinkField, + $this->getProduct()->getData($this->productLinkField) + ); + if ($storeId) { $product->setStoreId($storeId); } diff --git a/dev/tests/integration/testsuite/Magento/Csp/Model/Policy/Renderer/SimplePolicyHeaderRendererTest.php b/dev/tests/integration/testsuite/Magento/Csp/Model/Policy/Renderer/SimplePolicyHeaderRendererTest.php index 12ed71b708b88..93e7833038a42 100644 --- a/dev/tests/integration/testsuite/Magento/Csp/Model/Policy/Renderer/SimplePolicyHeaderRendererTest.php +++ b/dev/tests/integration/testsuite/Magento/Csp/Model/Policy/Renderer/SimplePolicyHeaderRendererTest.php @@ -58,8 +58,6 @@ public function testRenderRestrictMode(): void foreach ($header as $item) { $contentSecurityPolicyContent[] = $item->getFieldValue(); } - } else { - $contentSecurityPolicyContent = [$header->getFieldValue()]; } $this->assertEquals(['default-src https://magento.com \'self\';'], $contentSecurityPolicyContent); } @@ -86,8 +84,6 @@ public function testRenderRestrictWithReportingMode(): void foreach ($header as $item) { $contentSecurityPolicyContent[] = $item->getFieldValue(); } - } else { - $contentSecurityPolicyContent = [$header->getFieldValue()]; } $this->assertEquals( ['default-src https://magento.com \'self\'; report-uri /csp-reports/; report-to report-endpoint;'], diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/blacklist/common.txt index 6a7c814f50524..f54defbd57604 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpstan/blacklist/common.txt @@ -14,5 +14,3 @@ dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressRepositoryTest.ph dev/tests/api-functional/testsuite/Magento/Framework/Model/Entity/HydratorTest.php dev/tests/api-functional/testsuite/Magento/Integration/Model/AdminTokenServiceTest.php dev/tests/api-functional/testsuite/Magento/Integration/Model/CustomerTokenServiceTest.php -lib/internal/Magento/Framework/Storage/AdapterFactory/AwsS3Factory.php -lib/internal/Magento/Framework/Storage/AdapterFactory/AzureFactory.php diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php index 2adff8162e5a3..dc3e63fcc7df8 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php @@ -19,11 +19,6 @@ class Response extends \Zend\Http\PhpEnvironment\Response implements \Magento\Fr */ protected $isRedirect = false; - /** - * @var bool - */ - private $headersSent; - /** * @inheritdoc */ @@ -33,10 +28,6 @@ public function getHeader($name) $headers = $this->getHeaders(); if ($headers->has($name)) { $header = $headers->get($name); - // zend-http >= 2.10.11 can return \ArrayIterator instead of a single Header - if ($header instanceof \ArrayIterator) { - $header = $header->current(); - } } return $header; } diff --git a/lib/internal/Magento/Framework/Storage/AdapterFactory/AdapterFactoryInterface.php b/lib/internal/Magento/Framework/Storage/AdapterFactory/AdapterFactoryInterface.php deleted file mode 100644 index 56794bbd29fcf..0000000000000 --- a/lib/internal/Magento/Framework/Storage/AdapterFactory/AdapterFactoryInterface.php +++ /dev/null @@ -1,25 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage\AdapterFactory; - -use League\Flysystem\AdapterInterface; - -/** - * Storage adapter factory - * - * A storage adapter should have a factory implementing this interface in order to be supported by Magento. - * A new factory should be registered in \Magento\Framework\Storage\StorageProvider::$storageAdapters via di.xml. - */ -interface AdapterFactoryInterface -{ - /** - * Create instance of a storage adapter - * - * @param array $options - * @return AdapterInterface - */ - public function create(array $options): AdapterInterface; -} diff --git a/lib/internal/Magento/Framework/Storage/AdapterFactory/AwsS3Factory.php b/lib/internal/Magento/Framework/Storage/AdapterFactory/AwsS3Factory.php deleted file mode 100644 index 719c85b6f7f9d..0000000000000 --- a/lib/internal/Magento/Framework/Storage/AdapterFactory/AwsS3Factory.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage\AdapterFactory; - -use Aws\S3\S3Client; -use League\Flysystem\AdapterInterface; -use League\Flysystem\AwsS3v3\AwsS3Adapter; -use Magento\Framework\Storage\InvalidStorageConfigurationException; - -/** - * Factory for AWS S3 storage adapter - */ -class AwsS3Factory implements AdapterFactoryInterface -{ - /** - * @inheritdoc - */ - public function create(array $options): AdapterInterface - { - if (empty($options['client']) || empty($options['bucket'])) { - throw new InvalidStorageConfigurationException( - "Can't create AWS S3 adapter: required 'client' and/or 'bucket' options are absent" - ); - } - $client = new S3Client($options['client']); - return new AwsS3Adapter($client, $options['bucket'], $options['prefix'] ?? ''); - } -} diff --git a/lib/internal/Magento/Framework/Storage/AdapterFactory/AzureFactory.php b/lib/internal/Magento/Framework/Storage/AdapterFactory/AzureFactory.php deleted file mode 100644 index 1d548151cb95a..0000000000000 --- a/lib/internal/Magento/Framework/Storage/AdapterFactory/AzureFactory.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage\AdapterFactory; - -use League\Flysystem\AdapterInterface; -use League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter; -use Magento\Framework\Storage\InvalidStorageConfigurationException; -use MicrosoftAzure\Storage\Blob\BlobRestProxy; - -/** - * Factory for Azure storage adapter - */ -class AzureFactory implements AdapterFactoryInterface -{ - /** - * @inheritdoc - */ - public function create(array $options): AdapterInterface - { - if (empty($options['connection_string']) || empty($options['container_name'])) { - throw new InvalidStorageConfigurationException( - "Can't create Azure Blob storage adapter: " . - "required 'connection_string' and/or 'container_name' options are absent" - ); - } - $client = BlobRestProxy::createBlobService($options['connection_string']); - return new AzureBlobStorageAdapter($client, $options['container_name'], $options['prefix'] ?? null); - } -} diff --git a/lib/internal/Magento/Framework/Storage/AdapterFactory/LocalFactory.php b/lib/internal/Magento/Framework/Storage/AdapterFactory/LocalFactory.php deleted file mode 100644 index edf6535f372f9..0000000000000 --- a/lib/internal/Magento/Framework/Storage/AdapterFactory/LocalFactory.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage\AdapterFactory; - -use League\Flysystem\Adapter\Local; -use League\Flysystem\AdapterInterface; -use Magento\Framework\Storage\InvalidStorageConfigurationException; - -/** - * Factory for local filesystem storage adapter - */ -class LocalFactory implements AdapterFactoryInterface -{ - public const ADAPTER_NAME = 'local'; - - /** - * @inheritdoc - */ - public function create(array $options): AdapterInterface - { - if (empty($options['root'])) { - throw new InvalidStorageConfigurationException( - "Can't create local filesystem storage adapter: required 'root' option is absent" - ); - } - return new Local($options['root']); - } -} diff --git a/lib/internal/Magento/Framework/Storage/FileNotFoundException.php b/lib/internal/Magento/Framework/Storage/FileNotFoundException.php deleted file mode 100644 index 540fc332130e8..0000000000000 --- a/lib/internal/Magento/Framework/Storage/FileNotFoundException.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\Storage; - -/** - * Exception: FileNotFoundException - * - * Exception to be thrown when the a requested file does not exists - */ -class FileNotFoundException extends \RuntimeException -{ -} diff --git a/lib/internal/Magento/Framework/Storage/InvalidStorageConfigurationException.php b/lib/internal/Magento/Framework/Storage/InvalidStorageConfigurationException.php deleted file mode 100644 index 6f388103bfe21..0000000000000 --- a/lib/internal/Magento/Framework/Storage/InvalidStorageConfigurationException.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Framework\Storage; - -/** - * Exception to be thrown in a case when storage is configured incorrectly - */ -class InvalidStorageConfigurationException extends \RuntimeException -{ -} diff --git a/lib/internal/Magento/Framework/Storage/README.md b/lib/internal/Magento/Framework/Storage/README.md deleted file mode 100644 index 4cd276df8808f..0000000000000 --- a/lib/internal/Magento/Framework/Storage/README.md +++ /dev/null @@ -1,121 +0,0 @@ -The Storage library provides abstraction over different file storage providers. - -## Usage - -A module that needs file storage, it can be configured via `\Magento\Framework\Storage\StorageProvider` in `di.xml`: - -```xml -<type name="Magento\Framework\Storage\StorageProvider"> - <arguments> - <argument name="storage" xsi:type="array"> - <item name="storage-name" xsi:type="string">default/location</item> - </argument> - </arguments> -</type> -``` - -`default/location` is a default path in local filesystem, relative to Magento root. - -Now, in a PHP class that uses the declared storage, use the same `\Magento\Framework\Storage\StorageProvider` to get it: - -```php -/** - * @var \Magento\Framework\Storage\StorageProvider - */ -private $storageProvider; - -public function doSomething() -{ - $storage = $this->storageProvider->get('storage-name') - $storage->put('path.txt', $content); -} -``` - -## Configuring Storage - -A storage can be configured in `env.php`: - -```php -'storage' => [ - 'storage-name' => [ - 'adapter' => 'aws_s3', - 'options' => [ - 'client' => [ - 'credentials' => [ - 'key' => '<key>', - 'secret' => '<secret>' - ], - 'region' => '<region>', - 'version' => 'latest', - ], - 'bucket' => '<bucket>', - ], - ], - 'media' => [ - // this is default configuration, so it doesn't need to be configured explicitly like so - 'adapter' => 'local', - 'options' => [ - 'root' => 'pub/media' - ] - ] -] -``` - -Different providers have different `options` available for configuration. -Under the hood, Magento Storage relies on [Flysystem](https://github.com/thephpleague/flysystem) library, so`options` might reflect options required by a corresponding storage adapter implemented for Flysystem. - -## Storage Providers - -By default, Magento Storage provides support for the following storage providers: - -* Local filesystem (based on `\League\Flysystem\Adapter\Local`) - * Adapter name: `local` - * Options: - ```php - [ - 'root' => 'path/relative/to/magento/root' - ] - ``` -* AWS S3 V3 (based on `\League\Flysystem\AwsS3v3\AwsS3Adapter`) - * Adapter name: `aws_s3` - * Options: - ```php - [ - 'client' => [ - 'credentials' => [ - 'key' => '<key>', - 'secret' => '<secret>' - ], - 'region' => '<region>', - 'version' => 'latest', - ], - 'bucket' => '<bucket>', - 'prefix' => '<prefix>', - ] - ``` -* Azure Blob storage (based on `\League\Flysystem\AzureBlobStorage\AzureBlobStorageAdapter`) - * Adapter name: `ms_azure` - * Options: - ```php - [ - 'connection_string' => '<connection-string>', - 'container_name' => '<container-name>', - 'prefix' => '<prefix>', - ] - ``` - -Additional adapters can be added by: -1. Creating an adapter factory implementing `\Magento\Framework\Storage\AdapterFactory\AdapterFactoryInterface` -2. Registering the factory in `Magento\Framework\Storage\StorageProvider` via `di.xml`: - ```xml - <type name="Magento\Framework\Storage\StorageProvider"> - <arguments> - <argument name="storageAdapters" xsi:type="array"> - <item name="custom_adapter" xsi:type="string">My\Storage\AdapterFactory</item> - </argument> - </arguments> - </type> - ``` - -The factory is registered as a "string" (name of the class). -That's because in most cases only a few adapters will be really created for a single application, and we don't want to create unnecessary factory instances. diff --git a/lib/internal/Magento/Framework/Storage/RootViolationException.php b/lib/internal/Magento/Framework/Storage/RootViolationException.php deleted file mode 100644 index 3ea41bfb57073..0000000000000 --- a/lib/internal/Magento/Framework/Storage/RootViolationException.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\Storage; - -/** - * Exception: RootViolationException - * - * Exception to be thrown when the a directory root not specified - */ -class RootViolationException extends \RuntimeException -{ -} diff --git a/lib/internal/Magento/Framework/Storage/Storage.php b/lib/internal/Magento/Framework/Storage/Storage.php deleted file mode 100644 index fd0eaac008368..0000000000000 --- a/lib/internal/Magento/Framework/Storage/Storage.php +++ /dev/null @@ -1,77 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage; - -use League\Flysystem\Filesystem; - -/** - * File storage abstraction - */ -class Storage implements StorageInterface -{ - - /** - * @var Filesystem - */ - private $filesystem; - - /** - * Storage constructor. - * - * @param Filesystem $filesystem - */ - public function __construct(Filesystem $filesystem) - { - $this->filesystem = $filesystem; - } - - /** - * @inheritDoc - */ - public function put($path, $contents, array $config = []): bool - { - return $this->filesystem->put($path, $contents, $config); - } - - /** - * @inheritDoc - */ - public function deleteDir($dirname): bool - { - try { - $result = $this->filesystem->deleteDir($dirname); - } catch (\League\Flysystem\RootViolationException $exception) { - throw new \Magento\Framework\Storage\RootViolationException($exception->getMessage()); - } - return $result; - } - - /** - * @inheritDoc - */ - public function getMetadata($path): ?array - { - try { - $metadata = $this->filesystem->getMetadata($path); - } catch (\League\Flysystem\FileNotFoundException $exception) { - throw new \Magento\Framework\Storage\FileNotFoundException( - $exception->getMessage() - ); - } - if ($metadata === false) { - $metadata = null; - } - return $metadata; - } - - /** - * @inheritDoc - */ - public function has($path): bool - { - return $this->filesystem->has($path); - } -} diff --git a/lib/internal/Magento/Framework/Storage/StorageAdapterProvider.php b/lib/internal/Magento/Framework/Storage/StorageAdapterProvider.php deleted file mode 100644 index bcd5fe806c0c3..0000000000000 --- a/lib/internal/Magento/Framework/Storage/StorageAdapterProvider.php +++ /dev/null @@ -1,63 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage; - -use League\Flysystem\AdapterInterface; -use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\Storage\AdapterFactory\AdapterFactoryInterface; - -/** - * Provider of storage adapters based on storage name - */ -class StorageAdapterProvider -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @var array - */ - private $config; - - /** - * Constructor - * - * @param ObjectManagerInterface $objectManager - * @param array $config - */ - public function __construct(ObjectManagerInterface $objectManager, array $config) - { - $this->objectManager = $objectManager; - $this->config = $config; - } - - /** - * Create storage adapter based on its name with provided options - * - * @param string $adapterName - * @param array $options - * @return AdapterInterface|null - */ - public function create(string $adapterName, array $options) :? AdapterInterface - { - if (!isset($this->config[$adapterName])) { - throw new InvalidStorageConfigurationException( - "Configured adapter '$adapterName' is not supported" - ); - } - $adapterFactoryClass = $this->config[$adapterName]; - $adapterFactory = $this->objectManager->get($adapterFactoryClass); - if (!$adapterFactory instanceof AdapterFactoryInterface) { - throw new InvalidStorageConfigurationException( - "Configured storage adapter factory '$adapterFactory' must implement " . - "'\Magento\Framework\Storage\AdapterFactory\AdapterFactoryInterface'" - ); - } - return $adapterFactory->create($options); - } -} diff --git a/lib/internal/Magento/Framework/Storage/StorageInterface.php b/lib/internal/Magento/Framework/Storage/StorageInterface.php deleted file mode 100644 index 4cf965b2287cd..0000000000000 --- a/lib/internal/Magento/Framework/Storage/StorageInterface.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage; - -/** - * Storage interface to be used by client code to manipulate objects in the storage - * - * Retrieve a real instance of storage via $storageProvider->get('<your-storage-name>'), - * where $storageProvider is an instance of \Magento\Framework\Storage\StorageProvider - */ -interface StorageInterface -{ - /** - * Create a file or update if exists. - * - * @param string $path The path to the file. - * @param string $contents The file contents. - * @param array $config An optional configuration array. - * - * @return bool True on success, false on failure. - */ - public function put($path, $contents, array $config = []): bool; - - /** - * Delete a directory. - * - * @param string $dirname - * - * @throws RootViolationException Thrown if $dirname is empty. - * - * @return bool True on success, false on failure. - */ - public function deleteDir($dirname): bool; - - /** - * Get a file's metadata. - * - * @param string $path The path to the file. - * - * @throws FileNotFoundException - * - * @return array|false The file metadata or false on failure. - */ - public function getMetadata($path): ?array; - - /** - * Check whether a file exists. - * - * @param string $path - * - * @return bool - */ - public function has($path): bool; -} diff --git a/lib/internal/Magento/Framework/Storage/StorageProvider.php b/lib/internal/Magento/Framework/Storage/StorageProvider.php deleted file mode 100644 index bad0813f33002..0000000000000 --- a/lib/internal/Magento/Framework/Storage/StorageProvider.php +++ /dev/null @@ -1,102 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage; - -use Magento\Framework\App\DeploymentConfig; -use Magento\Framework\Storage\AdapterFactory\LocalFactory; -use Magento\Framework\Storage\StorageFactory; -use League\Flysystem\FilesystemFactory; - -/** - * Main entry point for accessing file storage - * - * See README.md for usage details - */ -class StorageProvider -{ - private $storageConfig = []; - - private $storage = []; - - /** - * @var StorageFactory - */ - private $storageFactory; - - /** - * @var StorageAdapterProvider - */ - private $adapterProvider; - - /** - * @var FilesystemFactory - */ - private $filesystemFactory; - - /** - * StorageProvider constructor. - * @param StorageAdapterProvider $adapterProvider - * @param \Magento\Framework\Storage\StorageFactory $storageFactory - * @param array $storage - * @param DeploymentConfig $envConfig - * @param FilesystemFactory $filesystemFactory - * @throws \Magento\Framework\Exception\FileSystemException - * @throws \Magento\Framework\Exception\RuntimeException - */ - public function __construct( - StorageAdapterProvider $adapterProvider, - StorageFactory $storageFactory, - array $storage, - DeploymentConfig $envConfig, - FilesystemFactory $filesystemFactory - ) { - foreach ($storage as $storageName => $localPath) { - $this->storageConfig[$storageName] = [ - 'adapter' => LocalFactory::ADAPTER_NAME, - 'options' => [ - 'root' => BP . '/' . $localPath, - ], - ]; - $envStorageConfig = $envConfig->get('storage/' . $storageName); - if ($envStorageConfig) { - $this->storageConfig[$storageName] = array_replace( - $this->storageConfig[$storageName], - $envStorageConfig - ); - } - } - $this->filesystemFactory = $filesystemFactory; - $this->storageFactory = $storageFactory; - $this->adapterProvider = $adapterProvider; - } - - /** - * Get storage by its name - * - * @param string $storageName - * @return StorageInterface - */ - public function get(string $storageName): StorageInterface - { - if (!isset($this->storage[$storageName])) { - if (isset($this->storageConfig[$storageName])) { - $config = $this->storageConfig[$storageName]; - if (empty($config['adapter']) || empty($config['options'])) { - throw new InvalidStorageConfigurationException( - "Incorrect configuration for storage '$storageName': required field " . - "'adapter' and/or 'options' is not defined" - ); - } - $adapter = $this->adapterProvider->create($config['adapter'], $config['options']); - $filesystem = $this->filesystemFactory->create(['adapter' => $adapter]); - $this->storage[$storageName] = $this->storageFactory->create(['filesystem' => $filesystem]); - } else { - throw new UnsupportedStorageException("No storage with name '$storageName' is declared"); - } - } - return $this->storage[$storageName]; - } -} diff --git a/lib/internal/Magento/Framework/Storage/UnsupportedStorageException.php b/lib/internal/Magento/Framework/Storage/UnsupportedStorageException.php deleted file mode 100644 index 8267fea85319e..0000000000000 --- a/lib/internal/Magento/Framework/Storage/UnsupportedStorageException.php +++ /dev/null @@ -1,13 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Storage; - -/** - * Exception to be thrown when unsupported (undeclared) storage is requested - */ -class UnsupportedStorageException extends \RuntimeException -{ -} diff --git a/setup/src/Magento/Setup/Test/Unit/Module/I18n/ContextTest.php b/setup/src/Magento/Setup/Test/Unit/Module/I18n/ContextTest.php new file mode 100644 index 0000000000000..1917d7aef5d13 --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Module/I18n/ContextTest.php @@ -0,0 +1,153 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Setup\Test\Unit\Module\I18n; + +use Magento\Framework\Component\ComponentRegistrar; +use \Magento\Setup\Module\I18n\Context; + +class ContextTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Setup\Module\I18n\Context + */ + protected $context; + + /** + * @var \Magento\Framework\Component\ComponentRegistrar|\PHPUnit_Framework_MockObject_MockObject + */ + protected $componentRegistrar; + + protected function setUp() + { + $this->componentRegistrar = $this->createMock(\Magento\Framework\Component\ComponentRegistrar::class); + } + + /** + * @param array $context + * @param string $path + * @param array $pathValues + * @dataProvider dataProviderContextByPath + */ + public function testGetContextByPath($context, $path, $pathValues) + { + $this->componentRegistrar->expects($this->any()) + ->method('getPaths') + ->willReturnMap($pathValues); + $this->context = new Context($this->componentRegistrar); + $this->assertEquals($context, $this->context->getContextByPath($path)); + } + + /** + * @return array + */ + public function dataProviderContextByPath() + { + return [ + [ + [Context::CONTEXT_TYPE_MODULE, 'Magento_Module'], + '/app/code/Magento/Module/Block/Test.php', + [ + [Context::CONTEXT_TYPE_MODULE, ['Magento_Module' => '/app/code/Magento/Module']], + [Context::CONTEXT_TYPE_THEME, []], + ] + ], + [ + [Context::CONTEXT_TYPE_THEME, 'frontend/Some/theme'], + '/app/design/area/theme/test.phtml', + [ + [Context::CONTEXT_TYPE_MODULE, []], + [Context::CONTEXT_TYPE_THEME, ['frontend/Some/theme' => '/app/design/area/theme']], + ] + ], + [ + [Context::CONTEXT_TYPE_LIB, 'lib/web/module/test.phtml'], + '/lib/web/module/test.phtml', + [ + [Context::CONTEXT_TYPE_MODULE, []], + [Context::CONTEXT_TYPE_THEME, []], + ] + ], + ]; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid path given: "invalid_path". + */ + public function testGetContextByPathWithInvalidPath() + { + $this->componentRegistrar->expects($this->any()) + ->method('getPaths') + ->willReturnMap([ + [ComponentRegistrar::MODULE, ['/path/to/module']], + [ComponentRegistrar::THEME, ['/path/to/theme']] + ]); + $this->context = new Context($this->componentRegistrar); + $this->context->getContextByPath('invalid_path'); + } + + /** + * @param string $path + * @param array $context + * @param array $registrar + * @dataProvider dataProviderPathToLocaleDirectoryByContext + */ + public function testBuildPathToLocaleDirectoryByContext($path, $context, $registrar) + { + $paths = []; + foreach ($registrar as $module) { + $paths[$module[1]] = $module[2]; + } + $this->componentRegistrar->expects($this->any()) + ->method('getPath') + ->willReturnMap($registrar); + $this->context = new Context($this->componentRegistrar); + $this->assertEquals($path, $this->context->buildPathToLocaleDirectoryByContext($context[0], $context[1])); + } + + /** + * @return array + */ + public function dataProviderPathToLocaleDirectoryByContext() + { + return [ + [ + BP . '/app/code/Magento/Module/i18n/', + [Context::CONTEXT_TYPE_MODULE, 'Magento_Module'], + [[ComponentRegistrar::MODULE, 'Magento_Module', BP . '/app/code/Magento/Module']] + ], + [ + BP . '/app/design/frontend/Magento/luma/i18n/', + [Context::CONTEXT_TYPE_THEME, 'frontend/Magento/luma'], + [[ComponentRegistrar::THEME, 'frontend/Magento/luma', BP . '/app/design/frontend/Magento/luma']] + ], + + [ + null, + [Context::CONTEXT_TYPE_MODULE, 'Unregistered_Module'], + [[ComponentRegistrar::MODULE, 'Unregistered_Module', null]] + ], + [ + null, + [Context::CONTEXT_TYPE_THEME, 'frontend/Magento/unregistered'], + [[ComponentRegistrar::THEME, 'frontend/Magento/unregistered', null]] + ], + [BP . '/lib/web/i18n/', [Context::CONTEXT_TYPE_LIB, 'lib/web/module/test.phtml'], []], + ]; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Invalid context given: "invalid_type". + */ + public function testBuildPathToLocaleDirectoryByContextWithInvalidType() + { + $this->componentRegistrar->expects($this->never()) + ->method('getPath'); + $this->context = new Context($this->componentRegistrar); + $this->context->buildPathToLocaleDirectoryByContext('invalid_type', 'Magento_Module'); + } +} From 34c63d2e213010b2abb686bb07d08cae1577857e Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Fri, 6 Mar 2020 08:53:49 +0200 Subject: [PATCH 200/369] MC-31292: PayPal Express redirect to Login page --- ...uestPaymentInformationManagementPlugin.php | 4 +- .../PreventExpressCheckoutObserver.php | 89 ---------- ...PaymentInformationManagementPluginTest.php | 10 +- .../PreventExpressCheckoutObserverTest.php | 168 ------------------ .../Persistent/etc/frontend/events.xml | 1 - 5 files changed, 8 insertions(+), 264 deletions(-) delete mode 100644 app/code/Magento/Persistent/Observer/PreventExpressCheckoutObserver.php delete mode 100644 app/code/Magento/Persistent/Test/Unit/Observer/PreventExpressCheckoutObserverTest.php diff --git a/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php b/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php index 2641102ca4d72..b4df4125a2c60 100644 --- a/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php +++ b/app/code/Magento/Persistent/Model/Checkout/GuestPaymentInformationManagementPlugin.php @@ -11,6 +11,8 @@ /** * Plugin to convert shopping cart from persistent cart to guest cart before order save when customer not logged in + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class GuestPaymentInformationManagementPlugin { @@ -93,7 +95,7 @@ public function __construct( * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeSavePaymentInformationAndPlaceOrder( + public function beforeSavePaymentInformation( GuestPaymentInformationManagement $subject, $cartId, $email, diff --git a/app/code/Magento/Persistent/Observer/PreventExpressCheckoutObserver.php b/app/code/Magento/Persistent/Observer/PreventExpressCheckoutObserver.php deleted file mode 100644 index d7a54f2c5fec3..0000000000000 --- a/app/code/Magento/Persistent/Observer/PreventExpressCheckoutObserver.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php -/** - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Persistent\Observer; - -use Magento\Framework\Event\ObserverInterface; - -class PreventExpressCheckoutObserver implements ObserverInterface -{ - /** - * @var \Magento\Framework\Message\ManagerInterface - */ - protected $messageManager; - - /** - * Url model - * - * @var \Magento\Framework\UrlInterface - */ - protected $_url; - - /** - * Persistent session - * - * @var \Magento\Persistent\Helper\Session - */ - protected $_persistentSession = null; - - /** - * @var \Magento\Customer\Model\Session - */ - protected $_customerSession; - - /** - * @var \Magento\Checkout\Helper\ExpressRedirect - */ - protected $_expressRedirectHelper; - - /** - * @param \Magento\Persistent\Helper\Session $persistentSession - * @param \Magento\Customer\Model\Session $customerSession - * @param \Magento\Framework\UrlInterface $url - * @param \Magento\Framework\Message\ManagerInterface $messageManager - * @param \Magento\Checkout\Helper\ExpressRedirect $expressRedirectHelper - */ - public function __construct( - \Magento\Persistent\Helper\Session $persistentSession, - \Magento\Customer\Model\Session $customerSession, - \Magento\Framework\UrlInterface $url, - \Magento\Framework\Message\ManagerInterface $messageManager, - \Magento\Checkout\Helper\ExpressRedirect $expressRedirectHelper - ) { - $this->_persistentSession = $persistentSession; - $this->_customerSession = $customerSession; - $this->_url = $url; - $this->messageManager = $messageManager; - $this->_expressRedirectHelper = $expressRedirectHelper; - } - - /** - * Prevent express checkout - * - * @param \Magento\Framework\Event\Observer $observer - * @return void - */ - public function execute(\Magento\Framework\Event\Observer $observer) - { - if (!($this->_persistentSession->isPersistent() && !$this->_customerSession->isLoggedIn())) { - return; - } - - /** @var $controllerAction \Magento\Checkout\Controller\Express\RedirectLoginInterface*/ - $controllerAction = $observer->getEvent()->getControllerAction(); - if (!$controllerAction || - !$controllerAction instanceof \Magento\Checkout\Controller\Express\RedirectLoginInterface || - $controllerAction->getRedirectActionName() != $controllerAction->getRequest()->getActionName() - ) { - return; - } - - $this->messageManager->addNotice(__('To check out, please sign in using your email address.')); - $customerBeforeAuthUrl = $this->_url->getUrl('persistent/index/expressCheckout'); - - $this->_expressRedirectHelper->redirectLogin($controllerAction, $customerBeforeAuthUrl); - } -} diff --git a/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php b/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php index c7f84b476fa7e..01f8c5403ea22 100644 --- a/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Model/Checkout/GuestPaymentInformationManagementPluginTest.php @@ -111,7 +111,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderCartConvertsToGuest $collectionMock->expects($this->once())->method('walk')->with($walkMethod, $walkArgs); $this->cartRepositoryMock->expects($this->once())->method('save')->with($quoteMock); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, @@ -134,7 +134,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderShoppingCartNotPers $this->persistentSessionMock->expects($this->once())->method('isPersistent')->willReturn(true); $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->willReturn(false); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, @@ -155,7 +155,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderPersistentSessionNo $this->persistentSessionMock->expects($this->once())->method('isPersistent')->willReturn(false); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, @@ -177,7 +177,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderCustomerSessionInLo $this->persistentSessionMock->expects($this->once())->method('isPersistent')->willReturn(true); $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->willReturn(true); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, @@ -201,7 +201,7 @@ public function testBeforeSavePaymentInformationAndPlaceOrderQuoteManagerNotInPe $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->willReturn(false); $this->quoteManagerMock->expects($this->once())->method('isPersistent')->willReturn(false); - $this->plugin->beforeSavePaymentInformationAndPlaceOrder( + $this->plugin->beforeSavePaymentInformation( $this->subjectMock, $cartId, $email, diff --git a/app/code/Magento/Persistent/Test/Unit/Observer/PreventExpressCheckoutObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Observer/PreventExpressCheckoutObserverTest.php deleted file mode 100644 index 7749377bbfcd0..0000000000000 --- a/app/code/Magento/Persistent/Test/Unit/Observer/PreventExpressCheckoutObserverTest.php +++ /dev/null @@ -1,168 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Persistent\Test\Unit\Observer; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class PreventExpressCheckoutObserverTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Persistent\Observer\PreventExpressCheckoutObserver - */ - protected $_model; - - /** - * @var \Magento\Framework\Event - */ - protected $_event; - - /** - * @var \Magento\Framework\Event\Observer - */ - protected $_observer; - - /** - * Customer session - * - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_customerSession; - - /** - * Persistent session - * - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_persistentSession; - - /** - * - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_messageManager; - - /** - * Url model - * - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_url; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $_expressRedirectHelper; - - protected function setUp() - { - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->_event = new \Magento\Framework\Event(); - $this->_observer = new \Magento\Framework\Event\Observer(); - $this->_observer->setEvent($this->_event); - - $this->_customerSession = $this->getMockBuilder( - \Magento\Customer\Model\Session::class - )->disableOriginalConstructor()->setMethods( - ['isLoggedIn'] - )->getMock(); - - $this->_persistentSession = $this->getMockBuilder( - \Magento\Persistent\Helper\Session::class - )->disableOriginalConstructor()->setMethods( - ['isPersistent'] - )->getMock(); - - $this->_messageManager = $this->getMockBuilder( - \Magento\Framework\Message\ManagerInterface::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - - $this->_url = $this->getMockBuilder( - \Magento\Framework\UrlInterface::class - )->disableOriginalConstructor()->setMethods( - [] - )->getMock(); - - $this->_expressRedirectHelper = $this->getMockBuilder( - \Magento\Checkout\Helper\ExpressRedirect::class - )->disableOriginalConstructor()->setMethods( - ['redirectLogin'] - )->getMock(); - - $this->_model = $helper->getObject( - \Magento\Persistent\Observer\PreventExpressCheckoutObserver::class, - [ - 'customerSession' => $this->_customerSession, - 'persistentSession' => $this->_persistentSession, - 'messageManager' => $this->_messageManager, - 'url' => $this->_url, - 'expressRedirectHelper' => $this->_expressRedirectHelper - ] - ); - } - - public function testPreventExpressCheckoutOnline() - { - $this->_customerSession->expects($this->once())->method('isLoggedIn')->will($this->returnValue(true)); - $this->_persistentSession->expects($this->once())->method('isPersistent')->will($this->returnValue(true)); - $this->_model->execute($this->_observer); - } - - public function testPreventExpressCheckoutEmpty() - { - $this->_customerSession->expects($this->any())->method('isLoggedIn')->will($this->returnValue(false)); - $this->_persistentSession->expects($this->any())->method('isPersistent')->will($this->returnValue(true)); - - $this->_event->setControllerAction(null); - $this->_model->execute($this->_observer); - - $this->_event->setControllerAction(new \StdClass()); - $this->_model->execute($this->_observer); - - $expectedActionName = 'realAction'; - $unexpectedActionName = 'notAction'; - $request = new \Magento\Framework\DataObject(); - $request->setActionName($unexpectedActionName); - $expressRedirectMock = $this->getMockBuilder( - \Magento\Checkout\Controller\Express\RedirectLoginInterface::class - )->disableOriginalConstructor()->setMethods( - [ - 'getActionFlagList', - 'getResponse', - 'getCustomerBeforeAuthUrl', - 'getLoginUrl', - 'getRedirectActionName', - 'getRequest', - ] - )->getMock(); - $expressRedirectMock->expects($this->any())->method('getRequest')->will($this->returnValue($request)); - $expressRedirectMock->expects( - $this->any() - )->method( - 'getRedirectActionName' - )->will( - $this->returnValue($expectedActionName) - ); - $this->_event->setControllerAction($expressRedirectMock); - $this->_model->execute($this->_observer); - - $expectedAuthUrl = 'expectedAuthUrl'; - $request->setActionName($expectedActionName); - $this->_url->expects($this->once())->method('getUrl')->will($this->returnValue($expectedAuthUrl)); - $this->_expressRedirectHelper->expects( - $this->once() - )->method( - 'redirectLogin' - )->with( - $expressRedirectMock, - $expectedAuthUrl - ); - $this->_model->execute($this->_observer); - } -} diff --git a/app/code/Magento/Persistent/etc/frontend/events.xml b/app/code/Magento/Persistent/etc/frontend/events.xml index 79720695ea6f6..840297b0ff098 100644 --- a/app/code/Magento/Persistent/etc/frontend/events.xml +++ b/app/code/Magento/Persistent/etc/frontend/events.xml @@ -34,7 +34,6 @@ <observer name="persistent_session" instance="Magento\Persistent\Observer\RenewCookieObserver" /> <observer name="persistent_quote" instance="Magento\Persistent\Observer\CheckExpirePersistentQuoteObserver" /> <observer name="persistent_customer" instance="Magento\Persistent\Observer\EmulateCustomerObserver" /> - <observer name="persistent_checkout" instance="Magento\Persistent\Observer\PreventExpressCheckoutObserver" /> </event> <event name="customer_customer_authenticated"> <observer name="persistent" instance="Magento\Persistent\Observer\CustomerAuthenticatedEventObserver" /> From 8211eb79022e2167dad38349c63bb9c36279e34d Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Fri, 6 Mar 2020 10:31:24 +0200 Subject: [PATCH 201/369] MC-31573: PayflowPro Checkout Broken with SameSite Cookie Changes from Chrome 80 --- .../Adminhtml/Transparent/Redirect.php | 60 ++++++++++++++++++- .../Controller/Transparent/Redirect.php | 25 ++++---- 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php index 8201761cc3a29..6e25574929550 100644 --- a/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php +++ b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php @@ -5,9 +5,67 @@ */ namespace Magento\Paypal\Controller\Adminhtml\Transparent; +use Magento\Backend\App\AbstractAction; +use Magento\Backend\App\Action\Context; +use Magento\Framework\View\Result\LayoutFactory; +use Magento\Payment\Model\Method\Logger; +use Magento\Paypal\Model\Payflow\Transparent; + /** * Class for redirecting the Paypal response result to Magento controller. */ -class Redirect extends \Magento\Paypal\Controller\Transparent\Redirect +class Redirect extends AbstractAction { + /** + * @var LayoutFactory + */ + private $resultLayoutFactory; + + /** + * @var Transparent + */ + private $transparent; + + /** + * @var Logger + */ + private $logger; + + /** + * @param Context $context + * @param LayoutFactory $resultLayoutFactory + * @param Transparent $transparent + * @param Logger $logger + */ + public function __construct( + Context $context, + LayoutFactory $resultLayoutFactory, + Transparent $transparent, + Logger $logger + ) + { + parent::__construct($context); + $this->transparent = $transparent; + $this->logger = $logger; + $this->resultLayoutFactory = $resultLayoutFactory; + } + + /** + * @inheritdoc + */ + public function execute() + { + $gatewayResponse = (array)$this->getRequest()->getPostValue(); + $this->logger->debug( + ['PayPal PayflowPro redirect:' => $gatewayResponse], + $this->transparent->getDebugReplacePrivateDataKeys(), + $this->transparent->getDebugFlag() + ); + + $resultLayout = $this->resultLayoutFactory->create(); + $resultLayout->addDefaultHandle(); + $resultLayout->getLayout()->getUpdate()->load(['transparent_payment_redirect']); + + return $resultLayout; + } } diff --git a/app/code/Magento/Paypal/Controller/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Transparent/Redirect.php index 7358d51f5c3d6..1b14a97990275 100644 --- a/app/code/Magento/Paypal/Controller/Transparent/Redirect.php +++ b/app/code/Magento/Paypal/Controller/Transparent/Redirect.php @@ -5,8 +5,9 @@ */ namespace Magento\Paypal\Controller\Transparent; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\Action\Context; use Magento\Framework\App\Action\HttpPostActionInterface; -use Magento\Framework\App\ActionInterface; use Magento\Framework\App\CsrfAwareActionInterface; use Magento\Framework\App\Request\InvalidRequestException; use Magento\Framework\App\RequestInterface; @@ -19,13 +20,8 @@ /** * Class for redirecting the Paypal response result to Magento controller. */ -class Redirect implements ActionInterface, CsrfAwareActionInterface, HttpPostActionInterface +class Redirect extends Action implements CsrfAwareActionInterface, HttpPostActionInterface { - /** - * @var RequestInterface - */ - private $request; - /** * @var LayoutFactory */ @@ -42,27 +38,26 @@ class Redirect implements ActionInterface, CsrfAwareActionInterface, HttpPostAct private $logger; /** - * Constructor - * - * @param RequestInterface $request + * @param Context $context * @param LayoutFactory $resultLayoutFactory * @param Transparent $transparent * @param Logger $logger */ public function __construct( - RequestInterface $request, + Context $context, LayoutFactory $resultLayoutFactory, Transparent $transparent, Logger $logger ) { - $this->request = $request; $this->resultLayoutFactory = $resultLayoutFactory; $this->transparent = $transparent; $this->logger = $logger; + + parent::__construct($context); } /** - * @inheritDoc + * @inheritdoc */ public function createCsrfValidationException( RequestInterface $request @@ -71,7 +66,7 @@ public function createCsrfValidationException( } /** - * @inheritDoc + * @inheritdoc */ public function validateForCsrf(RequestInterface $request): ?bool { @@ -86,7 +81,7 @@ public function validateForCsrf(RequestInterface $request): ?bool */ public function execute() { - $gatewayResponse = (array)$this->request->getPostValue(); + $gatewayResponse = (array)$this->getRequest()->getPostValue(); $this->logger->debug( ['PayPal PayflowPro redirect:' => $gatewayResponse], $this->transparent->getDebugReplacePrivateDataKeys(), From 6d6af36f4fb31dfe7f585061d34332ed0819fe1a Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Fri, 6 Mar 2020 11:32:26 +0200 Subject: [PATCH 202/369] MC-31573: PayflowPro Checkout Broken with SameSite Cookie Changes from Chrome 80 --- .../Adminhtml/Transparent/Redirect.php | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php index 6e25574929550..f105843e5abfd 100644 --- a/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php +++ b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php @@ -7,6 +7,10 @@ use Magento\Backend\App\AbstractAction; use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\CsrfAwareActionInterface; +use Magento\Framework\App\Request\InvalidRequestException; +use Magento\Framework\App\RequestInterface; use Magento\Framework\View\Result\LayoutFactory; use Magento\Payment\Model\Method\Logger; use Magento\Paypal\Model\Payflow\Transparent; @@ -14,7 +18,7 @@ /** * Class for redirecting the Paypal response result to Magento controller. */ -class Redirect extends AbstractAction +class Redirect extends AbstractAction implements HttpPostActionInterface, CsrfAwareActionInterface { /** * @var LayoutFactory @@ -42,8 +46,7 @@ public function __construct( LayoutFactory $resultLayoutFactory, Transparent $transparent, Logger $logger - ) - { + ) { parent::__construct($context); $this->transparent = $transparent; $this->logger = $logger; @@ -68,4 +71,21 @@ public function execute() return $resultLayout; } + + /** + * @inheritdoc + */ + public function createCsrfValidationException( + RequestInterface $request + ): ?InvalidRequestException { + return null; + } + + /** + * @inheritdoc + */ + public function validateForCsrf(RequestInterface $request): ?bool + { + return true; + } } From 04e1bb0a74b3d6c17714f4e9d1c80320d5adf5a1 Mon Sep 17 00:00:00 2001 From: Viktor Petryk <victor.petryk@transoftgroup.com> Date: Fri, 6 Mar 2020 12:59:55 +0200 Subject: [PATCH 203/369] MC-24245: [MFTF Test] Product is deleted from cart if not included in shared catalog --- ...ProductAbsentOnCategoryPageActionGroup.xml | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductAbsentOnCategoryPageActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductAbsentOnCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductAbsentOnCategoryPageActionGroup.xml new file mode 100644 index 0000000000000..f98229f8aaada --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductAbsentOnCategoryPageActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontProductAbsentOnCategoryPageActionGroup"> + <annotations> + <description>Navigate to category page and verify product is absent.</description> + </annotations> + <arguments> + <argument name="categoryUrlKey" defaultValue="{{_defaultCategory.url_key}}"/> + <argument name="productName" defaultValue="{{SimpleProduct.name}}"/> + </arguments> + + <amOnPage url="{{StorefrontCategoryPage.url(categoryUrlKey)}}" stepKey="navigateToCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <dontSee selector="{{StorefrontCategoryMainSection.productName}}" userInput="{{productName}}" stepKey="assertProductIsNotPresent"/> + </actionGroup> +</actionGroups> From 6f1e2e7b0481933f35f4e6def35b63ad2b3ffc8e Mon Sep 17 00:00:00 2001 From: Roman Zhupanyn <roma.dj.elf@gmail.com> Date: Fri, 6 Mar 2020 13:02:32 +0200 Subject: [PATCH 204/369] MC-32126: Admin: Create/update/delete customer --- .../Controller/Adminhtml/Index/DeleteTest.php | 104 ++++ .../Controller/Adminhtml/Index/SaveTest.php | 584 ++++++++++++++++++ .../Adminhtml/Index/ValidateTest.php | 123 ++++ .../Controller/Adminhtml/IndexTest.php | 347 +---------- .../AccountManagement/CreateAccountTest.php | 398 +++++++++++- .../Model/AccountManagement/ValidateTest.php | 113 ++++ .../Customer/Model/AccountManagementTest.php | 330 ---------- 7 files changed, 1327 insertions(+), 672 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/DeleteTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/SaveTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ValidateTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/ValidateTest.php diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/DeleteTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/DeleteTest.php new file mode 100644 index 0000000000000..1f40c459c43e0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/DeleteTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Adminhtml\Index; + +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Tests for delete customer via backend/customer/index/delete controller. + * + * @magentoAppArea adminhtml + */ +class DeleteTest extends AbstractBackendController +{ + /** @var FormKey */ + private $formKey; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->formKey = $this->_objectManager->get(FormKey::class); + } + + /** + * Delete customer + * + * @dataProvider deleteCustomerProvider + * @magentoDataFixture Magento/Customer/_files/customer_sample.php + * + * @param array $paramsData + * @param string $expected + * @return void + */ + public function testDeleteCustomer(array $paramsData, array $expected): void + { + $this->dispatchCustomerDelete($paramsData); + + $this->assertRedirect($this->stringContains('customer/index')); + $this->assertSessionMessages( + $this->equalTo([(string)__(...$expected['message'])]), + $expected['message_type'] + ); + } + + /** + * Delete customer provider + * + * @return array + */ + public function deleteCustomerProvider(): array + { + return [ + 'delete_customer_success' => [ + 'params_data' => [ + 'id' => 1, + ], + 'expected' => [ + 'message' => ['You deleted the customer.'], + 'message_type' => MessageInterface::TYPE_SUCCESS, + ], + ], + 'not_existing_customer_error' => [ + 'params_data' => [ + 'id' => 2, + ], + 'expected' => [ + 'message' => [ + 'No such entity with %fieldName = %fieldValue', + [ + 'fieldName' => 'customerId', + 'fieldValue' => '2', + ], + ], + 'message_type' => MessageInterface::TYPE_ERROR, + ], + ], + ]; + } + + /** + * Delete customer using backend/customer/index/delete action. + * + * @param array $params + * @return void + */ + private function dispatchCustomerDelete(array $params): void + { + $params['form_key'] = $this->formKey->getFormKey(); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setParams($params); + $this->dispatch('backend/customer/index/delete'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/SaveTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/SaveTest.php new file mode 100644 index 0000000000000..aed6003ea76ac --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/SaveTest.php @@ -0,0 +1,584 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Adminhtml\Index; + +use Magento\Backend\Model\Session; +use Magento\Customer\Api\CustomerNameGenerationInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\Data\Customer as CustomerData; +use Magento\Customer\Model\EmailNotification; +use Magento\Framework\App\Area; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Mail\Template\TransportBuilder; +use Magento\Framework\Mail\TransportInterface; +use Magento\Framework\Message\MessageInterface; +use Magento\Newsletter\Model\Subscriber; +use Magento\Newsletter\Model\SubscriberFactory; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Tests for save customer via backend/customer/index/save controller. + * + * @magentoAppArea adminhtml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class SaveTest extends AbstractBackendController +{ + /** + * Base controller URL + * + * @var string + */ + private $_baseControllerUrl = 'http://localhost/index.php/backend/customer/index/'; + + /** @var CustomerRepositoryInterface */ + private $customerRepository; + + /**@var CustomerNameGenerationInterface */ + private $customerViewHelper; + + /** @var SubscriberFactory */ + private $subscriberFactory; + + /** @var Session */ + private $session; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->customerViewHelper = $this->_objectManager->get(CustomerNameGenerationInterface::class); + $this->subscriberFactory = $this->_objectManager->get(SubscriberFactory::class); + $this->session = $this->_objectManager->get(Session::class); + $this->storeManager = $this->_objectManager->get(StoreManagerInterface::class); + } + + /** + * Create customer + * + * @dataProvider createCustomerProvider + * @magentoDbIsolation enabled + * + * @param array $postData + * @param array $expectedData + * @return void + */ + public function testCreateCustomer(array $postData, array $expectedData): void + { + $this->dispatchCustomerSave($postData); + $this->assertSessionMessages( + $this->equalTo([(string)__('You saved the customer.')]), + MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); + $this->assertCustomerData( + $postData['customer'][CustomerData::EMAIL], + (int)$postData['customer'][CustomerData::WEBSITE_ID], + $expectedData + ); + } + + /** + * Create customer provider + * + * @return array + */ + public function createCustomerProvider(): array + { + $defaultCustomerData = $this->getDefaultCustomerData(); + $expectedCustomerData = $this->getExpectedCustomerData($defaultCustomerData); + return [ + "fill_all_fields" => [ + 'post_data' => $defaultCustomerData, + 'expected_data' => $expectedCustomerData + ], + 'only_require_fields' => [ + 'post_data' => array_replace_recursive( + $defaultCustomerData, + [ + 'customer' => [ + CustomerData::DISABLE_AUTO_GROUP_CHANGE => '0', + CustomerData::PREFIX => '', + CustomerData::MIDDLENAME => '', + CustomerData::SUFFIX => '', + CustomerData::DOB => '', + CustomerData::TAXVAT => '', + CustomerData::GENDER => '', + ], + ] + ), + 'expected_data' => array_replace_recursive( + $expectedCustomerData, + [ + 'customer' => [ + CustomerData::DISABLE_AUTO_GROUP_CHANGE => '0', + CustomerData::PREFIX => '', + CustomerData::MIDDLENAME => '', + CustomerData::SUFFIX => '', + CustomerData::DOB => '', + CustomerData::TAXVAT => '', + CustomerData::GENDER => '0', + ], + ] + ), + ], + ]; + } + + /** + * Create customer with exceptions + * + * @dataProvider createCustomerErrorsProvider + * @magentoDbIsolation enabled + * + * @param array $postData + * @param array $expectedData + * @param array $expectedMessage + * @return void + */ + public function testCreateCustomerErrors(array $postData, array $expectedData, array $expectedMessage): void + { + $this->dispatchCustomerSave($postData); + $this->assertSessionMessages( + $this->equalTo($expectedMessage), + MessageInterface::TYPE_ERROR + ); + $this->assertNotEmpty($this->session->getCustomerFormData()); + $this->assertArraySubset($expectedData, $this->session->getCustomerFormData()); + $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new/key/')); + } + + /** + * Create customer errors provider + * + * @return array + */ + public function createCustomerErrorsProvider(): array + { + $defaultCustomerData = $this->getDefaultCustomerData(); + return [ + 'without_some_require_fields' => [ + 'post_data' => array_replace_recursive( + $defaultCustomerData, + [ + 'customer' => [ + CustomerData::FIRSTNAME => '', + CustomerData::LASTNAME => '', + ], + ] + ), + 'expected_data' => array_replace_recursive( + $defaultCustomerData, + [ + 'customer' => [ + CustomerData::FIRSTNAME => '', + CustomerData::LASTNAME => '', + CustomerData::DOB => '2000-01-01', + ], + ] + ), + 'expected_message' => [ + (string)__('"%1" is a required value.', 'First Name'), + (string)__('"%1" is a required value.', 'Last Name'), + ], + ], + 'with_empty_post_data' => [ + 'post_data' => [], + 'expected_data' => [], + 'expected_message' => [ + (string)__('The customer email is missing. Enter and try again.'), + ], + ], + 'with_invalid_form_data' => [ + 'post_data' => [ + 'account' => [ + 'middlename' => 'test middlename', + 'group_id' => 1, + ], + ], + 'expected_data' => [ + 'account' => [ + 'middlename' => 'test middlename', + 'group_id' => 1, + ], + ], + 'expected_message' => [ + (string)__('The customer email is missing. Enter and try again.'), + ], + ] + ]; + } + + /** + * Update customer with subscription and redirect to edit page. + * + * @magentoDataFixture Magento/Store/_files/core_fixturestore.php + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testUpdateCustomer(): void + { + /** @var CustomerData $customerData */ + $customerData = $this->customerRepository->getById(1); + $secondStore = $this->storeManager->getStore('fixturestore'); + $postData = $expectedData = [ + 'customer' => [ + CustomerData::FIRSTNAME => 'Jane', + CustomerData::MIDDLENAME => 'Mdl', + CustomerData::LASTNAME => 'Doe', + ], + 'subscription_status' => [$customerData->getWebsiteId() => '1'], + 'subscription_store' => [$customerData->getWebsiteId() => $secondStore->getId()], + ]; + $postData['customer']['entity_id'] = $customerData->getId(); + $params = ['back' => true]; + + $this->dispatchCustomerSave($postData, $params); + $this->assertSessionMessages( + $this->equalTo([(string)__('You saved the customer.')]), + MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringStartsWith( + $this->_baseControllerUrl . 'edit/id/' . $customerData->getId() + )); + $this->assertCustomerData($customerData->getEmail(), (int)$customerData->getWebsiteId(), $expectedData); + $this->assertCustomerSubscription( + (int)$customerData->getId(), + (int)$customerData->getWebsiteId(), + Subscriber::STATUS_SUBSCRIBED, + (int)$secondStore->getId() + ); + } + + /** + * @magentoDataFixture Magento/Newsletter/_files/subscribers.php + * @return void + */ + public function testExistingCustomerUnsubscribeNewsletter(): void + { + /** @var CustomerData $customerData */ + $customerData = $this->customerRepository->getById(1); + /** @var Store $defaultStore */ + $defaultStore = $this->storeManager->getWebsite()->getDefaultStore(); + $postData = [ + 'customer' => [ + 'entity_id' => $customerData->getId(), + CustomerData::EMAIL => 'customer@example.com', + CustomerData::FIRSTNAME => 'test firstname', + CustomerData::LASTNAME => 'test lastname', + 'sendemail_store_id' => '1' + ], + 'subscription_status' => [$customerData->getWebsiteId() => '0'], + 'subscription_store' => [$customerData->getWebsiteId() => $defaultStore->getId()], + ]; + $this->dispatchCustomerSave($postData); + $this->assertSessionMessages( + $this->equalTo([(string)__('You saved the customer.')]), + MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); + $this->assertCustomerSubscription( + (int)$customerData->getId(), + (int)$customerData->getWebsiteId(), + Subscriber::STATUS_UNSUBSCRIBED, + (int)$defaultStore->getId() + ); + } + + /** + * Ensure that an email is sent during save action + * + * @magentoConfigFixture current_store customer/account_information/change_email_template change_email_template + * @magentoConfigFixture current_store customer/password/forgot_email_identity support + * @magentoDataFixture Magento/Customer/_files/customer_sample.php + * @return void + */ + public function testExistingCustomerChangeEmail(): void + { + $customerId = 1; + $newEmail = 'newcustomer@example.com'; + $transportBuilderMock = $this->prepareEmailMock( + 2, + 'change_email_template', + [ + 'name' => 'CustomerSupport', + 'email' => 'support@example.com', + ], + $customerId, + $newEmail + ); + $this->addEmailMockToClass($transportBuilderMock, EmailNotification::class); + $postData = [ + 'customer' => [ + 'entity_id' => $customerId, + CustomerData::WEBSITE_ID => '1', + CustomerData::GROUP_ID => '1', + CustomerData::FIRSTNAME => 'test firstname', + CustomerData::MIDDLENAME => 'test middlename', + CustomerData::LASTNAME => 'test lastname', + CustomerData::EMAIL => $newEmail, + 'new_password' => 'auto', + 'sendemail_store_id' => '1', + 'sendemail' => '1', + CustomerData::CREATED_AT => '2000-01-01 00:00:00', + CustomerData::DEFAULT_SHIPPING => '_item1', + CustomerData::DEFAULT_BILLING => '1' + ] + ]; + $this->dispatchCustomerSave($postData); + + /** + * Check that no errors were generated and set to session + */ + $this->assertSessionMessages($this->isEmpty(), MessageInterface::TYPE_ERROR); + $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_sample.php + * @return void + */ + public function testCreateSameEmailFormatDateError(): void + { + $postData = [ + 'customer' => [ + CustomerData::WEBSITE_ID => '1', + CustomerData::FIRSTNAME => 'test firstname', + CustomerData::MIDDLENAME => 'test middlename', + CustomerData::LASTNAME => 'test lastname', + CustomerData::EMAIL => 'customer@example.com', + CustomerData::DOB => '12/3/1996', + ], + ]; + $postFormatted = array_replace_recursive( + $postData, + [ + 'customer' => [ + CustomerData::DOB => '1996-12-03', + ], + ] + ); + $this->dispatchCustomerSave($postData); + $this->assertSessionMessages( + $this->equalTo([ + (string)__('A customer with the same email address already exists in an associated website.'), + ]), + MessageInterface::TYPE_ERROR + ); + $this->assertArraySubset( + $postFormatted, + $this->session->getCustomerFormData(), + true, + 'Customer form data should be formatted' + ); + $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new/key/')); + } + + /** + * Default values for customer creation + * + * @return array + */ + private function getDefaultCustomerData(): array + { + return [ + 'customer' => [ + CustomerData::WEBSITE_ID => '1', + CustomerData::GROUP_ID => '1', + CustomerData::DISABLE_AUTO_GROUP_CHANGE => '1', + CustomerData::PREFIX => 'Mr.', + CustomerData::FIRSTNAME => 'Jane', + CustomerData::MIDDLENAME => 'Mdl', + CustomerData::LASTNAME => 'Doe', + CustomerData::SUFFIX => 'Esq.', + CustomerData::EMAIL => 'janedoe' . uniqid() . '@example.com', + CustomerData::DOB => '01/01/2000', + CustomerData::TAXVAT => '121212', + CustomerData::GENDER => 'Male', + 'sendemail_store_id' => '1', + ] + ]; + } + + /** + * Expected values for customer creation + * + * @param array $defaultCustomerData + * @return array + */ + private function getExpectedCustomerData(array $defaultCustomerData): array + { + unset($defaultCustomerData['customer']['sendemail_store_id']); + return array_replace_recursive( + $defaultCustomerData, + [ + 'customer' => [ + CustomerData::DOB => '2000-01-01', + CustomerData::GENDER => '0', + CustomerData::STORE_ID => 1, + CustomerData::CREATED_IN => 'Default Store View', + ], + ] + ); + } + + /** + * Create or update customer using backend/customer/index/save action. + * + * @param array $postData + * @param array $params + * @return void + */ + private function dispatchCustomerSave(array $postData, array $params = []): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setPostValue($postData); + if (!empty($params)) { + $this->getRequest()->setParams($params); + } + $this->dispatch('backend/customer/index/save'); + } + + /** + * Check that customer parameters match expected values. + * + * @param string $customerEmail + * @param int $customerWebsiteId + * @param array $expectedData + * @return void + */ + private function assertCustomerData( + string $customerEmail, + int $customerWebsiteId, + array $expectedData + ): void { + /** @var CustomerData $customerData */ + $customerData = $this->customerRepository->get($customerEmail, $customerWebsiteId); + $actualCustomerArray = $customerData->__toArray(); + foreach ($expectedData['customer'] as $key => $expectedValue) { + $this->assertEquals( + $expectedValue, + $actualCustomerArray[$key], + "Invalid expected value for $key field." + ); + } + } + + /** + * Check that customer subscription status match expected status. + * + * @param int $customerId + * @param int $websiteId + * @param int $expectedStatus + * @param int $expectedStoreId + * @return void + */ + private function assertCustomerSubscription( + int $customerId, + int $websiteId, + int $expectedStatus, + int $expectedStoreId + ): void { + $subscriber = $this->subscriberFactory->create(); + $subscriber->loadByCustomer($customerId, $websiteId); + $this->assertNotEmpty($subscriber->getId()); + $this->assertEquals($expectedStatus, $subscriber->getStatus()); + $this->assertEquals($expectedStoreId, $subscriber->getStoreId()); + } + + /** + * Prepare email mock to test emails. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @param int $occurrenceNumber + * @param string $templateId + * @param array $sender + * @param int $customerId + * @param string|null $newEmail + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function prepareEmailMock( + int $occurrenceNumber, + string $templateId, + array $sender, + int $customerId, + $newEmail = null + ) : \PHPUnit_Framework_MockObject_MockObject { + $area = Area::AREA_FRONTEND; + $customer = $this->customerRepository->getById($customerId); + $storeId = $customer->getStoreId(); + $name = $this->customerViewHelper->getCustomerName($customer); + + $transportMock = $this->getMockBuilder(TransportInterface::class) + ->setMethods(['sendMessage']) + ->getMockForAbstractClass(); + $transportMock->expects($this->exactly($occurrenceNumber)) + ->method('sendMessage'); + $transportBuilderMock = $this->getMockBuilder(TransportBuilder::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'addTo', + 'setFrom', + 'setTemplateIdentifier', + 'setTemplateVars', + 'setTemplateOptions', + 'getTransport', + ] + ) + ->getMock(); + $transportBuilderMock->method('setTemplateIdentifier') + ->with($templateId) + ->willReturnSelf(); + $transportBuilderMock->method('setTemplateOptions') + ->with(['area' => $area, 'store' => $storeId]) + ->willReturnSelf(); + $transportBuilderMock->method('setTemplateVars') + ->willReturnSelf(); + $transportBuilderMock->method('setFrom') + ->with($sender) + ->willReturnSelf(); + $transportBuilderMock->method('addTo') + ->with($this->logicalOr($customer->getEmail(), $newEmail), $name) + ->willReturnSelf(); + $transportBuilderMock->expects($this->exactly($occurrenceNumber)) + ->method('getTransport') + ->willReturn($transportMock); + + return $transportBuilderMock; + } + + /** + * Add email mock to class + * + * @param \PHPUnit_Framework_MockObject_MockObject $transportBuilderMock + * @param string $className + * @return void + */ + private function addEmailMockToClass( + \PHPUnit_Framework_MockObject_MockObject $transportBuilderMock, + $className + ): void { + $mocked = $this->_objectManager->create( + $className, + ['transportBuilder' => $transportBuilderMock] + ); + $this->_objectManager->addSharedInstance( + $mocked, + $className + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ValidateTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ValidateTest.php new file mode 100644 index 0000000000000..d42132823d3da --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ValidateTest.php @@ -0,0 +1,123 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller\Adminhtml\Index; + +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Tests for validation customer via backend/customer/index/validate controller. + * + * @magentoAppArea adminhtml + */ +class ValidateTest extends AbstractBackendController +{ + /** @var SerializerInterface */ + private $jsonSerializer; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->jsonSerializer = $this->_objectManager->get(SerializerInterface::class); + } + + /** + * Validate customer with exception + * + * @magentoDbIsolation enabled + * @return void + */ + public function testValidateCustomerErrors(): void + { + $postData = [ + 'customer' => [], + ]; + $attributeEmptyMessage = 'The "%1" attribute value is empty. Set the attribute and try again.'; + $expectedErrors = [ + 'error' => true, + 'messages' => [ + (string)__($attributeEmptyMessage, 'First Name'), + (string)__($attributeEmptyMessage, 'Last Name'), + (string)__($attributeEmptyMessage, 'Email'), + ], + ]; + + $this->dispatchCustomerValidate($postData); + $errors = $this->jsonSerializer->unserialize($this->getResponse()->getBody()); + $this->assertEquals($expectedErrors, $errors); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @return void + */ + public function testValidateCustomerWithAddressSuccess(): void + { + $customerData = [ + 'customer' => [ + 'entity_id' => '1', + 'middlename' => 'new middlename', + 'group_id' => 1, + 'website_id' => 1, + 'firstname' => 'new firstname', + 'lastname' => 'new lastname', + 'email' => 'example@domain.com', + 'default_shipping' => '_item1', + 'new_password' => 'auto', + 'sendemail_store_id' => '1', + 'sendemail' => '1', + ], + 'address' => [ + '_item1' => [ + 'firstname' => 'update firstname', + 'lastname' => 'update lastname', + 'street' => ['update street'], + 'city' => 'update city', + 'country_id' => 'US', + 'region_id' => 10, + 'postcode' => '01001', + 'telephone' => '+7000000001', + ], + '_template_' => [ + 'firstname' => '', + 'lastname' => '', + 'street' => [], + 'city' => '', + 'country_id' => 'US', + 'postcode' => '', + 'telephone' => '', + ], + ], + ]; + $this->dispatchCustomerValidate($customerData); + + $this->assertSessionMessages($this->isEmpty(), MessageInterface::TYPE_ERROR); + $errors = $this->jsonSerializer->unserialize($this->getResponse()->getBody()); + $this->assertEquals(['error' => 0], $errors); + } + + /** + * Validate customer using backend/customer/index/validate action. + * + * @param array $postData + * @return void + */ + private function dispatchCustomerValidate(array $postData): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->getRequest()->setPostValue($postData); + $this->dispatch('backend/customer/index/validate'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 4a7cc7591f7aa..756270344d720 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -6,19 +6,19 @@ namespace Magento\Customer\Controller\Adminhtml; -use Magento\Customer\Api\AccountManagementInterface; -use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Backend\Model\Session; use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Customer\Controller\RegistryConstants; +use Magento\Customer\Helper\View; use Magento\Customer\Model\EmailNotification; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\TestFramework\TestCase\AbstractBackendController; /** * @magentoAppArea adminhtml * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class IndexTest extends \Magento\TestFramework\TestCase\AbstractBackendController +class IndexTest extends AbstractBackendController { /** * Base controller URL @@ -30,21 +30,9 @@ class IndexTest extends \Magento\TestFramework\TestCase\AbstractBackendControlle /** @var CustomerRepositoryInterface */ protected $customerRepository; - /** @var AddressRepositoryInterface */ - protected $addressRepository; - - /** @var AccountManagementInterface */ - protected $accountManagement; - - /** @var \Magento\Framework\Data\Form\FormKey */ - protected $formKey; - - /**@var \Magento\Customer\Helper\View */ + /** @var View */ protected $customerViewHelper; - /** @var \Magento\TestFramework\ObjectManager */ - protected $objectManager; - /** * @inheritDoc */ @@ -52,23 +40,8 @@ protected function setUp() { parent::setUp(); $this->_baseControllerUrl = 'http://localhost/index.php/backend/customer/index/'; - $this->customerRepository = Bootstrap::getObjectManager()->get( - \Magento\Customer\Api\CustomerRepositoryInterface::class - ); - $this->addressRepository = Bootstrap::getObjectManager()->get( - \Magento\Customer\Api\AddressRepositoryInterface::class - ); - $this->accountManagement = Bootstrap::getObjectManager()->get( - \Magento\Customer\Api\AccountManagementInterface::class - ); - $this->formKey = Bootstrap::getObjectManager()->get( - \Magento\Framework\Data\Form\FormKey::class - ); - - $this->objectManager = Bootstrap::getObjectManager(); - $this->customerViewHelper = $this->objectManager->get( - \Magento\Customer\Helper\View::class - ); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->customerViewHelper = $this->_objectManager->get(View::class); } /** @@ -79,144 +52,12 @@ protected function tearDown() /** * Unset customer data */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->setCustomerData(null); + $this->_objectManager->get(Session::class)->setCustomerData(null); /** * Unset messages */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getMessages(true); - } - - /** - * @magentoDbIsolation enabled - */ - public function testSaveActionWithEmptyPostData() - { - $this->getRequest()->setPostValue([])->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); - } - - /** - * @magentoDbIsolation enabled - */ - public function testSaveActionWithInvalidFormData() - { - $post = ['account' => ['middlename' => 'test middlename', 'group_id' => 1]]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - /** - * Check that errors was generated and set to session - */ - $this->assertSessionMessages( - $this->logicalNot($this->isEmpty()), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - /** @var \Magento\Backend\Model\Session $session */ - $session = $this->objectManager->get(\Magento\Backend\Model\Session::class); - /** - * Check that customer data were set to session - */ - $this->assertNotEmpty($session->getCustomerFormData()); - $this->assertArraySubset($post, $session->getCustomerFormData()); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new')); - } - - /** - * @magentoDataFixture Magento/Newsletter/_files/subscribers.php - */ - public function testSaveActionExistingCustomerUnsubscribeNewsletter() - { - $customerId = 1; - $websiteId = 1; - - /** @var \Magento\Newsletter\Model\Subscriber $subscriber */ - $subscriber = $this->objectManager->get(\Magento\Newsletter\Model\SubscriberFactory::class)->create(); - $this->assertEmpty($subscriber->getId()); - $subscriber->loadByCustomerId($customerId); - $this->assertNotEmpty($subscriber->getId()); - $this->assertEquals(1, $subscriber->getStatus()); - - $post = [ - 'customer' => [ - 'entity_id' => $customerId, - 'email' => 'customer@example.com', - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'sendemail_store_id' => 1 - ], - 'subscription_status' => [$websiteId => '0'] - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->getRequest()->setParam('id', 1); - $this->dispatch('backend/customer/index/save'); - - /** @var \Magento\Newsletter\Model\Subscriber $subscriber */ - $subscriber = $this->objectManager->get(\Magento\Newsletter\Model\SubscriberFactory::class)->create(); - $this->assertEmpty($subscriber->getId()); - $subscriber->loadByCustomerId($customerId); - $this->assertNotEmpty($subscriber->getId()); - $this->assertEquals(3, $subscriber->getStatus()); - - /** - * Check that success message is set - */ - $this->assertSessionMessages( - $this->equalTo(['You saved the customer.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); - } - - /** - * Ensure that an email is sent during save action - * - * @magentoConfigFixture current_store customer/account_information/change_email_template change_email_template - * @magentoConfigFixture current_store customer/password/forgot_email_identity support - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testSaveActionExistingCustomerChangeEmail() - { - $customerId = 1; - $newEmail = 'newcustomer@example.com'; - $transportBuilderMock = $this->prepareEmailMock( - 2, - 'change_email_template', - [ - 'name' => 'CustomerSupport', - 'email' => 'support@example.com', - ], - $customerId, - $newEmail - ); - $this->addEmailMockToClass($transportBuilderMock, EmailNotification::class); - $post = [ - 'customer' => [ - 'entity_id' => $customerId, - 'middlename' => 'test middlename', - 'group_id' => 1, - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => $newEmail, - 'new_password' => 'auto', - 'sendemail_store_id' => '1', - 'sendemail' => '1', - 'created_at' => '2000-01-01 00:00:00', - 'default_shipping' => '_item1', - 'default_billing' => 1, - ] - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->getRequest()->setParam('id', 1); - $this->dispatch('backend/customer/index/save'); - - /** - * Check that no errors were generated and set to session - */ - $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); + $this->_objectManager->get(Session::class)->getMessages(true); } /** @@ -265,83 +106,6 @@ public function testInlineEditChangeEmail() $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); } - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testSaveActionCoreException() - { - $post = [ - 'customer' => [ - 'middlename' => 'test middlename', - 'group_id' => 1, - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'customer@example.com', - 'password' => 'password', - ], - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - /* - * Check that error message is set - */ - $this->assertSessionMessages( - $this->equalTo(['A customer with the same email address already exists in an associated website.']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - $this->assertArraySubset( - $post, - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getCustomerFormData() - ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new/key/')); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testSaveActionCoreExceptionFormatFormData() - { - $post = [ - 'customer' => [ - 'middlename' => 'test middlename', - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'customer@example.com', - 'dob' => '12/3/1996', - ], - ]; - $postCustomerFormatted = [ - 'middlename' => 'test middlename', - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'customer@example.com', - 'dob' => '1996-12-03', - ]; - - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - /* - * Check that error message is set - */ - $this->assertSessionMessages( - $this->equalTo(['A customer with the same email address already exists in an associated website.']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - - $customerFormData = Bootstrap::getObjectManager() - ->get(\Magento\Backend\Model\Session::class) - ->getCustomerFormData(); - $this->assertEquals( - $postCustomerFormatted, - $customerFormData['customer'], - 'Customer form data should be formatted' - ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new/key/')); - } - /** * @magentoDataFixture Magento/Customer/_files/customer_sample.php */ @@ -390,42 +154,6 @@ public function te1stNewActionWithCustomerData() $this->testNewAction(); } - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testDeleteAction() - { - $this->getRequest()->setParam('id', 1); - $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()); - - $this->getRequest()->setMethod(\Zend\Http\Request::METHOD_POST); - - $this->dispatch('backend/customer/index/delete'); - $this->assertRedirect($this->stringContains('customer/index')); - $this->assertSessionMessages( - $this->equalTo(['You deleted the customer.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testNotExistingCustomerDeleteAction() - { - $this->getRequest()->setParam('id', 2); - $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()); - - $this->getRequest()->setMethod(\Zend\Http\Request::METHOD_POST); - - $this->dispatch('backend/customer/index/delete'); - $this->assertRedirect($this->stringContains('customer/index')); - $this->assertSessionMessages( - $this->equalTo(['No such entity with customerId = 2']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - } - /** * @magentoDataFixture Magento/Customer/_files/customer_sample.php */ @@ -437,63 +165,6 @@ public function testCartAction() $this->assertContains('<div id="customer_cart_grid1"', $body); } - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDataFixture Magento/Customer/_files/customer_address.php - */ - public function testValidateCustomerWithAddressSuccess() - { - $customerData = [ - 'customer' => [ - 'entity_id' => '1', - 'middlename' => 'new middlename', - 'group_id' => 1, - 'website_id' => 1, - 'firstname' => 'new firstname', - 'lastname' => 'new lastname', - 'email' => 'example@domain.com', - 'default_shipping' => '_item1', - 'new_password' => 'auto', - 'sendemail_store_id' => '1', - 'sendemail' => '1', - ], - 'address' => [ - '_item1' => [ - 'firstname' => 'update firstname', - 'lastname' => 'update lastname', - 'street' => ['update street'], - 'city' => 'update city', - 'country_id' => 'US', - 'region_id' => 10, - 'postcode' => '01001', - 'telephone' => '+7000000001', - ], - '_template_' => [ - 'firstname' => '', - 'lastname' => '', - 'street' => [], - 'city' => '', - 'country_id' => 'US', - 'postcode' => '', - 'telephone' => '', - ], - ], - ]; - /** - * set customer data - */ - $this->getRequest()->setParams($customerData)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/validate'); - $body = $this->getResponse()->getBody(); - - /** - * Check that no errors were generated and set to session - */ - $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); - - $this->assertEquals('{"error":0}', $body); - } - /** * @magentoDbIsolation enabled */ diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountTest.php index 03473e9247c51..8be3dfc10d86e 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/CreateAccountTest.php @@ -8,22 +8,31 @@ namespace Magento\Customer\Model\AccountManagement; use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Customer\Model\Customer; +use Magento\Customer\Model\CustomerFactory; use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\Api\SimpleDataObjectConverter; +use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Math\Random; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Validator\Exception; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; use PHPUnit\Framework\TestCase; /** * Tests for customer creation via customer account management service. * - * @magentoAppArea frontend * @magentoDbIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CreateAccountTest extends TestCase { @@ -56,6 +65,41 @@ class CreateAccountTest extends TestCase 'lastname' => 'Last name', ]; + /** + * @var TransportBuilderMock + */ + private $transportBuilderMock; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @var ExtensibleDataObjectConverter + */ + private $extensibleDataObjectConverter; + + /** + * @var CustomerFactory + */ + private $customerModelFactory; + + /** + * @var Random + */ + private $random; + + /** + * @var EncryptorInterface + */ + private $encryptor; + /** * @inheritdoc */ @@ -65,6 +109,13 @@ protected function setUp() $this->accountManagement = $this->objectManager->get(AccountManagementInterface::class); $this->customerFactory = $this->objectManager->get(CustomerInterfaceFactory::class); $this->dataObjectHelper = $this->objectManager->create(DataObjectHelper::class); + $this->transportBuilderMock = $this->objectManager->get(TransportBuilderMock::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + $this->customerRepository = $this->objectManager->get(CustomerRepositoryInterface::class); + $this->extensibleDataObjectConverter = $this->objectManager->get(ExtensibleDataObjectConverter::class); + $this->customerModelFactory = $this->objectManager->get(CustomerFactory::class); + $this->random = $this->objectManager->get(Random::class); + $this->encryptor = $this->objectManager->get(EncryptorInterface::class); parent::setUp(); } @@ -82,9 +133,7 @@ public function testCreateAccountWithInvalidFields( string $errorType, array $errorMessage ): void { - $data = array_merge($this->defaultCustomerData, $customerData); - $customerEntity = $this->customerFactory->create(); - $this->dataObjectHelper->populateWithArray($customerEntity, $data, CustomerInterface::class); + $customerEntity = $this->populateCustomerEntity($this->defaultCustomerData, $customerData); $this->expectException($errorType); $this->expectExceptionMessage((string)__(...$errorMessage)); $this->accountManagement->createAccount($customerEntity, $password); @@ -156,7 +205,300 @@ public function createInvalidAccountDataProvider(): array 'The password can\'t be the same as the email address. Create a new password and try again.', ], ], + 'send_email_store_id_not_match_website' => [ + 'customer_data' => [ + CustomerInterface::WEBSITE_ID => 1, + CustomerInterface::STORE_ID => 5, + ], + 'password' => '_aPassword1', + 'error_type' => LocalizedException::class, + 'error_message' => [ + 'The store view is not in the associated website.', + ], + ], + ]; + } + + /** + * Assert that when you create customer account via admin, link with "set password" is send to customer email. + * + * @return void + */ + public function testSendEmailWithSetPasswordLink(): void + { + $customerEntity = $this->populateCustomerEntity($this->defaultCustomerData); + $newCustomerEntity = $this->accountManagement->createAccount($customerEntity); + $mailTemplate = $this->transportBuilderMock->getSentMessage()->getBody()->getParts()[0]->getRawContent(); + + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf("//a[contains(@href, 'customer/account/createPassword/?id=%s')]", $newCustomerEntity->getId()), + $mailTemplate + ), + 'Password creation link was not found.' + ); + } + + /** + * @magentoDataFixture Magento/Store/_files/second_website_with_two_stores.php + * @return void + */ + public function testCreateCustomerOnSecondWebsite(): void + { + $customerData = [ + CustomerInterface::WEBSITE_ID => $this->storeManager->getWebsite('test')->getId(), + CustomerInterface::STORE_ID => $this->storeManager->getStore('fixture_third_store')->getId(), + ]; + $expectedCustomerData = array_merge($this->defaultCustomerData, $customerData); + $customerEntity = $this->populateCustomerEntity($this->defaultCustomerData, $customerData); + $savedCustomerEntity = $this->accountManagement->createAccount($customerEntity); + + $this->assertNotNull($savedCustomerEntity->getId()); + $this->assertCustomerData($savedCustomerEntity, $expectedCustomerData); + } + + /** + * @return void + */ + public function testCreateNewCustomerWithPasswordHash(): void + { + $customerData = $expectedCustomerData = [ + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::STORE_ID => 1, + CustomerInterface::FIRSTNAME => 'Tester', + CustomerInterface::LASTNAME => 'McTest', + CustomerInterface::GROUP_ID => 1, + ]; + $newCustomerEntity = $this->populateCustomerEntity($customerData); + $password = $this->random->getRandomString(8); + $passwordHash = $this->encryptor->getHash($password, true); + $savedCustomer = $this->accountManagement->createAccountWithPasswordHash( + $newCustomerEntity, + $passwordHash + ); + $this->assertNotNull($savedCustomer->getId()); + $this->assertCustomerData($savedCustomer, $expectedCustomerData); + $this->assertEmpty($savedCustomer->getSuffix()); + $this->assertEquals( + $savedCustomer->getId(), + $this->accountManagement->authenticate($customerData[CustomerInterface::EMAIL], $password)->getId() + ); + } + + /** + * Customer has two addresses one of it is allowed in website and second is not + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoDataFixture Magento/Store/_files/websites_different_countries.php + * @magentoConfigFixture fixture_second_store_store general/country/allow UA + * @return void + */ + public function testCreateNewCustomerWithPasswordHashWithNotAllowedCountry(): void + { + $customerId = 1; + $allowedCountryIdForSecondWebsite = 'UA'; + $store = $this->storeManager->getStore('fixture_second_store'); + $customerData = $this->customerRepository->getById($customerId); + $customerData->getAddresses()[1]->setRegion(null)->setCountryId($allowedCountryIdForSecondWebsite) + ->setRegionId(null); + $customerData->setStoreId($store->getId())->setWebsiteId($store->getWebsiteId())->setId(null); + $password = $this->random->getRandomString(8); + $passwordHash = $this->encryptor->getHash($password, true); + $savedCustomer = $this->accountManagement->createAccountWithPasswordHash( + $customerData, + $passwordHash + ); + $this->assertCount( + 1, + $savedCustomer->getAddresses(), + 'The wrong address quantity was saved' + ); + $this->assertSame( + 'UA', + $savedCustomer->getAddresses()[0]->getCountryId(), + 'The address with the disallowed country was saved' + ); + } + + /** + * @magentoAppArea frontend + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testCreateNoExistingCustomer(): void + { + $existingCustId = 1; + $existingCustomer = $this->customerRepository->getById($existingCustId); + $customerData = $expectedCustomerData = [ + CustomerInterface::EMAIL => 'savecustomer@example.com', + CustomerInterface::FIRSTNAME => 'Firstsave', + CustomerInterface::LASTNAME => 'Lastsave', + CustomerInterface::ID => null, ]; + unset($expectedCustomerData[CustomerInterface::ID]); + $customerEntity = $this->populateCustomerEntity($existingCustomer->__toArray(), $customerData); + + $customerAfter = $this->accountManagement->createAccount($customerEntity, '_aPassword1'); + $this->assertGreaterThan(0, $customerAfter->getId()); + $this->assertCustomerData($customerAfter, $expectedCustomerData); + $this->accountManagement->authenticate( + $customerAfter->getEmail(), + '_aPassword1' + ); + $attributesBefore = $this->extensibleDataObjectConverter->toFlatArray( + $existingCustomer, + [], + CustomerInterface::class + ); + $attributesAfter = $this->extensibleDataObjectConverter->toFlatArray( + $customerAfter, + [], + CustomerInterface::class + ); + // ignore 'updated_at' + unset($attributesBefore['updated_at']); + unset($attributesAfter['updated_at']); + $inBeforeOnly = array_diff_assoc($attributesBefore, $attributesAfter); + $inAfterOnly = array_diff_assoc($attributesAfter, $attributesBefore); + $expectedInBefore = [ + 'email', + 'firstname', + 'id', + 'lastname', + ]; + sort($expectedInBefore); + $actualInBeforeOnly = array_keys($inBeforeOnly); + sort($actualInBeforeOnly); + $this->assertEquals($expectedInBefore, $actualInBeforeOnly); + $expectedInAfter = [ + 'created_in', + 'email', + 'firstname', + 'id', + 'lastname', + ]; + $actualInAfterOnly = array_keys($inAfterOnly); + foreach ($expectedInAfter as $item) { + $this->assertContains($item, $actualInAfterOnly); + } + } + + /** + * @return void + */ + public function testCreateCustomerInServiceVsInModel(): void + { + $password = '_aPassword1'; + $firstCustomerData = $secondCustomerData = [ + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::FIRSTNAME => 'Tester', + CustomerInterface::LASTNAME => 'McTest', + CustomerInterface::GROUP_ID => 1, + ]; + $secondCustomerData[CustomerInterface::EMAIL] = 'email2@example.com'; + + /** @var Customer $customerModel */ + $customerModel = $this->customerModelFactory->create(); + $customerModel->setData($firstCustomerData)->setPassword($password); + $customerModel->save(); + /** @var Customer $customerModel */ + $savedModel = $this->customerModelFactory->create()->load($customerModel->getId()); + $dataInModel = $savedModel->getData(); + $newCustomerEntity = $this->populateCustomerEntity($secondCustomerData); + + $customerData = $this->accountManagement->createAccount($newCustomerEntity, $password); + $this->assertNotNull($customerData->getId()); + $savedCustomer = $this->customerRepository->getById($customerData->getId()); + + /** @var SimpleDataObjectConverter $simpleDataObjectConverter */ + $simpleDataObjectConverter = $this->objectManager->get(SimpleDataObjectConverter::class); + + $dataInService = $simpleDataObjectConverter->toFlatArray( + $savedCustomer, + CustomerInterface::class + ); + $expectedDifferences = [ + 'created_at', + 'updated_at', + 'email', + 'is_active', + 'entity_id', + 'entity_type_id', + 'password_hash', + 'attribute_set_id', + 'disable_auto_group_change', + 'confirmation', + 'reward_update_notification', + 'reward_warning_notification', + ]; + foreach ($dataInModel as $key => $value) { + if (!in_array($key, $expectedDifferences)) { + if ($value === null) { + $this->assertArrayNotHasKey($key, $dataInService); + } elseif (isset($dataInService[$key])) { + $this->assertEquals($value, $dataInService[$key], 'Failed asserting value for ' . $key); + } + } + } + $this->assertEquals($secondCustomerData[CustomerInterface::EMAIL], $dataInService['email']); + $this->assertArrayNotHasKey('is_active', $dataInService); + $this->assertArrayNotHasKey('password_hash', $dataInService); + } + + /** + * @return void + */ + public function testCreateNewCustomer(): void + { + $customerData = $expectedCustomerData = [ + CustomerInterface::EMAIL => 'email@example.com', + CustomerInterface::STORE_ID => 1, + CustomerInterface::FIRSTNAME => 'Tester', + CustomerInterface::LASTNAME => 'McTest', + CustomerInterface::GROUP_ID => 1, + ]; + $newCustomerEntity = $this->populateCustomerEntity($customerData); + + $savedCustomer = $this->accountManagement->createAccount($newCustomerEntity, '_aPassword1'); + $this->assertNotNull($savedCustomer->getId()); + $this->assertCustomerData($savedCustomer, $expectedCustomerData); + $this->assertEmpty($savedCustomer->getSuffix()); + } + + /** + * @magentoAppArea frontend + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testCreateNewCustomerFromClone(): void + { + $existingCustId = 1; + $existingCustomer = $this->customerRepository->getById($existingCustId); + $customerEntity = $this->customerFactory->create(); + $this->dataObjectHelper->mergeDataObjects( + CustomerInterface::class, + $customerEntity, + $existingCustomer + ); + $customerData = $expectedCustomerData = [ + CustomerInterface::EMAIL => 'savecustomer@example.com', + CustomerInterface::FIRSTNAME => 'Firstsave', + CustomerInterface::LASTNAME => 'Lastsave', + CustomerInterface::ID => null, + ]; + unset($expectedCustomerData[CustomerInterface::ID]); + $customerEntity = $this->populateCustomerEntity($customerData, [], $customerEntity); + + $customer = $this->accountManagement->createAccount($customerEntity, '_aPassword1'); + $this->assertNotEmpty($customer->getId()); + $this->assertCustomerData($customer, $expectedCustomerData); + $this->accountManagement->authenticate( + $customer->getEmail(), + '_aPassword1', + true + ); } /** @@ -174,4 +516,52 @@ private function getRandomNumericString(int $length): string return $string; } + + /** + * Fill in customer entity using array of customer data and additional customer data. + * + * @param array $customerData + * @param array $additionalCustomerData + * @param CustomerInterface|null $customerEntity + * @return CustomerInterface + */ + private function populateCustomerEntity( + array $customerData, + array $additionalCustomerData = [], + ?CustomerInterface $customerEntity = null + ): CustomerInterface { + $customerEntity = $customerEntity ?? $this->customerFactory->create(); + $customerData = array_merge( + $customerData, + $additionalCustomerData + ); + $this->dataObjectHelper->populateWithArray( + $customerEntity, + $customerData, + CustomerInterface::class + ); + + return $customerEntity; + } + + /** + * Check that customer parameters match expected values. + * + * @param CustomerInterface $customer + * @param array $expectedData + * return void + */ + private function assertCustomerData( + CustomerInterface $customer, + array $expectedData + ): void { + $actualCustomerArray = $customer->__toArray(); + foreach ($expectedData as $key => $expectedValue) { + $this->assertEquals( + $expectedValue, + $actualCustomerArray[$key], + "Invalid expected value for $key field." + ); + } + } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/ValidateTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/ValidateTest.php new file mode 100644 index 0000000000000..8daa310d6dc03 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/ValidateTest.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\AccountManagement; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Tests for customer validation via customer account management service. + * + * @magentoDbIsolation enabled + */ +class ValidateTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var AccountManagementInterface */ + private $accountManagement; + + /** @var CustomerInterfaceFactory */ + private $customerFactory; + + /** @var DataObjectHelper */ + private $dataObjectHelper; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->accountManagement = $this->objectManager->get(AccountManagementInterface::class); + $this->customerFactory = $this->objectManager->get(CustomerInterfaceFactory::class); + $this->dataObjectHelper = $this->objectManager->get(DataObjectHelper::class); + parent::setUp(); + } + + /** + * Validate customer fields. + * + * @dataProvider validateFieldsProvider + * + * @param array $customerData + * @param array $expectedResults + * @return void + */ + public function testValidateFields( + array $customerData, + array $expectedResults + ): void { + $customerEntity = $this->customerFactory->create(); + $this->dataObjectHelper->populateWithArray( + $customerEntity, + $customerData, + CustomerInterface::class + ); + $validationResults = $this->accountManagement->validate($customerEntity); + $this->assertEquals( + $expectedResults, + [ + 'valid' => $validationResults->isValid(), + 'messages' => $validationResults->getMessages(), + ] + ); + } + + /** + * @return array + */ + public function validateFieldsProvider(): array + { + $attributeEmptyMessage = 'The "%1" attribute value is empty. Set the attribute and try again.'; + return [ + 'without_required_fields' => [ + 'customer_data' => [], + 'expectedResults' => [ + 'valid' => false, + 'messages' => [ + (string)__($attributeEmptyMessage, 'Associate to Website'), + (string)__($attributeEmptyMessage, 'Group'), + (string)__($attributeEmptyMessage, 'First Name'), + (string)__($attributeEmptyMessage, 'Last Name'), + (string)__($attributeEmptyMessage, 'Email'), + ], + ], + ], + 'with_required_fields' => [ + 'customer_data' => [ + CustomerInterface::WEBSITE_ID => 1, + CustomerInterface::GROUP_ID => 1, + CustomerInterface::FIRSTNAME => 'Jane', + CustomerInterface::LASTNAME => 'Doe', + CustomerInterface::EMAIL => 'janedoe' . uniqid() . '@example.com', + ], + 'expectedResults' => [ + 'valid' => true, + 'messages' => [], + ], + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php index 754c949747d61..e41d7d9a441c3 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php @@ -8,13 +8,11 @@ use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\AddressRepositoryInterface; -use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\AddressInterface; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\State\ExpiredException; use Magento\Framework\Reflection\DataObjectProcessor; -use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; /** @@ -30,9 +28,6 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase /** @var AccountManagementInterface */ private $accountManagement; - /** @var CustomerRepositoryInterface */ - private $customerRepository; - /** @var AddressRepositoryInterface needed to setup tests */ private $addressRepository; @@ -45,18 +40,9 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Customer\Api\Data\AddressInterfaceFactory */ private $addressFactory; - /** @var \Magento\Customer\Api\Data\CustomerInterfaceFactory */ - private $customerFactory; - /** @var DataObjectProcessor */ private $dataProcessor; - /** @var \Magento\Framework\Api\ExtensibleDataObjectConverter */ - private $extensibleDataObjectConverter; - - /** @var StoreManagerInterface */ - private $storeManager; - /** @var \Magento\Framework\Api\DataObjectHelper */ protected $dataObjectHelper; @@ -65,16 +51,10 @@ protected function setUp() $this->objectManager = Bootstrap::getObjectManager(); $this->accountManagement = $this->objectManager ->create(\Magento\Customer\Api\AccountManagementInterface::class); - $this->customerRepository = $this->objectManager - ->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); $this->addressRepository = $this->objectManager->create(\Magento\Customer\Api\AddressRepositoryInterface::class); $this->addressFactory = $this->objectManager->create(\Magento\Customer\Api\Data\AddressInterfaceFactory::class); - $this->customerFactory = $this->objectManager->create( - \Magento\Customer\Api\Data\CustomerInterfaceFactory::class - ); - $this->dataObjectHelper = $this->objectManager->create(\Magento\Framework\Api\DataObjectHelper::class); $regionFactory = $this->objectManager->create(\Magento\Customer\Api\Data\RegionInterfaceFactory::class); $address = $this->addressFactory->create(); @@ -115,12 +95,6 @@ protected function setUp() $this->dataProcessor = $this->objectManager ->create(\Magento\Framework\Reflection\DataObjectProcessor::class); - - $this->extensibleDataObjectConverter = $this->objectManager - ->create(\Magento\Framework\Api\ExtensibleDataObjectConverter::class); - - $this->storeManager = $this->objectManager - ->create(StoreManagerInterface::class); } /** @@ -620,310 +594,6 @@ public function testResendConfirmationNotNeeded() $this->accountManagement->resendConfirmation('customer@example.com', 1); } - /** - * @magentoDbIsolation enabled - */ - public function testCreateCustomerException() - { - $customerEntity = $this->customerFactory->create(); - - try { - $this->accountManagement->createAccount($customerEntity); - $this->fail('Expected exception not thrown'); - } catch (InputException $ie) { - $this->assertEquals('The customer email is missing. Enter and try again.', $ie->getMessage()); - } - } - - /** - * @magentoAppArea frontend - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDbIsolation enabled - */ - public function testCreateNonexistingCustomer() - { - $existingCustId = 1; - $existingCustomer = $this->customerRepository->getById($existingCustId); - - $email = 'savecustomer@example.com'; - $firstName = 'Firstsave'; - $lastName = 'Lastsave'; - $customerData = array_merge( - $existingCustomer->__toArray(), - [ - 'email' => $email, - 'firstname' => $firstName, - 'lastname' => $lastName, - 'id' => null - ] - ); - $customerEntity = $this->customerFactory->create(); - $this->dataObjectHelper->populateWithArray( - $customerEntity, - $customerData, - \Magento\Customer\Api\Data\CustomerInterface::class - ); - - $customerAfter = $this->accountManagement->createAccount($customerEntity, '_aPassword1'); - $this->assertGreaterThan(0, $customerAfter->getId()); - $this->assertEquals($email, $customerAfter->getEmail()); - $this->assertEquals($firstName, $customerAfter->getFirstname()); - $this->assertEquals($lastName, $customerAfter->getLastname()); - $this->accountManagement->authenticate( - $customerAfter->getEmail(), - '_aPassword1' - ); - $attributesBefore = $this->extensibleDataObjectConverter->toFlatArray( - $existingCustomer, - [], - \Magento\Customer\Api\Data\CustomerInterface::class - ); - $attributesAfter = $this->extensibleDataObjectConverter->toFlatArray( - $customerAfter, - [], - \Magento\Customer\Api\Data\CustomerInterface::class - ); - // ignore 'updated_at' - unset($attributesBefore['updated_at']); - unset($attributesAfter['updated_at']); - $inBeforeOnly = array_diff_assoc($attributesBefore, $attributesAfter); - $inAfterOnly = array_diff_assoc($attributesAfter, $attributesBefore); - $expectedInBefore = [ - 'email', - 'firstname', - 'id', - 'lastname', - ]; - sort($expectedInBefore); - $actualInBeforeOnly = array_keys($inBeforeOnly); - sort($actualInBeforeOnly); - $this->assertEquals($expectedInBefore, $actualInBeforeOnly); - $expectedInAfter = [ - 'created_in', - 'email', - 'firstname', - 'id', - 'lastname', - ]; - $actualInAfterOnly = array_keys($inAfterOnly); - foreach ($expectedInAfter as $item) { - $this->assertContains($item, $actualInAfterOnly); - } - } - - /** - * @magentoDbIsolation enabled - */ - public function testCreateCustomerInServiceVsInModel() - { - $email = 'email@example.com'; - $email2 = 'email2@example.com'; - $firstname = 'Tester'; - $lastname = 'McTest'; - $groupId = 1; - $password = '_aPassword1'; - - /** @var \Magento\Customer\Model\Customer $customerModel */ - $customerModel = $this->objectManager->create(\Magento\Customer\Model\CustomerFactory::class)->create(); - $customerModel->setEmail($email) - ->setFirstname($firstname) - ->setLastname($lastname) - ->setGroupId($groupId) - ->setPassword($password); - $customerModel->save(); - /** @var \Magento\Customer\Model\Customer $customerModel */ - $savedModel = $this->objectManager - ->create(\Magento\Customer\Model\CustomerFactory::class) - ->create() - ->load($customerModel->getId()); - $dataInModel = $savedModel->getData(); - - $newCustomerEntity = $this->customerFactory->create() - ->setEmail($email2) - ->setFirstname($firstname) - ->setLastname($lastname) - ->setGroupId($groupId); - $customerData = $this->accountManagement->createAccount($newCustomerEntity, $password); - $this->assertNotNull($customerData->getId()); - $savedCustomer = $this->customerRepository->getById($customerData->getId()); - - /** @var \Magento\Framework\Api\SimpleDataObjectConverter $simpleDataObjectConverter */ - $simpleDataObjectConverter = Bootstrap::getObjectManager() - ->get(\Magento\Framework\Api\SimpleDataObjectConverter::class); - - $dataInService = $simpleDataObjectConverter->toFlatArray( - $savedCustomer, - \Magento\Customer\Api\Data\CustomerInterface::class - ); - $expectedDifferences = [ - 'created_at', - 'updated_at', - 'email', - 'is_active', - 'entity_id', - 'entity_type_id', - 'password_hash', - 'attribute_set_id', - 'disable_auto_group_change', - 'confirmation', - 'reward_update_notification', - 'reward_warning_notification', - ]; - foreach ($dataInModel as $key => $value) { - if (!in_array($key, $expectedDifferences)) { - if ($value === null) { - $this->assertArrayNotHasKey($key, $dataInService); - } else { - if (isset($dataInService[$key])) { - $this->assertEquals($value, $dataInService[$key], 'Failed asserting value for ' . $key); - } - } - } - } - $this->assertEquals($email2, $dataInService['email']); - $this->assertArrayNotHasKey('is_active', $dataInService); - $this->assertArrayNotHasKey('password_hash', $dataInService); - } - - /** - * @magentoDbIsolation enabled - */ - public function testCreateNewCustomer() - { - $email = 'email@example.com'; - $storeId = 1; - $firstname = 'Tester'; - $lastname = 'McTest'; - $groupId = 1; - - $newCustomerEntity = $this->customerFactory->create() - ->setStoreId($storeId) - ->setEmail($email) - ->setFirstname($firstname) - ->setLastname($lastname) - ->setGroupId($groupId); - $savedCustomer = $this->accountManagement->createAccount($newCustomerEntity, '_aPassword1'); - $this->assertNotNull($savedCustomer->getId()); - $this->assertEquals($email, $savedCustomer->getEmail()); - $this->assertEquals($storeId, $savedCustomer->getStoreId()); - $this->assertEquals($firstname, $savedCustomer->getFirstname()); - $this->assertEquals($lastname, $savedCustomer->getLastname()); - $this->assertEquals($groupId, $savedCustomer->getGroupId()); - $this->assertTrue(!$savedCustomer->getSuffix()); - } - - /** - * @magentoDbIsolation enabled - */ - public function testCreateNewCustomerWithPasswordHash() - { - $email = 'email@example.com'; - $storeId = 1; - $firstname = 'Tester'; - $lastname = 'McTest'; - $groupId = 1; - - $newCustomerEntity = $this->customerFactory->create() - ->setStoreId($storeId) - ->setEmail($email) - ->setFirstname($firstname) - ->setLastname($lastname) - ->setGroupId($groupId); - /** @var \Magento\Framework\Math\Random $mathRandom */ - $password = $this->objectManager->get(\Magento\Framework\Math\Random::class)->getRandomString(8); - /** @var \Magento\Framework\Encryption\EncryptorInterface $encryptor */ - $encryptor = $this->objectManager->get(\Magento\Framework\Encryption\EncryptorInterface::class); - $passwordHash = $encryptor->getHash($password, true); - $savedCustomer = $this->accountManagement->createAccountWithPasswordHash( - $newCustomerEntity, - $passwordHash - ); - $this->assertNotNull($savedCustomer->getId()); - $this->assertEquals($email, $savedCustomer->getEmail()); - $this->assertEquals($storeId, $savedCustomer->getStoreId()); - $this->assertEquals($firstname, $savedCustomer->getFirstname()); - $this->assertEquals($lastname, $savedCustomer->getLastname()); - $this->assertEquals($groupId, $savedCustomer->getGroupId()); - $this->assertTrue(!$savedCustomer->getSuffix()); - $this->assertEquals( - $savedCustomer->getId(), - $this->accountManagement->authenticate($email, $password)->getId() - ); - } - - /** - * Customer has two addresses one of it is allowed in website and second is not - * - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php - * @magentoDataFixture Magento/Store/_files/websites_different_countries.php - * @magentoConfigFixture fixture_second_store_store general/country/allow UA - * @return void - */ - public function testCreateNewCustomerWithPasswordHashWithNotAllowedCountry() - { - $customerId = 1; - $allowedCountryIdForSecondWebsite = 'UA'; - $store = $this->storeManager->getStore('fixture_second_store'); - $customerData = $this->customerRepository->getById($customerId); - $customerData->getAddresses()[1]->setRegion(null)->setCountryId($allowedCountryIdForSecondWebsite) - ->setRegionId(null); - $customerData->setStoreId($store->getId())->setWebsiteId($store->getWebsiteId())->setId(null); - $encryptor = $this->objectManager->get(\Magento\Framework\Encryption\EncryptorInterface::class); - /** @var \Magento\Framework\Math\Random $mathRandom */ - $password = $this->objectManager->get(\Magento\Framework\Math\Random::class)->getRandomString(8); - $passwordHash = $encryptor->getHash($password, true); - $savedCustomer = $this->accountManagement->createAccountWithPasswordHash( - $customerData, - $passwordHash - ); - $this->assertCount( - 1, - $savedCustomer->getAddresses(), - 'The wrong address quantity was saved' - ); - $this->assertSame( - 'UA', - $savedCustomer->getAddresses()[0]->getCountryId(), - 'The address with the disallowed country was saved' - ); - } - - /** - * @magentoAppArea frontend - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testCreateNewCustomerFromClone() - { - $email = 'savecustomer@example.com'; - $firstName = 'Firstsave'; - $lastname = 'Lastsave'; - - $existingCustId = 1; - $existingCustomer = $this->customerRepository->getById($existingCustId); - $customerEntity = $this->customerFactory->create(); - $this->dataObjectHelper->mergeDataObjects( - \Magento\Customer\Api\Data\CustomerInterface::class, - $customerEntity, - $existingCustomer - ); - $customerEntity->setEmail($email) - ->setFirstname($firstName) - ->setLastname($lastname) - ->setId(null); - - $customer = $this->accountManagement->createAccount($customerEntity, '_aPassword1'); - $this->assertNotEmpty($customer->getId()); - $this->assertEquals($email, $customer->getEmail()); - $this->assertEquals($firstName, $customer->getFirstname()); - $this->assertEquals($lastname, $customer->getLastname()); - $this->accountManagement->authenticate( - $customer->getEmail(), - '_aPassword1', - true - ); - } - /** * @magentoDataFixture Magento/Customer/_files/customer.php */ From 36722d9a6994e00904727730030488d539544e03 Mon Sep 17 00:00:00 2001 From: Joridos <joridoss@gmail.com> Date: Fri, 7 Feb 2020 04:31:50 +0200 Subject: [PATCH 205/369] #26698 Fix region getId() on NULL collection in Paypal Nvp API --- app/code/Magento/Paypal/Model/Api/Nvp.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Paypal/Model/Api/Nvp.php b/app/code/Magento/Paypal/Model/Api/Nvp.php index 2ec88df492fb9..41be1e4aaad37 100644 --- a/app/code/Magento/Paypal/Model/Api/Nvp.php +++ b/app/code/Magento/Paypal/Model/Api/Nvp.php @@ -1519,10 +1519,13 @@ protected function _applyStreetAndRegionWorkarounds(DataObject $address) )->setPageSize( 1 ); - $regionItems = $regions->getItems(); - $region = array_shift($regionItems); - $address->setRegionId($region->getId()); - $address->setExportedKeys(array_merge($address->getExportedKeys(), ['region_id'])); + + if ($regions->count()) { + $regionItems = $regions->getItems(); + $region = array_shift($regionItems); + $address->setRegionId($region->getId()); + $address->setExportedKeys(array_merge($address->getExportedKeys(), ['region_id'])); + } } } From 8a73e53ee369b24b95e3c94bc379ce48856ee8ca Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Fri, 6 Mar 2020 14:05:06 +0200 Subject: [PATCH 206/369] MC-31196: [2.4.0] Paypal issue with region on 2.3.4 --- app/code/Magento/Paypal/Model/Api/Nvp.php | 20 ++++++------- .../Paypal/Model/Express/CheckoutTest.php | 30 +++++++++++++++---- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/Paypal/Model/Api/Nvp.php b/app/code/Magento/Paypal/Model/Api/Nvp.php index 41be1e4aaad37..9e4f7693f4bfb 100644 --- a/app/code/Magento/Paypal/Model/Api/Nvp.php +++ b/app/code/Magento/Paypal/Model/Api/Nvp.php @@ -1512,13 +1512,11 @@ protected function _applyStreetAndRegionWorkarounds(DataObject $address) } // attempt to fetch region_id from directory if ($address->getCountryId() && $address->getRegion()) { - $regions = $this->_countryFactory->create()->loadByCode( - $address->getCountryId() - )->getRegionCollection()->addRegionCodeOrNameFilter( - $address->getRegion() - )->setPageSize( - 1 - ); + $regions = $this->_countryFactory->create() + ->loadByCode($address->getCountryId()) + ->getRegionCollection() + ->addRegionCodeOrNameFilter($address->getRegion()) + ->setPageSize(1); if ($regions->count()) { $regionItems = $regions->getItems(); @@ -1627,7 +1625,7 @@ protected function _filterPeriodUnit($value) case 'year': return 'Year'; default: - break; + return ''; } } @@ -1656,7 +1654,7 @@ protected function _filterBillingAgreementStatus($value) case 'active': return 'Active'; default: - break; + return ''; } } @@ -1697,7 +1695,7 @@ protected function _filterPaymentStatusFromNvpToInfo($value) case 'Voided': return \Magento\Paypal\Model\Info::PAYMENTSTATUS_VOIDED; default: - break; + return null; } } @@ -1715,7 +1713,7 @@ protected function _filterPaymentReviewAction($value) case \Magento\Paypal\Model\Pro::PAYMENT_REVIEW_DENY: return 'Deny'; default: - break; + return null; } } diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php index 8d6e4dbf30ae5..23dc60d347427 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php @@ -6,6 +6,7 @@ namespace Magento\Paypal\Model\Express; use Magento\Checkout\Model\Type\Onepage; +use Magento\Directory\Model\CountryFactory; use Magento\Framework\ObjectManagerInterface; use Magento\Paypal\Model\Api\Nvp; use Magento\Paypal\Model\Api\Type\Factory; @@ -17,8 +18,6 @@ use Magento\TestFramework\Helper\Bootstrap; /** - * Class CheckoutTest - * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CheckoutTest extends \PHPUnit\Framework\TestCase @@ -634,10 +633,6 @@ public function testGuestReturnFromPaypal() ->setMethods(['call', 'getExportedShippingAddress', 'getExportedBillingAddress']) ->getMock(); - $api->expects($this->any()) - ->method('call') - ->will($this->returnValue([])); - $apiTypeFactory->expects($this->any()) ->method('create') ->will($this->returnValue($api)); @@ -652,6 +647,14 @@ public function testGuestReturnFromPaypal() ->method('getExportedShippingAddress') ->will($this->returnValue($exportedShippingAddress)); + $this->addCountryFactory($api); + $data = [ + 'COUNTRYCODE' => $quote->getShippingAddress()->getCountryId(), + 'STATE' => 'unknown' + ]; + $api->method('call') + ->willReturn($data); + $paypalInfo->expects($this->once()) ->method('importToPayment') ->with($api, $quote->getPayment()); @@ -710,4 +713,19 @@ private function getFixtureQuote(): Quote return $quoteCollection->getLastItem(); } + + /** + * Adds countryFactory to a mock. + * + * @param \PHPUnit\Framework\MockObject\MockObject $api + * @throws \ReflectionException + * @return void + */ + private function addCountryFactory(\PHPUnit\Framework\MockObject\MockObject $api): void + { + $reflection = new \ReflectionClass($api); + $property = $reflection->getProperty('_countryFactory'); + $property->setAccessible(true); + $property->setValue($api, $this->objectManager->get(CountryFactory::class)); + } } From eac1a534568c49b3eaee622d183ac7cddb1f0ae5 Mon Sep 17 00:00:00 2001 From: Roman Zhupanyn <roma.dj.elf@gmail.com> Date: Fri, 6 Mar 2020 14:15:10 +0200 Subject: [PATCH 207/369] MC-32130: Admin: Reset customer password on edit customer page in admin --- .../AccountManagement/ResetPasswordTest.php | 147 ++++++++++++++++++ .../Customer/Model/AccountManagementTest.php | 67 -------- 2 files changed, 147 insertions(+), 67 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/ResetPasswordTest.php diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/ResetPasswordTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/ResetPasswordTest.php new file mode 100644 index 0000000000000..012838ebdf697 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagement/ResetPasswordTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\AccountManagement; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Model\AccountManagement; +use Magento\Customer\Model\CustomerRegistry; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\ObjectManagerInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use PHPUnit\Framework\TestCase; + +/** + * Tests for customer password reset via customer account management service. + * + * @magentoDbIsolation enabled + */ +class ResetPasswordTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var AccountManagementInterface */ + private $accountManagement; + + /** @var TransportBuilderMock*/ + private $transportBuilderMock; + + /** @var CustomerRegistry */ + private $customerRegistry; + + /** @var StoreManagerInterface */ + private $storeManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->accountManagement = $this->objectManager->get(AccountManagementInterface::class); + $this->transportBuilderMock = $this->objectManager->get(TransportBuilderMock::class); + $this->customerRegistry = $this->objectManager->get(CustomerRegistry::class); + $this->storeManager = $this->objectManager->get(StoreManagerInterface::class); + parent::setUp(); + } + + /** + * Assert that when you reset customer password via admin, link with "Set a New Password" is send to customer email. + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testSendEmailWithSetNewPasswordLink(): void + { + $this->accountManagement->initiatePasswordReset( + 'customer@example.com', + AccountManagement::EMAIL_REMINDER, + 1 + ); + $customerSecure = $this->customerRegistry->retrieveSecureData(1); + $mailTemplate = $this->transportBuilderMock->getSentMessage()->getBody()->getParts()[0]->getRawContent(); + + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + '//a[contains(@href, \'customer/account/createPassword/?id=%1$d&token=%2$s\')]', + $customerSecure->getId(), + $customerSecure->getRpToken() + ), + $mailTemplate + ), + 'Reset password creation link was not found.' + ); + } + + /** + * @magentoAppArea frontend + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testSendPasswordResetLink(): void + { + $email = 'customer@example.com'; + $websiteId = (int)$this->storeManager->getWebsite('base')->getId(); + + $this->accountManagement->initiatePasswordReset($email, AccountManagement::EMAIL_RESET, $websiteId); + } + + /** + * @magentoAppArea frontend + * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void + */ + public function testSendPasswordResetLinkDefaultWebsite(): void + { + $email = 'customer@example.com'; + + $this->accountManagement->initiatePasswordReset($email, AccountManagement::EMAIL_RESET); + } + + /** + * @magentoAppArea frontend + * @dataProvider passwordResetErrorsProvider + * @magentoDataFixture Magento/Customer/_files/customer.php + * @param string $email + * @param int|null $websiteId + * @return void + */ + public function testPasswordResetErrors(string $email, ?int $websiteId = null): void + { + $websiteId = $websiteId ?? (int)$this->storeManager->getWebsite('base')->getId(); + $this->expectExceptionObject( + NoSuchEntityException::doubleField('email', $email, 'websiteId', $websiteId) + ); + $this->accountManagement->initiatePasswordReset( + $email, + AccountManagement::EMAIL_RESET, + $websiteId + ); + } + + /** + * @return array + */ + public function passwordResetErrorsProvider(): array + { + return [ + 'wrong_email' => [ + 'email' => 'foo@example.com', + ], + 'wrong_website_id' => [ + 'email' => 'customer@example.com', + 'website_id' => 0, + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php index 754c949747d61..9d78a6827f7ad 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php @@ -387,73 +387,6 @@ public function testValidateResetPasswordLinkTokenAmbiguous() $this->accountManagement->validateResetPasswordLinkToken(null, $token); } - /** - * @magentoAppArea frontend - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testSendPasswordResetLink() - { - $email = 'customer@example.com'; - - $this->accountManagement->initiatePasswordReset($email, AccountManagement::EMAIL_RESET, 1); - } - - /** - * @magentoAppArea frontend - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testSendPasswordResetLinkDefaultWebsite() - { - $email = 'customer@example.com'; - - $this->accountManagement->initiatePasswordReset($email, AccountManagement::EMAIL_RESET); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - * - */ - public function testSendPasswordResetLinkBadEmailOrWebsite() - { - $email = 'foo@example.com'; - - try { - $this->accountManagement->initiatePasswordReset( - $email, - AccountManagement::EMAIL_RESET, - 0 - ); - $this->fail('Expected exception not thrown.'); - } catch (NoSuchEntityException $e) { - $expectedParams = [ - 'fieldName' => 'email', - 'fieldValue' => $email, - 'field2Name' => 'websiteId', - 'field2Value' => 0, - ]; - $this->assertEquals($expectedParams, $e->getParameters()); - } - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - */ - public function testSendPasswordResetLinkBadEmailDefaultWebsite() - { - $email = 'foo@example.com'; - - try { - $this->accountManagement->initiatePasswordReset( - $email, - AccountManagement::EMAIL_RESET - ); - $this->fail('Expected exception not thrown.'); - } catch (NoSuchEntityException $nsee) { - // App area is frontend, so we expect websiteId of 1. - $this->assertEquals('No such entity with email = foo@example.com, websiteId = 1', $nsee->getMessage()); - } - } - /** * @magentoDataFixture Magento/Customer/_files/customer.php */ From 2134efd809aab5a9b58e90be395993359de784ef Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Fri, 6 Mar 2020 15:41:03 +0200 Subject: [PATCH 208/369] MC-31838: Product in Websites checkbox of New Product page isn't automatically checked for restricted admin user --- .../Product/Form/Modifier/WebsitesTest.php | 19 ++++++++---- .../Product/Form/Modifier/Websites.php | 30 +++++++++++++++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php index 829dc4824416d..2f8545c56e71e 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php @@ -6,17 +6,16 @@ namespace Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Websites; -use Magento\Store\Api\WebsiteRepositoryInterface; use Magento\Store\Api\GroupRepositoryInterface; use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\Group; +use Magento\Store\Model\Store as StoreView; use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\Website; -use Magento\Store\Model\Store as StoreView; -use Magento\Store\Model\Group; /** - * Class WebsitesTest + * Class WebsitesTest test the meta data and website data for different websites * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -111,7 +110,7 @@ protected function setUp() ->method('getWebsiteIds') ->willReturn($this->assignedWebsites); $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->setMethods(['isSingleStoreMode', 'getWesites']) + ->setMethods(['isSingleStoreMode', 'getWebsites']) ->getMockForAbstractClass(); $this->storeManagerMock->method('getWebsites') ->willReturn([$this->websiteMock, $this->secondWebsiteMock]); @@ -182,6 +181,14 @@ public function testModifyMeta() $this->assertTrue(isset($meta['websites']['children'][self::SECOND_WEBSITE_ID])); $this->assertTrue(isset($meta['websites']['children'][self::WEBSITE_ID])); $this->assertTrue(isset($meta['websites']['children']['copy_to_stores.' . self::WEBSITE_ID])); + $this->assertEquals( + $meta['websites']['children'][self::SECOND_WEBSITE_ID]['arguments']['data']['config']['value'], + (string) self::SECOND_WEBSITE_ID + ); + $this->assertEquals( + $meta['websites']['children'][self::WEBSITE_ID]['arguments']['data']['config']['value'], + "0" + ); } /** diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php index b9d8fc56a91d9..de204b312a3fd 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php @@ -6,12 +6,12 @@ namespace Magento\Catalog\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Model\Locator\LocatorInterface; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Store\Api\WebsiteRepositoryInterface; use Magento\Store\Api\GroupRepositoryInterface; use Magento\Store\Api\StoreRepositoryInterface; -use Magento\Ui\Component\Form; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\Ui\Component\DynamicRows; +use Magento\Ui\Component\Form; /** * Class Websites customizes websites panel @@ -211,6 +211,30 @@ protected function getFieldsForFieldset() } } + $children = $this->setDefaultWebsiteIdIfNoneAreSelected($children); + return $children; + } + + /** + * Set default website id if none are selected + * + * @param array $children + * @return array + */ + private function setDefaultWebsiteIdIfNoneAreSelected(array $children):array + { + $websitesList = $this->getWebsitesList(); + $defaultSelectedWebsite = false; + foreach ($websitesList as $website) { + if ($children[$website['id']]['arguments']['data']['config']['value']) { + $defaultSelectedWebsite = true; + break; + } + } + if (count($websitesList) === 1 && !$defaultSelectedWebsite) { + $website = reset($websitesList); + $children[$website['id']]['arguments']['data']['config']['value'] = (string)$website['id']; + } return $children; } From 27dd58a91fa285ecc846d5ec941fe573666cf6d2 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Fri, 6 Mar 2020 16:00:02 +0200 Subject: [PATCH 209/369] added Patch for update product url_key --- .../Patch/Data/UpdateUrlKeyForProducts.php | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeyForProducts.php diff --git a/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeyForProducts.php b/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeyForProducts.php new file mode 100644 index 0000000000000..5e7039912999b --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeyForProducts.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Setup\Patch\Data; + +use Magento\Catalog\Model\Product\Url; +use Magento\Eav\Setup\EavSetup; +use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\Setup\Patch\PatchVersionInterface; + +/** + * Update url_key all products. + */ +class UpdateUrlKeyForProducts implements DataPatchInterface, PatchVersionInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var EavSetup + */ + private $eavSetup; + + /** + * @var Url + */ + private $urlProduct; + + /** + * @param ModuleDataSetupInterface $moduleDataSetup + * @param EavSetupFactory $eavSetupFactory + * @param Url $urlProduct + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + EavSetupFactory $eavSetupFactory, + Url $urlProduct + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->eavSetup = $eavSetupFactory->create(['setup' => $moduleDataSetup]); + $this->urlProduct = $urlProduct; + } + + /** + * @inheritdoc + */ + public function apply() + { + $productTypeId = $this->eavSetup->getEntityTypeId(\Magento\Catalog\Model\Product::ENTITY); + $table = $this->moduleDataSetup->getTable('catalog_product_entity_varchar'); + $select = $this->moduleDataSetup->getConnection()->select()->from( + $table, + ['value_id', 'value'] + )->where( + 'attribute_id = ?', + $this->eavSetup->getAttributeId($productTypeId, 'url_key') + ); + + $result = $this->moduleDataSetup->getConnection()->fetchAll($select); + foreach ($result as $key => $item) { + $result[$key]['value'] = $this->urlProduct->formatUrlKey($item['value']); + } + + foreach (array_chunk($result, 500, true) as $pathResult) { + $this->moduleDataSetup->getConnection()->insertOnDuplicate($table, $pathResult, ['value']); + } + + return $this; + } + + /** + * @inheritDoc + */ + public static function getVersion() + { + return "2.4.0"; + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} From c1c1dfee2ef9928010195473b4a72e1bf3299756 Mon Sep 17 00:00:00 2001 From: Roman Zhupanyn <roma.dj.elf@gmail.com> Date: Fri, 6 Mar 2020 16:33:38 +0200 Subject: [PATCH 210/369] MC-32126: Admin: Create/update/delete customer --- .../Controller/Adminhtml/Index/SaveTest.php | 18 +++++++++--------- .../Controller/Adminhtml/IndexTest.php | 19 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/SaveTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/SaveTest.php index aed6003ea76ac..f532e2fcb7182 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/SaveTest.php @@ -36,7 +36,7 @@ class SaveTest extends AbstractBackendController * * @var string */ - private $_baseControllerUrl = 'http://localhost/index.php/backend/customer/index/'; + private $baseControllerUrl = 'backend/customer/index/'; /** @var CustomerRepositoryInterface */ private $customerRepository; @@ -83,7 +83,7 @@ public function testCreateCustomer(array $postData, array $expectedData): void $this->equalTo([(string)__('You saved the customer.')]), MessageInterface::TYPE_SUCCESS ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); + $this->assertRedirect($this->stringContains($this->baseControllerUrl . 'index/key/')); $this->assertCustomerData( $postData['customer'][CustomerData::EMAIL], (int)$postData['customer'][CustomerData::WEBSITE_ID], @@ -158,7 +158,7 @@ public function testCreateCustomerErrors(array $postData, array $expectedData, a ); $this->assertNotEmpty($this->session->getCustomerFormData()); $this->assertArraySubset($expectedData, $this->session->getCustomerFormData()); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new/key/')); + $this->assertRedirect($this->stringContains($this->baseControllerUrl . 'new/key/')); } /** @@ -251,8 +251,8 @@ public function testUpdateCustomer(): void $this->equalTo([(string)__('You saved the customer.')]), MessageInterface::TYPE_SUCCESS ); - $this->assertRedirect($this->stringStartsWith( - $this->_baseControllerUrl . 'edit/id/' . $customerData->getId() + $this->assertRedirect($this->stringContains( + $this->baseControllerUrl . 'edit/id/' . $customerData->getId() )); $this->assertCustomerData($customerData->getEmail(), (int)$customerData->getWebsiteId(), $expectedData); $this->assertCustomerSubscription( @@ -289,7 +289,7 @@ public function testExistingCustomerUnsubscribeNewsletter(): void $this->equalTo([(string)__('You saved the customer.')]), MessageInterface::TYPE_SUCCESS ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); + $this->assertRedirect($this->stringContains($this->baseControllerUrl . 'index/key/')); $this->assertCustomerSubscription( (int)$customerData->getId(), (int)$customerData->getWebsiteId(), @@ -344,7 +344,7 @@ public function testExistingCustomerChangeEmail(): void * Check that no errors were generated and set to session */ $this->assertSessionMessages($this->isEmpty(), MessageInterface::TYPE_ERROR); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); + $this->assertRedirect($this->stringContains($this->baseControllerUrl . 'index/key/')); } /** @@ -384,7 +384,7 @@ public function testCreateSameEmailFormatDateError(): void true, 'Customer form data should be formatted' ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new/key/')); + $this->assertRedirect($this->stringContains($this->baseControllerUrl . 'new/key/')); } /** @@ -449,7 +449,7 @@ private function dispatchCustomerSave(array $postData, array $params = []): void if (!empty($params)) { $this->getRequest()->setParams($params); } - $this->dispatch('backend/customer/index/save'); + $this->dispatch($this->baseControllerUrl . 'save'); } /** diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 756270344d720..23ea8011e9bc9 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -7,8 +7,8 @@ namespace Magento\Customer\Controller\Adminhtml; use Magento\Backend\Model\Session; +use Magento\Customer\Api\CustomerNameGenerationInterface; use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Customer\Helper\View; use Magento\Customer\Model\EmailNotification; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\App\Request\Http as HttpRequest; @@ -25,13 +25,13 @@ class IndexTest extends AbstractBackendController * * @var string */ - protected $_baseControllerUrl; + private $baseControllerUrl = 'backend/customer/index/'; /** @var CustomerRepositoryInterface */ - protected $customerRepository; + private $customerRepository; - /** @var View */ - protected $customerViewHelper; + /** @var CustomerNameGenerationInterface */ + private $customerViewHelper; /** * @inheritDoc @@ -39,9 +39,8 @@ class IndexTest extends AbstractBackendController protected function setUp() { parent::setUp(); - $this->_baseControllerUrl = 'http://localhost/index.php/backend/customer/index/'; $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); - $this->customerViewHelper = $this->_objectManager->get(View::class); + $this->customerViewHelper = $this->_objectManager->get(CustomerNameGenerationInterface::class); } /** @@ -173,7 +172,7 @@ public function testResetPasswordActionNoCustomerId() // No customer ID in post, will just get redirected to base $this->getRequest()->setMethod(HttpRequest::METHOD_GET); $this->dispatch('backend/customer/index/resetPassword'); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); + $this->assertRedirect($this->stringContains($this->baseControllerUrl)); } /** @@ -185,7 +184,7 @@ public function testResetPasswordActionBadCustomerId() $this->getRequest()->setMethod(HttpRequest::METHOD_GET); $this->getRequest()->setPostValue(['customer_id' => '789']); $this->dispatch('backend/customer/index/resetPassword'); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); + $this->assertRedirect($this->stringContains($this->baseControllerUrl)); } /** @@ -200,7 +199,7 @@ public function testResetPasswordActionSuccess() $this->equalTo(['The customer will receive an email with a link to reset password.']), \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'edit')); + $this->assertRedirect($this->stringContains($this->baseControllerUrl . 'edit')); } /** From 45b9e193ddd8a413cb041976de6f1d9da5f53d5f Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Fri, 6 Mar 2020 16:49:50 +0200 Subject: [PATCH 211/369] MC-31573: PayflowPro Checkout Broken with SameSite Cookie Changes from Chrome 80 --- .../Adminhtml/Transparent/Redirect.php | 80 +------------------ 1 file changed, 1 insertion(+), 79 deletions(-) diff --git a/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php index f105843e5abfd..8201761cc3a29 100644 --- a/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php +++ b/app/code/Magento/Paypal/Controller/Adminhtml/Transparent/Redirect.php @@ -5,87 +5,9 @@ */ namespace Magento\Paypal\Controller\Adminhtml\Transparent; -use Magento\Backend\App\AbstractAction; -use Magento\Backend\App\Action\Context; -use Magento\Framework\App\Action\HttpPostActionInterface; -use Magento\Framework\App\CsrfAwareActionInterface; -use Magento\Framework\App\Request\InvalidRequestException; -use Magento\Framework\App\RequestInterface; -use Magento\Framework\View\Result\LayoutFactory; -use Magento\Payment\Model\Method\Logger; -use Magento\Paypal\Model\Payflow\Transparent; - /** * Class for redirecting the Paypal response result to Magento controller. */ -class Redirect extends AbstractAction implements HttpPostActionInterface, CsrfAwareActionInterface +class Redirect extends \Magento\Paypal\Controller\Transparent\Redirect { - /** - * @var LayoutFactory - */ - private $resultLayoutFactory; - - /** - * @var Transparent - */ - private $transparent; - - /** - * @var Logger - */ - private $logger; - - /** - * @param Context $context - * @param LayoutFactory $resultLayoutFactory - * @param Transparent $transparent - * @param Logger $logger - */ - public function __construct( - Context $context, - LayoutFactory $resultLayoutFactory, - Transparent $transparent, - Logger $logger - ) { - parent::__construct($context); - $this->transparent = $transparent; - $this->logger = $logger; - $this->resultLayoutFactory = $resultLayoutFactory; - } - - /** - * @inheritdoc - */ - public function execute() - { - $gatewayResponse = (array)$this->getRequest()->getPostValue(); - $this->logger->debug( - ['PayPal PayflowPro redirect:' => $gatewayResponse], - $this->transparent->getDebugReplacePrivateDataKeys(), - $this->transparent->getDebugFlag() - ); - - $resultLayout = $this->resultLayoutFactory->create(); - $resultLayout->addDefaultHandle(); - $resultLayout->getLayout()->getUpdate()->load(['transparent_payment_redirect']); - - return $resultLayout; - } - - /** - * @inheritdoc - */ - public function createCsrfValidationException( - RequestInterface $request - ): ?InvalidRequestException { - return null; - } - - /** - * @inheritdoc - */ - public function validateForCsrf(RequestInterface $request): ?bool - { - return true; - } } From a8fec13eebb727ee4e589c47fe3e2c2a97bf0e6f Mon Sep 17 00:00:00 2001 From: Raul E Watson <raul.watson@maginus.com> Date: Fri, 6 Mar 2020 16:27:38 +0000 Subject: [PATCH 212/369] Remove @author annotation from Magento framework --- .../Magento/Framework/Component/ComponentRegistrar.php | 2 -- .../Framework/Component/ComponentRegistrarInterface.php | 3 --- lib/internal/Magento/Framework/Module/ModuleList/Loader.php | 5 ++--- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/internal/Magento/Framework/Component/ComponentRegistrar.php b/lib/internal/Magento/Framework/Component/ComponentRegistrar.php index 0a54d770300e8..8dba61bc5945b 100644 --- a/lib/internal/Magento/Framework/Component/ComponentRegistrar.php +++ b/lib/internal/Magento/Framework/Component/ComponentRegistrar.php @@ -8,8 +8,6 @@ /** * Provides ability to statically register components. * - * @author Josh Di Fabio <joshdifabio@gmail.com> - * * @api */ class ComponentRegistrar implements ComponentRegistrarInterface diff --git a/lib/internal/Magento/Framework/Component/ComponentRegistrarInterface.php b/lib/internal/Magento/Framework/Component/ComponentRegistrarInterface.php index 04d7676eff3c0..eb7144306b593 100644 --- a/lib/internal/Magento/Framework/Component/ComponentRegistrarInterface.php +++ b/lib/internal/Magento/Framework/Component/ComponentRegistrarInterface.php @@ -5,9 +5,6 @@ */ namespace Magento\Framework\Component; -/** - * @author Josh Di Fabio <joshdifabio@gmail.com> - */ interface ComponentRegistrarInterface { /** diff --git a/lib/internal/Magento/Framework/Module/ModuleList/Loader.php b/lib/internal/Magento/Framework/Module/ModuleList/Loader.php index b1d21a6db5f16..704d259be6486 100644 --- a/lib/internal/Magento/Framework/Module/ModuleList/Loader.php +++ b/lib/internal/Magento/Framework/Module/ModuleList/Loader.php @@ -80,7 +80,7 @@ public function load(array $exclude = []) $result = []; $excludeSet = array_flip($exclude); - foreach ($this->getModuleConfigs() as list($file, $contents)) { + foreach ($this->getModuleConfigs() as [$file, $contents]) { try { $this->parser->loadXML($contents); } catch (\Magento\Framework\Exception\LocalizedException $e) { @@ -111,8 +111,7 @@ public function load(array $exclude = []) * </code> * * @return \Traversable - * - * @author Josh Di Fabio <joshdifabio@gmail.com> + * @throws \Magento\Framework\Exception\FileSystemException */ private function getModuleConfigs() { From bc9c6161b93ee1bf4b896be4b6a4a26f826cca75 Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Thu, 5 Mar 2020 16:55:52 -0600 Subject: [PATCH 213/369] MC-31986: Add support for ES 7 to 2.4-develop --- .../Mftf/Data/ProductAttributeOptionData.xml | 24 + .../Catalog/Test/Mftf/Data/StoreLabelData.xml | 12 + ...dminProductMultiselectAttributeSection.xml | 14 + ...tProductsDisplayUsingElasticSearchTest.xml | 2 +- .../Config/Elasticsearch5/TestConnection.php | 3 +- .../System/Config/TestConnection.php | 31 -- .../CategoryFieldsProvider.php | 15 +- .../Adapter/DataMapper/ProductDataMapper.php | 15 +- .../FieldMapper/ProductFieldMapper.php | 74 +-- .../Model/Client/Elasticsearch.php | 2 + .../Elasticsearch5/SearchAdapter/Adapter.php | 23 +- .../SearchAdapter/Aggregation/Interval.php | 14 +- .../CategoryFieldsProvider.php | 103 ----- .../BatchDataMapper/DataMapperFactory.php | 2 +- .../BatchDataMapper/DataMapperResolver.php | 2 +- .../BatchDataMapper/PriceFieldsProvider.php | 17 +- .../Adapter/DataMapper/ProductDataMapper.php | 19 - .../Model/Adapter/Elasticsearch.php | 19 +- .../Product/FieldProvider/DynamicField.php | 20 +- .../FieldName/Resolver/CategoryName.php | 11 +- .../FieldName/Resolver/Position.php | 11 +- .../FieldName/Resolver/Price.php | 12 +- .../Product/FieldProvider/StaticField.php | 8 +- .../FieldMapper/ProductFieldMapper.php | 17 - .../Model/Adapter/Index/IndexNameResolver.php | 4 +- .../Magento/Elasticsearch/Model/Config.php | 15 +- .../Model/DataProvider/Base}/Suggestions.php | 4 +- .../Model/DataProvider/Suggestions.php | 11 +- .../SearchAdapter/Aggregation/Builder.php | 15 +- .../SearchAdapter/Aggregation/Interval.php | 287 ------------ .../SearchAdapter/ConnectionManager.php | 4 +- .../SearchAdapter/DocumentFactory.php | 16 +- .../SearchAdapter/Filter/Builder.php | 59 ++- .../SearchAdapter/Filter/Builder/Term.php | 8 +- .../Elasticsearch/SearchAdapter/Mapper.php | 64 --- .../SearchAdapter/Query/Builder/Match.php | 54 +-- .../AssertSearchResultActionGroup.xml | 26 ++ .../ModifyCustomAttributeValueActionGroup.xml | 25 + .../Test/Mftf/Data/ConfigData.xml | 4 +- .../Suite/SearchEngineElasticsearchSuite.xml | 0 ...oductQuickSearchUsingElasticSearchTest.xml | 9 +- ...DecimalAttributeUsingElasticSearchTest.xml | 99 ++++ .../DataMapper/ProductDataMapperTest.php | 400 ---------------- .../Unit/Model/Adapter/ElasticsearchTest.php | 11 +- .../Adapter/Index/IndexNameResolverTest.php | 4 +- .../DataProvider/Base}/SuggestionsTest.php | 4 +- .../Model/DataProvider/SuggestionsTest.php | 5 +- .../Unit/Model/Indexer/IndexerHandlerTest.php | 10 +- .../Test/Unit/SearchAdapter/AdapterTest.php | 173 ------- .../Aggregation/IntervalTest.php | 358 --------------- .../SearchAdapter/ConnectionManagerTest.php | 4 +- .../Dynamic/DataProviderTest.php | 6 +- .../Test/Unit/SearchAdapter/MapperTest.php | 202 -------- app/code/Magento/Elasticsearch/composer.json | 2 +- .../Elasticsearch/etc/adminhtml/system.xml | 62 --- app/code/Magento/Elasticsearch/etc/config.xml | 12 +- app/code/Magento/Elasticsearch/etc/di.xml | 49 +- .../Elasticsearch/etc/search_engine.xml | 3 - ...frontElasticSearchForChineseLocaleTest.xml | 7 +- .../Unit/Model/Client/ElasticsearchTest.php | 4 +- app/code/Magento/Elasticsearch6/composer.json | 3 +- app/code/Magento/Elasticsearch6/etc/di.xml | 4 +- .../System/Config/TestConnection.php | 33 ++ app/code/Magento/Elasticsearch7/LICENSE.txt | 48 ++ .../Magento/Elasticsearch7/LICENSE_AFL.txt | 48 ++ .../FieldName/Resolver/DefaultResolver.php | 50 ++ .../Model/Client/Elasticsearch.php | 177 +++---- app/code/Magento/Elasticsearch7/README.md | 2 + .../SearchAdapter/Adapter.php | 73 ++- .../Elasticsearch7/SearchAdapter/Mapper.php | 44 ++ .../Unit}/Model/Client/ElasticsearchTest.php | 184 ++++---- app/code/Magento/Elasticsearch7/composer.json | 29 ++ .../Elasticsearch7/etc/adminhtml/system.xml | 85 ++++ .../Magento/Elasticsearch7/etc/config.xml | 20 + app/code/Magento/Elasticsearch7/etc/di.xml | 222 +++++++++ .../Magento/Elasticsearch7/etc/module.xml | 17 + .../Elasticsearch7/etc/search_engine.xml | 12 + .../Magento/Elasticsearch7/registration.php | 12 + composer.json | 3 +- composer.lock | 430 +++++++++--------- .../Model/ElasticsearchVersionChecker.php | 39 ++ .../Block/Product/ListProduct/SortingTest.php | 4 + .../Controller/Advanced/ResultTest.php | 4 +- .../SearchAdapter/AdapterTest.php | 5 +- .../Model/Client/ElasticsearchTest.php | 35 +- .../Model/Indexer/IndexHandlerTest.php | 36 +- .../Model/Indexer/ReindexAllTest.php | 37 +- .../SearchAdapter/AdapterTest.php | 67 ++- .../Controller/Advanced/ResultTest.php | 37 -- .../Controller/Result/IndexTest.php | 37 -- .../fulltext/Action/DataProviderTest.php | 32 -- .../Search/AttributeSearchWeightTest.php | 4 +- .../Model/QuickSearchTest.php | 69 --- .../Controller/QuickSearchTest.php | 9 +- .../Elasticsearch6/_files/full_reindex.php | 13 + .../testFromCreateProject/composer.lock | 6 +- .../_files/testSkeleton/composer.lock | 6 +- .../Formatters/FilteredErrorFormatter.php | 1 + .../Test/Legacy/_files/obsolete_classes.php | 7 + 99 files changed, 1717 insertions(+), 2717 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMultiselectAttributeSection.xml delete mode 100644 app/code/Magento/Elasticsearch/Block/Adminhtml/System/Config/TestConnection.php delete mode 100644 app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php delete mode 100644 app/code/Magento/Elasticsearch/Model/Adapter/DataMapper/ProductDataMapper.php delete mode 100644 app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/ProductFieldMapper.php rename app/code/Magento/{Elasticsearch6/Model/DataProvider => Elasticsearch/Model/DataProvider/Base}/Suggestions.php (98%) delete mode 100644 app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Interval.php delete mode 100644 app/code/Magento/Elasticsearch/SearchAdapter/Mapper.php create mode 100644 app/code/Magento/Elasticsearch/Test/Mftf/ActionGroup/AssertSearchResultActionGroup.xml create mode 100644 app/code/Magento/Elasticsearch/Test/Mftf/ActionGroup/ModifyCustomAttributeValueActionGroup.xml rename app/code/Magento/{Elasticsearch6 => Elasticsearch}/Test/Mftf/Data/ConfigData.xml (75%) rename app/code/Magento/{Elasticsearch6 => Elasticsearch}/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml (100%) create mode 100644 app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchWithDecimalAttributeUsingElasticSearchTest.xml delete mode 100644 app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/DataMapper/ProductDataMapperTest.php rename app/code/Magento/{Elasticsearch6/Test/Unit/Model/DataProvider => Elasticsearch/Test/Unit/Model/DataProvider/Base}/SuggestionsTest.php (97%) delete mode 100644 app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/AdapterTest.php delete mode 100644 app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Aggregation/IntervalTest.php delete mode 100644 app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/MapperTest.php create mode 100644 app/code/Magento/Elasticsearch7/Block/Adminhtml/System/Config/TestConnection.php create mode 100644 app/code/Magento/Elasticsearch7/LICENSE.txt create mode 100644 app/code/Magento/Elasticsearch7/LICENSE_AFL.txt create mode 100644 app/code/Magento/Elasticsearch7/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php rename app/code/Magento/{Elasticsearch => Elasticsearch7}/Model/Client/Elasticsearch.php (60%) create mode 100644 app/code/Magento/Elasticsearch7/README.md rename app/code/Magento/{Elasticsearch => Elasticsearch7}/SearchAdapter/Adapter.php (53%) create mode 100644 app/code/Magento/Elasticsearch7/SearchAdapter/Mapper.php rename app/code/Magento/{Elasticsearch/Test/Unit/Elasticsearch5 => Elasticsearch7/Test/Unit}/Model/Client/ElasticsearchTest.php (93%) create mode 100644 app/code/Magento/Elasticsearch7/composer.json create mode 100644 app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml create mode 100644 app/code/Magento/Elasticsearch7/etc/config.xml create mode 100644 app/code/Magento/Elasticsearch7/etc/di.xml create mode 100644 app/code/Magento/Elasticsearch7/etc/module.xml create mode 100644 app/code/Magento/Elasticsearch7/etc/search_engine.xml create mode 100644 app/code/Magento/Elasticsearch7/registration.php create mode 100644 dev/tests/integration/_files/Magento/TestModuleCatalogSearch/Model/ElasticsearchVersionChecker.php delete mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Controller/Advanced/ResultTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Controller/Result/IndexTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Model/Indexer/fulltext/Action/DataProviderTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch6/ConfigurableProduct/Model/QuickSearchTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch6/_files/full_reindex.php diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml index a8646a58ae39c..34fee2f3445b2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml @@ -101,4 +101,28 @@ <entity name="ProductAttributeOptionTwoForExportImport" extends="productAttributeOption2" type="ProductAttributeOption"> <data key="label">option2</data> </entity> + <entity name="ProductAttributeOption10" type="ProductAttributeOption"> + <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="label" unique="suffix">3.5</data> + <data key="value" unique="suffix">3.5</data> + <data key="is_default">false</data> + <data key="sort_order">1</data> + <requiredEntity type="StoreLabel">Option12Store1</requiredEntity> + </entity> + <entity name="ProductAttributeOption11" type="ProductAttributeOption"> + <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="label" unique="suffix">10.12</data> + <data key="value" unique="suffix">10.12</data> + <data key="is_default">false</data> + <data key="sort_order">2</data> + <requiredEntity type="StoreLabel">Option13Store1</requiredEntity> + </entity> + <entity name="ProductAttributeOption12" type="ProductAttributeOption"> + <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="label" unique="suffix">36</data> + <data key="value" unique="suffix">36</data> + <data key="is_default">false</data> + <data key="sort_order">3</data> + <requiredEntity type="StoreLabel">Option14Store1</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml index dcd7fde92283c..2ae473cde5108 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml @@ -80,4 +80,16 @@ <data key="store_id">1</data> <data key="label">Blue</data> </entity> + <entity name="Option12Store1" type="StoreLabel"> + <data key="store_id">1</data> + <data key="label">3.5</data> + </entity> + <entity name="Option13Store1" type="StoreLabel"> + <data key="store_id">1</data> + <data key="label">10.12</data> + </entity> + <entity name="Option14Store1" type="StoreLabel"> + <data key="store_id">1</data> + <data key="label">36</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMultiselectAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMultiselectAttributeSection.xml new file mode 100644 index 0000000000000..66af84b08df69 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMultiselectAttributeSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminProductMultiselectAttributeSection"> + <element name="option" type="text" selector="//option[contains(@data-title,'{{value}}')]" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml index fe37f2110acc9..9690246bbe68c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontProductsDisplayUsingElasticSearchTest.xml @@ -127,7 +127,7 @@ </createData> <!--Enable ElasticSearch as search engine.--> - <magentoCLI command="config:set catalog/search/engine elasticsearch6" stepKey="enableElasticSearchAsSearchEngine"/> + <magentoCLI command="config:set {{SearchEngineElasticsearchConfigData.path}} {{SearchEngineElasticsearchConfigData.value}}" stepKey="enableElasticSearchAsSearchEngine"/> <magentoCLI command="indexer:reindex" stepKey="performReindexAfterElasticSearchEnable"/> <magentoCLI command="cache:flush" stepKey="cleanCacheAfterElasticSearchEnable"/> diff --git a/app/code/Magento/Elasticsearch/Block/Adminhtml/System/Config/Elasticsearch5/TestConnection.php b/app/code/Magento/Elasticsearch/Block/Adminhtml/System/Config/Elasticsearch5/TestConnection.php index aa40928ce2b16..2b2da7522dfa6 100644 --- a/app/code/Magento/Elasticsearch/Block/Adminhtml/System/Config/Elasticsearch5/TestConnection.php +++ b/app/code/Magento/Elasticsearch/Block/Adminhtml/System/Config/Elasticsearch5/TestConnection.php @@ -8,11 +8,12 @@ /** * Elasticsearch 5x test connection block * @codeCoverageIgnore + * @deprecated because of EOL for Elasticsearch5 */ class TestConnection extends \Magento\AdvancedSearch\Block\Adminhtml\System\Config\TestConnection { /** - * {@inheritdoc} + * @inheritdoc */ protected function _getFieldMapping() { diff --git a/app/code/Magento/Elasticsearch/Block/Adminhtml/System/Config/TestConnection.php b/app/code/Magento/Elasticsearch/Block/Adminhtml/System/Config/TestConnection.php deleted file mode 100644 index 5dc4476794da7..0000000000000 --- a/app/code/Magento/Elasticsearch/Block/Adminhtml/System/Config/TestConnection.php +++ /dev/null @@ -1,31 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Block\Adminhtml\System\Config; - -/** - * Elasticsearch test connection block - * @codeCoverageIgnore - */ -class TestConnection extends \Magento\AdvancedSearch\Block\Adminhtml\System\Config\TestConnection -{ - /** - * {@inheritdoc} - */ - protected function _getFieldMapping() - { - $fields = [ - 'engine' => 'catalog_search_engine', - 'hostname' => 'catalog_search_elasticsearch_server_hostname', - 'port' => 'catalog_search_elasticsearch_server_port', - 'index' => 'catalog_search_elasticsearch_index_prefix', - 'enableAuth' => 'catalog_search_elasticsearch_enable_auth', - 'username' => 'catalog_search_elasticsearch_username', - 'password' => 'catalog_search_elasticsearch_password', - 'timeout' => 'catalog_search_elasticsearch_server_timeout', - ]; - return array_merge(parent::_getFieldMapping(), $fields); - } -} diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php index eb7874a936140..bd62a7372579c 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php @@ -9,7 +9,6 @@ use Magento\Elasticsearch\Model\ResourceModel\Index; use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; /** @@ -34,19 +33,17 @@ class CategoryFieldsProvider implements AdditionalFieldsProviderInterface /** * @param Index $resourceIndex - * @param AttributeProvider|null $attributeAdapterProvider - * @param ResolverInterface|null $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param ResolverInterface $fieldNameResolver */ public function __construct( Index $resourceIndex, - AttributeProvider $attributeAdapterProvider = null, - ResolverInterface $fieldNameResolver = null + AttributeProvider $attributeAdapterProvider, + ResolverInterface $fieldNameResolver ) { $this->resourceIndex = $resourceIndex; - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver; } /** diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php index f0b7380397235..91bde497c612b 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php @@ -16,7 +16,6 @@ use Magento\Elasticsearch\Model\Adapter\DataMapperInterface; use Magento\Elasticsearch\Model\Adapter\FieldType\Date as DateFieldType; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; /** @@ -103,8 +102,8 @@ class ProductDataMapper implements DataMapperInterface * @param FieldMapperInterface $fieldMapper * @param StoreManagerInterface $storeManager * @param DateFieldType $dateFieldType - * @param AttributeProvider|null $attributeAdapterProvider - * @param ResolverInterface|null $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param ResolverInterface $fieldNameResolver */ public function __construct( Builder $builder, @@ -113,8 +112,8 @@ public function __construct( FieldMapperInterface $fieldMapper, StoreManagerInterface $storeManager, DateFieldType $dateFieldType, - AttributeProvider $attributeAdapterProvider = null, - ResolverInterface $fieldNameResolver = null + AttributeProvider $attributeAdapterProvider, + ResolverInterface $fieldNameResolver ) { $this->builder = $builder; $this->attributeContainer = $attributeContainer; @@ -122,10 +121,8 @@ public function __construct( $this->fieldMapper = $fieldMapper; $this->storeManager = $storeManager; $this->dateFieldType = $dateFieldType; - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver; $this->mediaGalleryRoles = [ self::MEDIA_ROLE_IMAGE, diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php index 5aea87e5e6ae1..fd989050deb2d 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php @@ -6,52 +6,16 @@ namespace Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper; -use Magento\Eav\Model\Config; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; -use Magento\Framework\App\ObjectManager; -use Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldType; -use Magento\Framework\Registry; -use Magento\Store\Model\StoreManagerInterface as StoreManager; -use \Magento\Customer\Model\Session as CustomerSession; /** - * Class ProductFieldMapper + * Class ProductFieldMapper provides field name by attribute code and retrieve all attribute types */ class ProductFieldMapper implements FieldMapperInterface { - /** - * @deprecated - * @var Config - */ - protected $eavConfig; - - /** - * @deprecated - * @var FieldType - */ - protected $fieldType; - - /** - * @deprecated - * @var CustomerSession - */ - protected $customerSession; - - /** - * @deprecated - * @var StoreManager - */ - protected $storeManager; - - /** - * @deprecated - * @var Registry - */ - protected $coreRegistry; - /** * @var AttributeProvider */ @@ -68,36 +32,18 @@ class ProductFieldMapper implements FieldMapperInterface private $fieldProvider; /** - * @param Config $eavConfig - * @param FieldType $fieldType - * @param CustomerSession $customerSession - * @param StoreManager $storeManager - * @param Registry $coreRegistry - * @param ResolverInterface|null $fieldNameResolver - * @param AttributeProvider|null $attributeAdapterProvider - * @param FieldProviderInterface|null $fieldProvider + * @param ResolverInterface $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param FieldProviderInterface $fieldProvider */ public function __construct( - Config $eavConfig, - FieldType $fieldType, - CustomerSession $customerSession, - StoreManager $storeManager, - Registry $coreRegistry, - ResolverInterface $fieldNameResolver = null, - AttributeProvider $attributeAdapterProvider = null, - FieldProviderInterface $fieldProvider = null + ResolverInterface $fieldNameResolver, + AttributeProvider $attributeAdapterProvider, + FieldProviderInterface $fieldProvider ) { - $this->eavConfig = $eavConfig; - $this->fieldType = $fieldType; - $this->customerSession = $customerSession; - $this->storeManager = $storeManager; - $this->coreRegistry = $coreRegistry; - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldProvider = $fieldProvider ?: ObjectManager::getInstance() - ->get(FieldProviderInterface::class); + $this->fieldNameResolver = $fieldNameResolver; + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldProvider = $fieldProvider; } /** diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php index ddf79f413df37..bd9a380230652 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Client/Elasticsearch.php @@ -10,6 +10,8 @@ /** * Elasticsearch client + * + * @deprecated the Elasticsearch 5 doesn't supported due to EOL */ class Elasticsearch implements ClientInterface { diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php index 0ae347d5791ad..eeb11a8b0a074 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php @@ -5,14 +5,14 @@ */ namespace Magento\Elasticsearch\Elasticsearch5\SearchAdapter; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Search\AdapterInterface; use Magento\Framework\Search\RequestInterface; use Magento\Framework\Search\Response\QueryResponse; use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder as AggregationBuilder; use Magento\Elasticsearch\SearchAdapter\ConnectionManager; -use \Magento\Elasticsearch\SearchAdapter\ResponseFactory; +use Magento\Elasticsearch\SearchAdapter\ResponseFactory; use Psr\Log\LoggerInterface; +use Magento\Elasticsearch\SearchAdapter\QueryContainerFactory; /** * Elasticsearch Search Adapter @@ -24,27 +24,27 @@ class Adapter implements AdapterInterface * * @var Mapper */ - protected $mapper; + private $mapper; /** * Response Factory * * @var ResponseFactory */ - protected $responseFactory; + private $responseFactory; /** * @var ConnectionManager */ - protected $connectionManager; + private $connectionManager; /** * @var AggregationBuilder */ - protected $aggregationBuilder; + private $aggregationBuilder; /** - * @var \Magento\Elasticsearch\SearchAdapter\QueryContainerFactory + * @var QueryContainerFactory */ private $queryContainerFactory; @@ -79,7 +79,7 @@ class Adapter implements AdapterInterface * @param Mapper $mapper * @param ResponseFactory $responseFactory * @param AggregationBuilder $aggregationBuilder - * @param \Magento\Elasticsearch\SearchAdapter\QueryContainerFactory $queryContainerFactory + * @param QueryContainerFactory $queryContainerFactory * @param LoggerInterface $logger */ public function __construct( @@ -87,16 +87,15 @@ public function __construct( Mapper $mapper, ResponseFactory $responseFactory, AggregationBuilder $aggregationBuilder, - \Magento\Elasticsearch\SearchAdapter\QueryContainerFactory $queryContainerFactory, - LoggerInterface $logger = null + QueryContainerFactory $queryContainerFactory, + LoggerInterface $logger ) { $this->connectionManager = $connectionManager; $this->mapper = $mapper; $this->responseFactory = $responseFactory; $this->aggregationBuilder = $aggregationBuilder; $this->queryContainerFactory = $queryContainerFactory; - $this->logger = $logger ?: ObjectManager::getInstance() - ->get(LoggerInterface::class); + $this->logger = $logger; } /** diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php index a1fcbeb061481..c1170a14d6970 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Aggregation/Interval.php @@ -87,7 +87,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function load($limit, $offset = null, $lower = null, $upper = null) { @@ -116,7 +116,7 @@ public function load($limit, $offset = null, $lower = null, $upper = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function loadPrevious($data, $index, $lower = null) { @@ -141,11 +141,15 @@ public function loadPrevious($data, $index, $lower = null) return false; } + if (is_array($offset)) { + $offset = $offset['value']; + } + return $this->load($index - $offset + 1, $offset - 1, $lower); } /** - * {@inheritdoc} + * @inheritdoc */ public function loadNext($data, $rightIndex, $upper = null) { @@ -166,6 +170,10 @@ public function loadNext($data, $rightIndex, $upper = null) return false; } + if (is_array($offset)) { + $offset = $offset['value']; + } + $from = ['gte' => $data - self::DELTA]; if ($upper !== null) { $to = ['lt' => $data - self::DELTA]; diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php deleted file mode 100644 index 0e130c24e79d3..0000000000000 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/CategoryFieldsProvider.php +++ /dev/null @@ -1,103 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Elasticsearch\Model\Adapter\BatchDataMapper; - -use Magento\Elasticsearch\Model\ResourceModel\Index; -use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; - -/** - * Provide data mapping for categories fields - */ -class CategoryFieldsProvider implements AdditionalFieldsProviderInterface -{ - /** - * @var Index - */ - private $resourceIndex; - - /** - * @var AttributeProvider - */ - private $attributeAdapterProvider; - - /** - * @var ResolverInterface - */ - private $fieldNameResolver; - - /** - * @param Index $resourceIndex - * @param AttributeProvider|null $attributeAdapterProvider - * @param ResolverInterface|null $fieldNameResolver - */ - public function __construct( - Index $resourceIndex, - AttributeProvider $attributeAdapterProvider = null, - ResolverInterface $fieldNameResolver = null - ) { - $this->resourceIndex = $resourceIndex; - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); - } - - /** - * @inheritdoc - */ - public function getFields(array $productIds, $storeId) - { - $categoryData = $this->resourceIndex->getFullCategoryProductIndexData($storeId, $productIds); - - $fields = []; - foreach ($productIds as $productId) { - $fields[$productId] = $this->getProductCategoryData($productId, $categoryData); - } - - return $fields; - } - - /** - * Prepare category index data for product - * - * @param int $productId - * @param array $categoryIndexData - * @return array - */ - private function getProductCategoryData($productId, array $categoryIndexData) - { - $result = []; - - if (array_key_exists($productId, $categoryIndexData)) { - $indexData = $categoryIndexData[$productId]; - $categoryIds = array_column($indexData, 'id'); - - if (count($categoryIds)) { - $result = ['category_ids' => implode(' ', $categoryIds)]; - $positionAttribute = $this->attributeAdapterProvider->getByAttributeCode('position'); - $categoryNameAttribute = $this->attributeAdapterProvider->getByAttributeCode('category_name'); - foreach ($indexData as $data) { - $categoryPositionKey = $this->fieldNameResolver->getFieldName( - $positionAttribute, - ['categoryId' => $data['id']] - ); - $categoryNameKey = $this->fieldNameResolver->getFieldName( - $categoryNameAttribute, - ['categoryId' => $data['id']] - ); - $result[$categoryPositionKey] = $data['position']; - $result[$categoryNameKey] = $data['name']; - } - } - } - - return $result; - } -} diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/DataMapperFactory.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/DataMapperFactory.php index 29bdb036e206d..212c0f7b7af9b 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/DataMapperFactory.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/DataMapperFactory.php @@ -11,7 +11,7 @@ use Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface; /** - * Data mapper factory + * Data mapper factory uses to create appropriate mapper class */ class DataMapperFactory { diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/DataMapperResolver.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/DataMapperResolver.php index fd7a64eb0c9b7..b0a5b805e387f 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/DataMapperResolver.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/DataMapperResolver.php @@ -34,7 +34,7 @@ public function __construct(DataMapperFactory $dataMapperFactory) } /** - * {@inheritdoc} + * @inheritdoc */ public function map(array $documentData, $storeId, array $context = []) { diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php index 56c84593256be..f03a7e67212e3 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php @@ -11,7 +11,6 @@ use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface; use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; /** @@ -48,23 +47,21 @@ class PriceFieldsProvider implements AdditionalFieldsProviderInterface * @param Index $resourceIndex * @param DataProvider $dataProvider * @param StoreManagerInterface $storeManager - * @param AttributeProvider|null $attributeAdapterProvider - * @param ResolverInterface|null $fieldNameResolver + * @param AttributeProvider $attributeAdapterProvider + * @param ResolverInterface $fieldNameResolver */ public function __construct( Index $resourceIndex, DataProvider $dataProvider, StoreManagerInterface $storeManager, - AttributeProvider $attributeAdapterProvider = null, - ResolverInterface $fieldNameResolver = null + AttributeProvider $attributeAdapterProvider, + ResolverInterface $fieldNameResolver ) { $this->resourceIndex = $resourceIndex; $this->dataProvider = $dataProvider; $this->storeManager = $storeManager; - $this->attributeAdapterProvider = $attributeAdapterProvider ?: ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(ResolverInterface::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; + $this->fieldNameResolver = $fieldNameResolver; } /** @@ -73,7 +70,7 @@ public function __construct( public function getFields(array $productIds, $storeId) { $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId(); - + $priceData = $this->dataProvider->getSearchableAttribute('price') ? $this->resourceIndex->getPriceIndexData($productIds, $storeId) : []; diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/DataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/DataMapper/ProductDataMapper.php deleted file mode 100644 index 24b740b554fcb..0000000000000 --- a/app/code/Magento/Elasticsearch/Model/Adapter/DataMapper/ProductDataMapper.php +++ /dev/null @@ -1,19 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Model\Adapter\DataMapper; - -use Magento\Elasticsearch\Model\Adapter\DataMapperInterface; -use Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper as ElasticSearch5ProductDataMapper; - -/** - * Don't use this product data mapper class. - * - * @deprecated 100.2.0 - * @see \Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface - */ -class ProductDataMapper extends ElasticSearch5ProductDataMapper implements DataMapperInterface -{ -} diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php index fa193d86c03c7..0640b61f9551e 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php @@ -6,8 +6,6 @@ namespace Magento\Elasticsearch\Model\Adapter; -use Magento\Framework\App\ObjectManager; - /** * Elasticsearch adapter * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -53,7 +51,7 @@ class Elasticsearch protected $clientConfig; /** - * @var \Magento\Elasticsearch\Model\Client\Elasticsearch + * @var \Magento\AdvancedSearch\Model\Client\ClientInterface */ protected $client; @@ -78,17 +76,17 @@ class Elasticsearch private $batchDocumentDataMapper; /** - * Constructor for Elasticsearch adapter. + * Elasticsearch constructor. * * @param \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager * @param DataMapperInterface $documentDataMapper * @param FieldMapperInterface $fieldMapper * @param \Magento\Elasticsearch\Model\Config $clientConfig - * @param \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface $indexBuilder + * @param Index\BuilderInterface $indexBuilder * @param \Psr\Log\LoggerInterface $logger - * @param \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver $indexNameResolver - * @param array $options + * @param Index\IndexNameResolver $indexNameResolver * @param BatchDataMapperInterface $batchDocumentDataMapper + * @param array $options * @throws \Magento\Framework\Exception\LocalizedException */ public function __construct( @@ -99,8 +97,8 @@ public function __construct( \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface $indexBuilder, \Psr\Log\LoggerInterface $logger, \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver $indexNameResolver, - $options = [], - BatchDataMapperInterface $batchDocumentDataMapper = null + BatchDataMapperInterface $batchDocumentDataMapper, + $options = [] ) { $this->connectionManager = $connectionManager; $this->documentDataMapper = $documentDataMapper; @@ -109,8 +107,7 @@ public function __construct( $this->indexBuilder = $indexBuilder; $this->logger = $logger; $this->indexNameResolver = $indexNameResolver; - $this->batchDocumentDataMapper = $batchDocumentDataMapper ?: - ObjectManager::getInstance()->get(BatchDataMapperInterface::class); + $this->batchDocumentDataMapper = $batchDocumentDataMapper; try { $this->client = $this->connectionManager->getConnection($options); diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php index 76bc7a15e47a7..8dfe34d765e4e 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php @@ -7,7 +7,6 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider; -use Magento\Catalog\Api\CategoryListInterface; use Magento\Customer\Api\GroupRepositoryInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; @@ -19,21 +18,12 @@ as FieldNameResolver; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Catalog\Model\ResourceModel\Category\Collection; -use Magento\Framework\App\ObjectManager; /** * Provide dynamic fields for product. */ class DynamicField implements FieldProviderInterface { - /** - * Category list. - * - * @deprecated - * @var CategoryListInterface - */ - private $categoryList; - /** * Category collection. * @@ -80,30 +70,26 @@ class DynamicField implements FieldProviderInterface * @param IndexTypeConverterInterface $indexTypeConverter * @param GroupRepositoryInterface $groupRepository * @param SearchCriteriaBuilder $searchCriteriaBuilder - * @param CategoryListInterface $categoryList * @param FieldNameResolver $fieldNameResolver * @param AttributeProvider $attributeAdapterProvider - * @param Collection|null $categoryCollection + * @param Collection $categoryCollection */ public function __construct( FieldTypeConverterInterface $fieldTypeConverter, IndexTypeConverterInterface $indexTypeConverter, GroupRepositoryInterface $groupRepository, SearchCriteriaBuilder $searchCriteriaBuilder, - CategoryListInterface $categoryList, FieldNameResolver $fieldNameResolver, AttributeProvider $attributeAdapterProvider, - Collection $categoryCollection = null + Collection $categoryCollection ) { $this->groupRepository = $groupRepository; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->fieldTypeConverter = $fieldTypeConverter; $this->indexTypeConverter = $indexTypeConverter; - $this->categoryList = $categoryList; $this->fieldNameResolver = $fieldNameResolver; $this->attributeAdapterProvider = $attributeAdapterProvider; - $this->categoryCollection = $categoryCollection ?: - ObjectManager::getInstance()->get(Collection::class); + $this->categoryCollection = $categoryCollection; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CategoryName.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CategoryName.php index 5824aca6cdd54..3ba2143f0e761 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CategoryName.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/CategoryName.php @@ -11,7 +11,6 @@ use Magento\Framework\Registry; use Magento\Store\Model\StoreManagerInterface as StoreManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; -use Magento\Framework\App\ObjectManager; /** * Resolver field name for Category name attribute. @@ -33,13 +32,11 @@ class CategoryName implements ResolverInterface * @param Registry $coreRegistry */ public function __construct( - StoreManager $storeManager = null, - Registry $coreRegistry = null + StoreManager $storeManager, + Registry $coreRegistry ) { - $this->storeManager = $storeManager ?: ObjectManager::getInstance() - ->get(StoreManager::class); - $this->coreRegistry = $coreRegistry ?: ObjectManager::getInstance() - ->get(Registry::class); + $this->storeManager = $storeManager; + $this->coreRegistry = $coreRegistry; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Position.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Position.php index 044d5d8da9a6c..a67153c6669b2 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Position.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Position.php @@ -11,7 +11,6 @@ use Magento\Framework\Registry; use Magento\Store\Model\StoreManagerInterface as StoreManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; -use Magento\Framework\App\ObjectManager; /** * Resolver field name for position attribute. @@ -33,13 +32,11 @@ class Position implements ResolverInterface * @param Registry $coreRegistry */ public function __construct( - StoreManager $storeManager = null, - Registry $coreRegistry = null + StoreManager $storeManager, + Registry $coreRegistry ) { - $this->storeManager = $storeManager ?: ObjectManager::getInstance() - ->get(StoreManager::class); - $this->coreRegistry = $coreRegistry ?: ObjectManager::getInstance() - ->get(Registry::class); + $this->storeManager = $storeManager; + $this->coreRegistry = $coreRegistry; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Price.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Price.php index 12e53ca2bd714..ddf1552e4a202 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Price.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/Price.php @@ -7,7 +7,6 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver; -use Magento\Framework\App\ObjectManager; use Magento\Customer\Model\Session as CustomerSession; use Magento\Store\Model\StoreManagerInterface as StoreManager; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; @@ -15,6 +14,7 @@ /** * Resolver field name for price attribute. + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Price implements ResolverInterface { @@ -33,13 +33,11 @@ class Price implements ResolverInterface * @param StoreManager $storeManager */ public function __construct( - CustomerSession $customerSession = null, - StoreManager $storeManager = null + CustomerSession $customerSession, + StoreManager $storeManager ) { - $this->storeManager = $storeManager ?: ObjectManager::getInstance() - ->get(StoreManager::class); - $this->customerSession = $customerSession ?: ObjectManager::getInstance() - ->get(CustomerSession::class); + $this->storeManager = $storeManager; + $this->customerSession = $customerSession; } /** diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php index 7a5d6fcdcc1b1..f032e561d8738 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php @@ -7,7 +7,6 @@ namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider; -use Magento\Framework\App\ObjectManager; use Magento\Eav\Model\Config; use Magento\Catalog\Api\Data\ProductAttributeInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; @@ -74,7 +73,7 @@ class StaticField implements FieldProviderInterface * @param FieldTypeResolver $fieldTypeResolver * @param FieldIndexResolver $fieldIndexResolver * @param AttributeProvider $attributeAdapterProvider - * @param FieldName\ResolverInterface|null $fieldNameResolver + * @param FieldName\ResolverInterface $fieldNameResolver * @param array $excludedAttributes */ public function __construct( @@ -84,7 +83,7 @@ public function __construct( FieldTypeResolver $fieldTypeResolver, FieldIndexResolver $fieldIndexResolver, AttributeProvider $attributeAdapterProvider, - FieldName\ResolverInterface $fieldNameResolver = null, + FieldName\ResolverInterface $fieldNameResolver, array $excludedAttributes = [] ) { $this->eavConfig = $eavConfig; @@ -93,8 +92,7 @@ public function __construct( $this->fieldTypeResolver = $fieldTypeResolver; $this->fieldIndexResolver = $fieldIndexResolver; $this->attributeAdapterProvider = $attributeAdapterProvider; - $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance() - ->get(FieldName\ResolverInterface::class); + $this->fieldNameResolver = $fieldNameResolver; $this->excludedAttributes = $excludedAttributes; } diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/ProductFieldMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/ProductFieldMapper.php deleted file mode 100644 index 657605bbd019b..0000000000000 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/ProductFieldMapper.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Model\Adapter\FieldMapper; - -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; -use Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapper - as Elasticsearch5ProductFieldMapper; - -/** - * Class ProductFieldMapper - */ -class ProductFieldMapper extends Elasticsearch5ProductFieldMapper implements FieldMapperInterface -{ -} diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/Index/IndexNameResolver.php b/app/code/Magento/Elasticsearch/Model/Adapter/Index/IndexNameResolver.php index f69f7001d5bce..40d3ac95cf49c 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/Index/IndexNameResolver.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/Index/IndexNameResolver.php @@ -6,7 +6,7 @@ namespace Magento\Elasticsearch\Model\Adapter\Index; -use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; +use Magento\AdvancedSearch\Model\Client\ClientInterface as ElasticsearchClient; use Magento\Elasticsearch\SearchAdapter\ConnectionManager; use Magento\Elasticsearch\Model\Config; use Psr\Log\LoggerInterface; @@ -14,7 +14,7 @@ use Magento\CatalogSearch\Model\Indexer\Fulltext; /** - * Index name resolver + * Index name resolver for Elasticsearch * @api * @since 100.1.0 */ diff --git a/app/code/Magento/Elasticsearch/Model/Config.php b/app/code/Magento/Elasticsearch/Model/Config.php index 0bf23f318c3bd..962c19595c4c0 100644 --- a/app/code/Magento/Elasticsearch/Model/Config.php +++ b/app/code/Magento/Elasticsearch/Model/Config.php @@ -7,11 +7,9 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Search\EngineResolverInterface; -use Magento\Search\Model\EngineResolver; use Magento\Store\Model\ScopeInterface; use Magento\AdvancedSearch\Model\Client\ClientOptionsInterface; use Magento\AdvancedSearch\Model\Client\ClientResolver; -use Magento\Framework\App\ObjectManager; /** * Elasticsearch config model @@ -69,22 +67,23 @@ class Config implements ClientOptionsInterface private $engineList; /** + * Config constructor. * @param ScopeConfigInterface $scopeConfig - * @param ClientResolver|null $clientResolver - * @param EngineResolverInterface|null $engineResolver + * @param ClientResolver $clientResolver + * @param EngineResolverInterface $engineResolver * @param string|null $prefix * @param array $engineList */ public function __construct( ScopeConfigInterface $scopeConfig, - ClientResolver $clientResolver = null, - EngineResolverInterface $engineResolver = null, + ClientResolver $clientResolver, + EngineResolverInterface $engineResolver, $prefix = null, $engineList = [] ) { $this->scopeConfig = $scopeConfig; - $this->clientResolver = $clientResolver ?: ObjectManager::getInstance()->get(ClientResolver::class); - $this->engineResolver = $engineResolver ?: ObjectManager::getInstance()->get(EngineResolverInterface::class); + $this->clientResolver = $clientResolver; + $this->engineResolver = $engineResolver; $this->prefix = $prefix ?: $this->clientResolver->getCurrentEngine(); $this->engineList = $engineList; } diff --git a/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php b/app/code/Magento/Elasticsearch/Model/DataProvider/Base/Suggestions.php similarity index 98% rename from app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php rename to app/code/Magento/Elasticsearch/Model/DataProvider/Base/Suggestions.php index d05471734bb8f..8364b6c116b7d 100644 --- a/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php +++ b/app/code/Magento/Elasticsearch/Model/DataProvider/Base/Suggestions.php @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Elasticsearch6\Model\DataProvider; +namespace Magento\Elasticsearch\Model\DataProvider\Base; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; use Magento\Store\Model\ScopeInterface; @@ -17,7 +17,7 @@ use Magento\Store\Model\StoreManagerInterface as StoreManager; /** - * Class Suggestions + * Default implementation to provide suggestions mechanism for Elasticsearch */ class Suggestions implements SuggestedQueriesInterface { diff --git a/app/code/Magento/Elasticsearch/Model/DataProvider/Suggestions.php b/app/code/Magento/Elasticsearch/Model/DataProvider/Suggestions.php index c4fab39dfde61..e0afdeb2ffb27 100644 --- a/app/code/Magento/Elasticsearch/Model/DataProvider/Suggestions.php +++ b/app/code/Magento/Elasticsearch/Model/DataProvider/Suggestions.php @@ -16,24 +16,27 @@ use Magento\Store\Model\StoreManagerInterface as StoreManager; /** - * Class Suggestions + * The implementation to provide suggestions mechanism for Elasticsearch5 + * + * @deprecated because of EOL for Elasticsearch5 + * @see \Magento\Elasticsearch\Model\DataProvider\Base\Suggestions */ class Suggestions implements SuggestedQueriesInterface { /** - * @deprecated + * @deprecated moved to interface * @see SuggestedQueriesInterface::SEARCH_SUGGESTION_COUNT */ const CONFIG_SUGGESTION_COUNT = 'catalog/search/search_suggestion_count'; /** - * @deprecated + * @deprecated moved to interface * @see SuggestedQueriesInterface::SEARCH_SUGGESTION_COUNT_RESULTS_ENABLED */ const CONFIG_SUGGESTION_COUNT_RESULTS_ENABLED = 'catalog/search/search_suggestion_count_results_enabled'; /** - * @deprecated + * @deprecated moved to interface * @see SuggestedQueriesInterface::SEARCH_SUGGESTION_ENABLED */ const CONFIG_SUGGESTION_ENABLED = 'catalog/search/search_suggestion_enabled'; diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php index 1e9b60da74a5b..9f7faf4aa13fa 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder.php @@ -7,22 +7,24 @@ namespace Magento\Elasticsearch\SearchAdapter\Aggregation; use Magento\Elasticsearch\SearchAdapter\QueryContainer; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Search\RequestInterface; use Magento\Framework\Search\Dynamic\DataProviderInterface; use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder\BucketBuilderInterface; +/** + * Builder class for Elasticsearch + */ class Builder { /** * @var DataProviderInterface[] */ - protected $dataProviderContainer; + private $dataProviderContainer; /** * @var BucketBuilderInterface[] */ - protected $aggregationContainer; + private $aggregationContainer; /** * @var DataProviderFactory @@ -37,12 +39,12 @@ class Builder /** * @param DataProviderInterface[] $dataProviderContainer * @param BucketBuilderInterface[] $aggregationContainer - * @param DataProviderFactory|null $dataProviderFactory + * @param DataProviderFactory $dataProviderFactory */ public function __construct( array $dataProviderContainer, array $aggregationContainer, - DataProviderFactory $dataProviderFactory = null + DataProviderFactory $dataProviderFactory ) { $this->dataProviderContainer = array_map( function (DataProviderInterface $dataProvider) { @@ -56,8 +58,7 @@ function (BucketBuilderInterface $bucketBuilder) { }, $aggregationContainer ); - $this->dataProviderFactory = $dataProviderFactory - ?: ObjectManager::getInstance()->get(DataProviderFactory::class); + $this->dataProviderFactory = $dataProviderFactory; } /** diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Interval.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Interval.php deleted file mode 100644 index 33ab1a4071560..0000000000000 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Interval.php +++ /dev/null @@ -1,287 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\SearchAdapter\Aggregation; - -use Magento\Framework\Search\Dynamic\IntervalInterface; -use Magento\Elasticsearch\SearchAdapter\ConnectionManager; -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; -use Magento\Elasticsearch\Model\Config; -use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; -use Magento\CatalogSearch\Model\Indexer\Fulltext; - -class Interval implements IntervalInterface -{ - /** - * Minimal possible value - */ - const DELTA = 0.005; - - /** - * @var ConnectionManager - */ - protected $connectionManager; - - /** - * @var FieldMapperInterface - */ - protected $fieldMapper; - - /** - * @var Config - */ - protected $clientConfig; - - /** - * @var string - */ - private $fieldName; - - /** - * @var string - */ - private $storeId; - - /** - * @var array - */ - private $entityIds; - - /** - * @var SearchIndexNameResolver - */ - private $searchIndexNameResolver; - - /** - * @param ConnectionManager $connectionManager - * @param FieldMapperInterface $fieldMapper - * @param Config $clientConfig - * @param SearchIndexNameResolver $searchIndexNameResolver - * @param string $fieldName - * @param string $storeId - * @param array $entityIds - */ - public function __construct( - ConnectionManager $connectionManager, - FieldMapperInterface $fieldMapper, - Config $clientConfig, - SearchIndexNameResolver $searchIndexNameResolver, - $fieldName, - $storeId, - $entityIds - ) { - $this->connectionManager = $connectionManager; - $this->fieldMapper = $fieldMapper; - $this->clientConfig = $clientConfig; - $this->fieldName = $fieldName; - $this->storeId = $storeId; - $this->entityIds = $entityIds; - $this->searchIndexNameResolver = $searchIndexNameResolver; - } - - /** - * {@inheritdoc} - */ - public function load($limit, $offset = null, $lower = null, $upper = null) - { - $from = $to = []; - if ($lower) { - $from = ['gte' => $lower - self::DELTA]; - } - if ($upper) { - $to = ['lt' => $upper - self::DELTA]; - } - - $requestQuery = [ - 'index' => $this->searchIndexNameResolver->getIndexName($this->storeId, Fulltext::INDEXER_ID), - 'type' => $this->clientConfig->getEntityType(), - 'body' => [ - 'fields' => [ - '_id', - $this->fieldName, - ], - 'query' => [ - 'filtered' => [ - 'query' => [ - 'match_all' => [], - ], - 'filter' => [ - 'bool' => [ - 'must' => [ - [ - 'terms' => [ - '_id' => $this->entityIds, - ], - ], - [ - 'range' => [ - $this->fieldName => array_merge($from, $to), - ], - ], - ], - ], - ], - ], - ], - 'sort' => [ - $this->fieldName, - ], - 'size' => $limit, - ], - ]; - if ($offset) { - $requestQuery['body']['from'] = $offset; - } - $queryResult = $this->connectionManager->getConnection() - ->query($requestQuery); - - return $this->arrayValuesToFloat($queryResult['hits']['hits'], $this->fieldName); - } - - /** - * {@inheritdoc} - */ - public function loadPrevious($data, $index, $lower = null) - { - if ($lower) { - $from = ['gte' => $lower - self::DELTA]; - } - if ($data) { - $to = ['lt' => $data - self::DELTA]; - } - - $requestQuery = [ - 'index' => $this->searchIndexNameResolver->getIndexName($this->storeId, Fulltext::INDEXER_ID), - 'type' => $this->clientConfig->getEntityType(), - 'search_type' => 'count', - 'body' => [ - 'fields' => [ - '_id' - ], - 'query' => [ - 'filtered' => [ - 'query' => [ - 'match_all' => [], - ], - 'filter' => [ - 'bool' => [ - 'must' => [ - [ - 'terms' => [ - '_id' => $this->entityIds, - ], - ], - [ - 'range' => [ - $this->fieldName => array_merge($from, $to), - ], - ], - ], - ], - ], - ], - ], - 'sort' => [ - $this->fieldName, - ], - ], - ]; - $queryResult = $this->connectionManager->getConnection() - ->query($requestQuery); - - $offset = $queryResult['hits']['total']; - if (!$offset) { - return false; - } - - return $this->load($index - $offset + 1, $offset - 1, $lower); - } - - /** - * {@inheritdoc} - */ - public function loadNext($data, $rightIndex, $upper = null) - { - $from = ['gt' => $data + self::DELTA]; - $to = ['lt' => $data - self::DELTA]; - - $requestCountQuery = [ - 'index' => $this->searchIndexNameResolver->getIndexName($this->storeId, Fulltext::INDEXER_ID), - 'type' => $this->clientConfig->getEntityType(), - 'search_type' => 'count', - 'body' => [ - 'fields' => [ - '_id' - ], - 'query' => [ - 'filtered' => [ - 'query' => [ - 'match_all' => [], - ], - 'filter' => [ - 'bool' => [ - 'must' => [ - [ - 'terms' => [ - '_id' => $this->entityIds, - ], - ], - [ - 'range' => [ - $this->fieldName => array_merge($from, $to), - ], - ], - ], - ], - ], - ], - ], - 'sort' => [ - $this->fieldName, - ], - ], - ]; - $queryCountResult = $this->connectionManager->getConnection() - ->query($requestCountQuery); - - $offset = $queryCountResult['hits']['total']; - if (!$offset) { - return false; - } - - $from = ['gte' => $data - self::DELTA]; - if ($upper !== null) { - $to = ['lt' => $data - self::DELTA]; - } - - $requestQuery = $requestCountQuery; - $requestCountQuery['body']['query']['filtered']['filter']['bool']['must']['range'] = - [$this->fieldName => array_merge($from, $to)]; - - $requestCountQuery['body']['from'] = $offset - 1; - $requestCountQuery['body']['size'] = $rightIndex - $offset + 1; - - $queryResult = $this->connectionManager->getConnection() - ->query($requestQuery); - - return array_reverse($this->arrayValuesToFloat($queryResult['hits']['hits'], $this->fieldName)); - } - - /** - * @param array $hits - * @param string $fieldName - * - * @return float[] - */ - private function arrayValuesToFloat($hits, $fieldName) - { - $returnPrices = []; - foreach ($hits as $hit) { - $returnPrices[] = (float) $hit['fields'][$fieldName][0]; - } - - return $returnPrices; - } -} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/ConnectionManager.php b/app/code/Magento/Elasticsearch/SearchAdapter/ConnectionManager.php index 77c42077323d4..57973b7fb92e4 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/ConnectionManager.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/ConnectionManager.php @@ -7,10 +7,12 @@ use Magento\AdvancedSearch\Model\Client\ClientOptionsInterface; use Magento\AdvancedSearch\Model\Client\ClientFactoryInterface; -use Magento\Elasticsearch\Model\Client\Elasticsearch; +use Magento\AdvancedSearch\Model\Client\ClientInterface as Elasticsearch; use Psr\Log\LoggerInterface; /** + * Class provides interface for Elasticsearch connection + * * @api * @since 100.1.0 */ diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/DocumentFactory.php b/app/code/Magento/Elasticsearch/SearchAdapter/DocumentFactory.php index 83652e08d4246..3372a2c26ae0e 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/DocumentFactory.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/DocumentFactory.php @@ -5,7 +5,6 @@ */ namespace Magento\Elasticsearch\SearchAdapter; -use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Search\EntityMetadata; use Magento\Framework\Api\AttributeInterface; use Magento\Framework\Api\AttributeValue; @@ -14,33 +13,22 @@ use Magento\Framework\Api\Search\DocumentInterface; /** - * Document Factory + * Document Factory to create Search Document instance * @api * @since 100.1.0 */ class DocumentFactory { - /** - * Object Manager instance - * - * @var ObjectManagerInterface - * @deprecated 100.1.0 - * @since 100.1.0 - */ - protected $objectManager; - /** * @var EntityMetadata */ private $entityMetadata; /** - * @param ObjectManagerInterface $objectManager * @param EntityMetadata $entityMetadata */ - public function __construct(ObjectManagerInterface $objectManager, EntityMetadata $entityMetadata) + public function __construct(EntityMetadata $entityMetadata) { - $this->objectManager = $objectManager; $this->entityMetadata = $entityMetadata; } diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder.php b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder.php index 8efc342d26452..db6bb3cc7c413 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder.php @@ -12,32 +12,15 @@ use Magento\Elasticsearch\SearchAdapter\Filter\Builder\Term; use Magento\Elasticsearch\SearchAdapter\Filter\Builder\Wildcard; +/** + * Class Builder to build Elasticsearch filter + */ class Builder implements BuilderInterface { - /** - * Text flag for Elasticsearch filter query condition types - * - * @deprecated - * @see BuilderInterface::FILTER_QUERY_CONDITION_MUST - */ - const QUERY_CONDITION_MUST = 'must'; - - /** - * @deprecated - * @see BuilderInterface::FILTER_QUERY_CONDITION_SHOULD - */ - const QUERY_CONDITION_SHOULD = 'should'; - - /** - * @deprecated - * @see BuilderInterface::FILTER_QUERY_CONDITION_MUST_NOT - */ - const QUERY_CONDITION_MUST_NOT = 'must_not'; - /** * @var FilterInterface[] */ - protected $filters; + private $filters; /** * @param Range $range @@ -57,7 +40,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function build(RequestFilterInterface $filter, $conditionType) { @@ -65,11 +48,13 @@ public function build(RequestFilterInterface $filter, $conditionType) } /** + * Processes filter object + * * @param RequestFilterInterface $filter * @param string $conditionType * @return array */ - protected function processFilter(RequestFilterInterface $filter, $conditionType) + private function processFilter(RequestFilterInterface $filter, $conditionType) { if (RequestFilterInterface::TYPE_BOOL == $filter->getType()) { $query = $this->processBoolFilter($filter, $this->isNegation($conditionType)); @@ -88,6 +73,8 @@ protected function processFilter(RequestFilterInterface $filter, $conditionType) } /** + * Processes filter + * * @param RequestFilterInterface|BoolExpression $filter * @param bool $isNegation * @return array @@ -96,12 +83,12 @@ protected function processBoolFilter(RequestFilterInterface $filter, $isNegation { $must = $this->buildFilters( $filter->getMust(), - $this->mapConditionType(self::QUERY_CONDITION_MUST, $isNegation) + $this->mapConditionType(BuilderInterface::FILTER_QUERY_CONDITION_MUST, $isNegation) ); - $should = $this->buildFilters($filter->getShould(), self::QUERY_CONDITION_SHOULD); + $should = $this->buildFilters($filter->getShould(), BuilderInterface::FILTER_QUERY_CONDITION_SHOULD); $mustNot = $this->buildFilters( $filter->getMustNot(), - $this->mapConditionType(self::QUERY_CONDITION_MUST_NOT, $isNegation) + $this->mapConditionType(BuilderInterface::FILTER_QUERY_CONDITION_MUST_NOT, $isNegation) ); $queries = [ @@ -116,6 +103,8 @@ protected function processBoolFilter(RequestFilterInterface $filter, $isNegation } /** + * Build filters + * * @param RequestFilterInterface[] $filters * @param string $conditionType * @return string @@ -126,25 +115,31 @@ private function buildFilters(array $filters, $conditionType) foreach ($filters as $filter) { $filterQuery = $this->processFilter($filter, $conditionType); if (isset($filterQuery['bool'][$conditionType])) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $queries['bool'][$conditionType] = array_merge( isset($queries['bool'][$conditionType]) ? $queries['bool'][$conditionType] : [], $filterQuery['bool'][$conditionType] ); } } + return $queries; } /** + * Returns is condition type navigation + * * @param string $conditionType * @return bool */ - protected function isNegation($conditionType) + private function isNegation($conditionType) { - return self::QUERY_CONDITION_MUST_NOT === $conditionType; + return BuilderInterface::FILTER_QUERY_CONDITION_MUST_NOT === $conditionType; } /** + * Maps condition type + * * @param string $conditionType * @param bool $isNegation * @return string @@ -152,10 +147,10 @@ protected function isNegation($conditionType) private function mapConditionType($conditionType, $isNegation) { if ($isNegation) { - if ($conditionType == self::QUERY_CONDITION_MUST) { - $conditionType = self::QUERY_CONDITION_MUST_NOT; - } elseif ($conditionType == self::QUERY_CONDITION_MUST_NOT) { - $conditionType = self::QUERY_CONDITION_MUST; + if ($conditionType == BuilderInterface::FILTER_QUERY_CONDITION_MUST) { + $conditionType = BuilderInterface::FILTER_QUERY_CONDITION_MUST_NOT; + } elseif ($conditionType == BuilderInterface::FILTER_QUERY_CONDITION_MUST_NOT) { + $conditionType = BuilderInterface::FILTER_QUERY_CONDITION_MUST; } } return $conditionType; diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php index d88c7e53d813a..be6ccce4a6b8c 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php @@ -6,7 +6,6 @@ namespace Magento\Elasticsearch\SearchAdapter\Filter\Builder; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Search\Request\Filter\Term as TermFilterRequest; use Magento\Framework\Search\Request\FilterInterface as RequestFilterInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; @@ -21,7 +20,7 @@ class Term implements FilterInterface /** * @var FieldMapperInterface */ - protected $fieldMapper; + private $fieldMapper; /** * @var AttributeProvider @@ -41,12 +40,11 @@ class Term implements FilterInterface */ public function __construct( FieldMapperInterface $fieldMapper, - AttributeProvider $attributeAdapterProvider = null, + AttributeProvider $attributeAdapterProvider, array $integerTypeAttributes = [] ) { $this->fieldMapper = $fieldMapper; - $this->attributeAdapterProvider = $attributeAdapterProvider - ?? ObjectManager::getInstance()->get(AttributeProvider::class); + $this->attributeAdapterProvider = $attributeAdapterProvider; $this->integerTypeAttributes = array_merge($this->integerTypeAttributes, $integerTypeAttributes); } diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Mapper.php b/app/code/Magento/Elasticsearch/SearchAdapter/Mapper.php deleted file mode 100644 index fd6ae3424b9da..0000000000000 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Mapper.php +++ /dev/null @@ -1,64 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\SearchAdapter; - -use Magento\Framework\Search\RequestInterface; -use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface; -use Magento\Framework\Search\Request\Query\BoolExpression as BoolQuery; -use Magento\Framework\Search\Request\Query\Filter as FilterQuery; -use Magento\Framework\Search\Request\Query\Match as MatchQuery; -use Magento\Elasticsearch\SearchAdapter\Query\Builder as QueryBuilder; -use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder; -use Magento\Elasticsearch\SearchAdapter\Filter\Builder as FilterBuilder; -use Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Mapper as Elasticsearch5Mapper; - -/** - * Mapper class - * @api - * @since 100.1.0 - */ -class Mapper extends Elasticsearch5Mapper -{ - /** - * @param QueryBuilder $queryBuilder - * @param MatchQueryBuilder $matchQueryBuilder - * @param FilterBuilder $filterBuilder - */ - public function __construct( - QueryBuilder $queryBuilder, - MatchQueryBuilder $matchQueryBuilder, - FilterBuilder $filterBuilder - ) { - $this->queryBuilder = $queryBuilder; - $this->matchQueryBuilder = $matchQueryBuilder; - $this->filterBuilder = $filterBuilder; - } - - /** - * Build adapter dependent query - * - * @param RequestInterface $request - * @return array - * @since 100.1.0 - */ - public function buildQuery(RequestInterface $request) - { - $searchQuery = $this->queryBuilder->initQuery($request); - $searchQuery['body']['query'] = array_merge( - $searchQuery['body']['query'], - $this->processQuery( - $request->getQuery(), - [], - BoolQuery::QUERY_CONDITION_MUST - ) - ); - - $searchQuery['body']['query']['bool']['minimum_should_match'] = 1; - - $searchQuery = $this->queryBuilder->initAggregations($request, $searchQuery); - return $searchQuery; - } -} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php index 8a44b58d35fb8..1012918b15a8b 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php @@ -9,7 +9,6 @@ use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver; use Magento\Elasticsearch\Model\Config; use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Search\Request\Query\BoolExpression; use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; @@ -30,13 +29,6 @@ class Match implements QueryInterface */ private $fieldMapper; - /** - * @deprecated - * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer - * @var PreprocessorInterface[] - */ - protected $preprocessorContainer; - /** * @var AttributeProvider */ @@ -58,29 +50,23 @@ class Match implements QueryInterface /** * @param FieldMapperInterface $fieldMapper - * @param PreprocessorInterface[] $preprocessorContainer - * @param AttributeProvider|null $attributeProvider - * @param TypeResolver|null $fieldTypeResolver - * @param ValueTransformerPool|null $valueTransformerPool - * @param Config|null $config + * @param AttributeProvider $attributeProvider + * @param TypeResolver $fieldTypeResolver + * @param ValueTransformerPool $valueTransformerPool + * @param Config $config */ public function __construct( FieldMapperInterface $fieldMapper, - array $preprocessorContainer, - AttributeProvider $attributeProvider = null, - TypeResolver $fieldTypeResolver = null, - ValueTransformerPool $valueTransformerPool = null, - Config $config = null + AttributeProvider $attributeProvider, + TypeResolver $fieldTypeResolver, + ValueTransformerPool $valueTransformerPool, + Config $config ) { $this->fieldMapper = $fieldMapper; - $this->preprocessorContainer = $preprocessorContainer; - $this->attributeProvider = $attributeProvider ?? ObjectManager::getInstance() - ->get(AttributeProvider::class); - $this->fieldTypeResolver = $fieldTypeResolver ?? ObjectManager::getInstance() - ->get(TypeResolver::class); - $this->valueTransformerPool = $valueTransformerPool ?? ObjectManager::getInstance() - ->get(ValueTransformerPool::class); - $this->config = $config ?? ObjectManager::getInstance()->get(Config::class); + $this->attributeProvider = $attributeProvider; + $this->fieldTypeResolver = $fieldTypeResolver; + $this->valueTransformerPool = $valueTransformerPool; + $this->config = $config; } /** @@ -183,20 +169,4 @@ protected function buildQueries(array $matches, array $queryValue) return $conditions; } - - /** - * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. - * - * @deprecated - * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer - * @param string $value - * @return string - */ - protected function escape($value) - { - $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/'; - $replace = '\\\$1'; - - return preg_replace($pattern, $replace, $value); - } } diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/ActionGroup/AssertSearchResultActionGroup.xml b/app/code/Magento/Elasticsearch/Test/Mftf/ActionGroup/AssertSearchResultActionGroup.xml new file mode 100644 index 0000000000000..5a2aba8df9f91 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Mftf/ActionGroup/AssertSearchResultActionGroup.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertSearchResultActionGroup"> + <annotations> + <description>Check search result on Storefront</description> + </annotations> + <arguments> + <argument name="keyword" defaultValue="Simple Product A" type="string"/> + <argument name="product" type="string" defaultValue="Simple Product A"/> + </arguments> + <fillField userInput="{{keyword}}" selector="{{StorefrontQuickSearchSection.searchPhrase}}" stepKey="fillSearchBar"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <seeInCurrentUrl url="{{StorefrontCatalogSearchPage.url}}" stepKey="checkUrl"/> + <seeElement selector="{{StorefrontCategoryMainSection.specifiedProductItemInfo(product)}}" stepKey="foundProductAOnPage"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/ActionGroup/ModifyCustomAttributeValueActionGroup.xml b/app/code/Magento/Elasticsearch/Test/Mftf/ActionGroup/ModifyCustomAttributeValueActionGroup.xml new file mode 100644 index 0000000000000..6389ca31ba534 --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Mftf/ActionGroup/ModifyCustomAttributeValueActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ModifyCustomAttributeValueActionGroup"> + <annotations> + <description>Update value for custom attribute</description> + </annotations> + <arguments> + <argument name="attributeValue" defaultValue="3.5" type="string"/> + <argument name="product" type="string" defaultValue="SKU0001"/> + </arguments> + <click selector="{{AdminProductGridSection.productRowBySku(product)}}" stepKey="clickOnSimpleProductRow"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{AdminProductMultiselectAttributeSection.option(attributeValue)}}" stepKey="selectFirstOption"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="save"/> + <waitForPageLoad stepKey="waitForProductSaved"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Data/ConfigData.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Data/ConfigData.xml similarity index 75% rename from app/code/Magento/Elasticsearch6/Test/Mftf/Data/ConfigData.xml rename to app/code/Magento/Elasticsearch/Test/Mftf/Data/ConfigData.xml index 03a1c63f71515..5a1118d079158 100644 --- a/app/code/Magento/Elasticsearch6/Test/Mftf/Data/ConfigData.xml +++ b/app/code/Magento/Elasticsearch/Test/Mftf/Data/ConfigData.xml @@ -10,7 +10,7 @@ <entity name="SearchEngineElasticsearchConfigData"> <data key="path">catalog/search/engine</data> <data key="scope_id">1</data> - <data key="label">Elasticsearch 6.x</data> - <data key="value">elasticsearch6</data> + <data key="label">Elasticsearch {{_ENV.ELASTICSEARCH_VERSION}}.0+</data> + <data key="value">elasticsearch{{_ENV.ELASTICSEARCH_VERSION}}</data> </entity> </entities> diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml similarity index 100% rename from app/code/Magento/Elasticsearch6/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml rename to app/code/Magento/Elasticsearch/Test/Mftf/Suite/SearchEngineElasticsearchSuite.xml diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml index 9fcc1909ab42c..a59e260bc4d94 100644 --- a/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml +++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml @@ -17,6 +17,7 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-94995"/> <group value="Catalog"/> + <group value="SearchEngineElasticsearch" /> </annotations> <before> <createData entity="SimpleSubCategory" stepKey="categoryFirst"/> @@ -28,16 +29,16 @@ <after> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> <deleteData createDataKey="categoryFirst" stepKey="deleteCategory"/> - <actionGroup ref="ResetSearchEngineConfigurationActionGroup" stepKey="resetCatalogSearchConfiguration"/> - <actionGroup ref="UpdateIndexerOnSaveActionGroup" stepKey="resetIndexerBackToOriginalState"> + <comment userInput="The test was moved to elasticsearch suite" stepKey="resetCatalogSearchConfiguration"/> + <actionGroup ref="updateIndexerOnSave" stepKey="resetIndexerBackToOriginalState"> <argument name="indexerName" value="catalogsearch_fulltext"/> </actionGroup> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <comment userInput="Change Catalog search engine option to Elastic Search 5.x" stepKey="chooseElasticSearch5"/> - <actionGroup ref="ChooseElasticSearchAsSearchEngineActionGroup" stepKey="chooseES5"/> + <comment userInput="Change Catalog search engine option to Elastic Search 5.0+" stepKey="chooseElasticSearch5"/> + <comment userInput="The test was moved to elasticsearch suite" stepKey="chooseES5"/> <actionGroup ref="ClearPageCacheActionGroup" stepKey="clearing"/> <actionGroup ref="UpdateIndexerByScheduleActionGroup" stepKey="updateAnIndexerBySchedule"> <argument name="indexerName" value="catalogsearch_fulltext"/> diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchWithDecimalAttributeUsingElasticSearchTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchWithDecimalAttributeUsingElasticSearchTest.xml new file mode 100644 index 0000000000000..c48f0d63b06ca --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/StorefrontProductQuickSearchWithDecimalAttributeUsingElasticSearchTest.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontProductQuickSearchWithDecimalAttributeUsingElasticSearchTest"> + <annotations> + <features value="Search"/> + <stories value="Elasticsearch 7.x.x Upgrade"/> + <title value="Search decimal attribute with ElasticSearch"/> + <description value="User should be able to search decimal attribute using ElasticSearch"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-31239"/> + <group value="SearchEngineElasticsearch" /> + </annotations> + <before> + <!-- Create product attribute with 3 options --> + <createData entity="multipleSelectProductAttribute" stepKey="customAttribute"/> + <createData entity="ProductAttributeOption10" stepKey="option1"> + <requiredEntity createDataKey="customAttribute"/> + </createData> + <createData entity="ProductAttributeOption11" stepKey="option2"> + <requiredEntity createDataKey="customAttribute"/> + </createData> + <createData entity="ProductAttributeOption12" stepKey="option3"> + <requiredEntity createDataKey="customAttribute"/> + </createData> + + <!-- Add custom attribute to Default Set --> + <createData entity="AddToDefaultSet" stepKey="addToDefaultAttributeSet"> + <requiredEntity createDataKey="customAttribute"/> + </createData> + + <!-- Create subcategory --> + <createData entity="SimpleSubCategory" stepKey="newCategory"/> + <createData entity="SimpleProduct" stepKey="product1"> + <requiredEntity createDataKey="newCategory"/> + <requiredEntity createDataKey="customAttribute"/> + </createData> + <createData entity="ApiSimpleProduct" stepKey="product2"> + <requiredEntity createDataKey="newCategory"/> + <requiredEntity createDataKey="customAttribute"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="product1" stepKey="deleteProduct1"/> + <deleteData createDataKey="product2" stepKey="deleteProduct2"/> + <deleteData createDataKey="newCategory" stepKey="deleteCategory"/> + <!--Delete attribute--> + <deleteData createDataKey="customAttribute" stepKey="deleteCustomAttribute"/> + <!--Reindex and clear cache--> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:clean" stepKey="cleanCache"/> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + <!--Navigate to backend and update value for custom attribute --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$$product1$$"/> + </actionGroup> + <actionGroup ref="ModifyCustomAttributeValueActionGroup" stepKey="ModifyCustomAttributeValueProduct1"> + <argument name="attributeValue" value="3.5"/> + <argument name="product" value="$$product1.sku$$"/> + </actionGroup> + + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForApiSimpleProduct"> + <argument name="product" value="$$product2$$"/> + </actionGroup> + <actionGroup ref="ModifyCustomAttributeValueActionGroup" stepKey="ModifyCustomAttributeValueProduct2"> + <argument name="attributeValue" value="10.12"/> + <argument name="product" value="$$product2.sku$$"/> + </actionGroup> + + <!--Reindex and clear cache--> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:clean" stepKey="cleanCache"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToHomePage"/> + <waitForPageLoad stepKey="waitForHomePageToLoad"/> + + <!-- Navigate to Storefront and search --> + <actionGroup ref="AssertSearchResultActionGroup" stepKey="assertSearchResult0"> + <argument name="keyword" value="$$product1.name$$"/> + <argument name="product" value="$$product1.name$$"/> + </actionGroup> + <actionGroup ref="AssertSearchResultActionGroup" stepKey="assertSearchResult1"> + <argument name="keyword" value="3.5"/> + <argument name="product" value="$$product1.name$$"/> + </actionGroup> + <actionGroup ref="AssertSearchResultActionGroup" stepKey="assertSearchResult2"> + <argument name="keyword" value="10.12"/> + <argument name="product" value="$$product2.name$$"/> + </actionGroup> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/DataMapper/ProductDataMapperTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/DataMapper/ProductDataMapperTest.php deleted file mode 100644 index ced529660af06..0000000000000 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/DataMapper/ProductDataMapperTest.php +++ /dev/null @@ -1,400 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Test\Unit\Model\Adapter\DataMapper; - -use Magento\Catalog\Model\ResourceModel\Eav\Attribute; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\Stdlib\DateTime; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; -use Magento\Elasticsearch\Model\Adapter\Container\Attribute as AttributeContainer; -use Magento\Elasticsearch\Model\Adapter\Document\Builder; -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Elasticsearch\Model\Adapter\DataMapper\ProductDataMapper; -use Magento\Elasticsearch\Model\ResourceModel\Index; -use Magento\AdvancedSearch\Model\ResourceModel\Index as AdvancedSearchIndex; -use Magento\Store\Api\Data\StoreInterface; - -/** - * Class ProductDataMapperTest - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ProductDataMapperTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var ProductDataMapper - */ - protected $model; - - /** - * @var Builder|\PHPUnit_Framework_MockObject_MockObject - */ - private $builderMock; - - /** - * @var AttributeContainer|\PHPUnit_Framework_MockObject_MockObject - */ - private $attributeContainerMock; - - /** - * @var Attribute|\PHPUnit_Framework_MockObject_MockObject - */ - private $attribute; - - /** - * @var Index|\PHPUnit_Framework_MockObject_MockObject - */ - private $resourceIndex; - - /** - * @var AdvancedSearchIndex|\PHPUnit_Framework_MockObject_MockObject - */ - private $advancedSearchIndex; - - /** - * @var FieldMapperInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $fieldMapperMock; - - /** - * @var DateTime|\PHPUnit_Framework_MockObject_MockObject - */ - private $dateTimeMock; - - /** - * @var TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $localeDateMock; - - /** - * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $scopeConfigMock; - - /** - * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $storeManagerMock; - - /** - * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $storeInterface; - - /** - * Set up test environment. - */ - protected function setUp() - { - $this->builderMock = $this->getMockBuilder(\Magento\Elasticsearch\Model\Adapter\Document\Builder::class) - ->setMethods(['addField', 'addFields', 'build']) - ->disableOriginalConstructor() - ->getMock(); - - $this->attributeContainerMock = $this->getMockBuilder( - \Magento\Elasticsearch\Model\Adapter\Container\Attribute::class - )->setMethods(['getAttribute', 'setStoreId', 'getBackendType', 'getFrontendInput']) - ->disableOriginalConstructor() - ->getMock(); - - $this->resourceIndex = $this->getMockBuilder(\Magento\Elasticsearch\Model\ResourceModel\Index::class) - ->disableOriginalConstructor() - ->setMethods([ - 'getPriceIndexData', - 'getFullCategoryProductIndexData', - 'getFullProductIndexData', - ]) - ->getMock(); - - $this->fieldMapperMock = $this->getMockBuilder(\Magento\Elasticsearch\Model\Adapter\FieldMapperInterface::class) - ->setMethods(['getFieldName', 'getAllAttributesTypes']) - ->disableOriginalConstructor() - ->getMock(); - - $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime::class) - ->setMethods(['isEmptyDate', 'setTimezone', 'format']) - ->disableOriginalConstructor() - ->getMock(); - - $this->localeDateMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->advancedSearchIndex = $this->getMockBuilder(\Magento\AdvancedSearch\Model\ResourceModel\Index::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->attribute = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->storeInterface = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $objectManager = new ObjectManagerHelper($this); - $this->model = $objectManager->getObject( - \Magento\Elasticsearch\Model\Adapter\DataMapper\ProductDataMapper::class, - [ - 'builder' => $this->builderMock, - 'attributeContainer' => $this->attributeContainerMock, - 'resourceIndex' => $this->resourceIndex, - 'fieldMapper' => $this->fieldMapperMock, - 'dateTime' => $this->dateTimeMock, - 'localeDate' => $this->localeDateMock, - 'scopeConfig' => $this->scopeConfigMock, - 'storeManager' => $this->storeManagerMock - ] - ); - } - - /** - * Tests modules data returns array - * - * @dataProvider mapProvider - * @param int $productId - * @param array $productData - * @param int $storeId - * @param bool $emptyDate - * @param string $type - * @param string $frontendInput - * - * @return void - */ - public function testGetMap($productId, $productData, $storeId, $emptyDate, $type, $frontendInput) - { - $this->attributeContainerMock->expects($this->any())->method('getAttribute')->will( - $this->returnValue($this->attribute) - ); - $this->resourceIndex->expects($this->any()) - ->method('getPriceIndexData') - ->with([1, ], 1) - ->willReturn([ - 1 => [1] - ]); - $this->resourceIndex->expects($this->any()) - ->method('getFullCategoryProductIndexData') - ->willReturn([ - 1 => [ - 0 => [ - 'id' => 2, - 'name' => 'Default Category', - 'position' => '1', - ], - 1 => [ - 'id' => 3, - 'name' => 'Gear', - 'position' => '1', - ], - 2 => [ - 'id' => 4, - 'name' => 'Bags', - 'position' => '1', - ], - ], - ]); - $this->storeManagerMock->expects($this->any()) - ->method('getStore') - ->willReturn($this->storeInterface); - $this->storeInterface->expects($this->any()) - ->method('getWebsiteId') - ->willReturn(1); - $this->attributeContainerMock->expects($this->any())->method('setStoreId')->will( - $this->returnValue($this->attributeContainerMock) - ); - $this->attribute->expects($this->any())->method('getBackendType')->will( - $this->returnValue($type) - ); - $this->attribute->expects($this->any())->method('getFrontendInput')->will( - $this->returnValue($frontendInput) - ); - $this->dateTimeMock->expects($this->any())->method('isEmptyDate')->will( - $this->returnValue($emptyDate) - ); - $this->scopeConfigMock->expects($this->any())->method('getValue')->will( - $this->returnValue('Europe/London') - ); - $this->builderMock->expects($this->any())->method('addField')->will( - $this->returnValue([]) - ); - $this->builderMock->expects($this->any())->method('addFields')->will( - $this->returnValue([]) - ); - $this->builderMock->expects($this->any())->method('build')->will( - $this->returnValue([]) - ); - - $this->resourceIndex->expects($this->once()) - ->method('getFullProductIndexData') - ->willReturn($productData); - - $this->assertInternalType( - 'array', - $this->model->map($productId, $productData, $storeId) - ); - } - - /** - * @return array - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public static function mapProvider() - { - return [ - [ - '1', - ['price'=>'11','created_at'=>'00-00-00 00:00:00', 'color_value'=>'11'], - '1', - false, - 'datetime', - 'select', - ], - [ - '1', - ['price'=>'11','created_at'=>'00-00-00 00:00:00', 'color_value'=>'11'], - '1', - false, - 'time', - 'multiselect', - ], - [ - '1', - ['price'=>'11','created_at'=>null,'color_value'=>'11', ], - '1', - true, - 'datetime', - 'select', - ], - [ - '1', - [ - 'tier_price'=> - [[ - 'price_id'=>'1', - 'website_id'=>'1', - 'all_groups'=>'1', - 'cust_group'=>'1', - 'price_qty'=>'1', - 'website_price'=>'1', - 'price'=>'1' - ]], - 'created_at'=>'00-00-00 00:00:00' - ], - '1', - false, - 'string', - 'select', - ], - [ - '1', - ['image'=>'11','created_at'=>'00-00-00 00:00:00'], - '1', - false, - 'string', - 'select', - ], - [ - '1', - [ - 'image' => '1', - 'small_image' => '1', - 'thumbnail' => '1', - 'swatch_image' => '1', - 'media_gallery'=> - [ - 'images' => - [[ - 'file'=>'1', - 'media_type'=>'image', - 'position'=>'1', - 'disabled'=>'1', - 'label'=>'1', - 'title'=>'1', - 'base_image'=>'1', - 'small_image'=>'1', - 'thumbnail'=>'1', - 'swatch_image'=>'1' - ]] - ], - 'created_at'=>'00-00-00 00:00:00' - ], - '1', - false, - 'string', - 'select', - ], - [ - '1', - [ - 'image' => '1', - 'small_image' => '1', - 'thumbnail' => '1', - 'swatch_image' => '1', - 'media_gallery'=> - [ - 'images' => - [[ - 'file'=>'1', - 'media_type'=>'video', - 'position'=>'1', - 'disabled'=>'1', - 'label'=>'1', - 'title'=>'1', - 'base_image'=>'1', - 'small_image'=>'1', - 'thumbnail'=>'1', - 'swatch_image'=>'1', - 'video_title'=>'1', - 'video_url'=>'1', - 'video_description'=>'1', - 'video_metadata'=>'1', - 'video_provider'=>'1' - ]] - ], - 'created_at'=>'00-00-00 00:00:00' - ], - '1', - false, - 'string', - 'select', - ], - [ - '1', - ['quantity_and_stock_status'=>'11','created_at'=>'00-00-00 00:00:00'], - '1', - false, - 'string', - 'select', - ], - [ - '1', - ['quantity_and_stock_status'=>['is_in_stock' => '1', 'qty' => '12'],'created_at'=>'00-00-00 00:00:00'], - '1', - false, - 'string', - 'select', - ], - [ - '1', - ['price'=>'11','created_at'=>'1995-12-31 23:59:59','options'=>['value1','value2']], - '1', - false, - 'string', - 'select', - ], - ]; - } -} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/ElasticsearchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/ElasticsearchTest.php index 326c04aad6165..153deb8bff845 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/ElasticsearchTest.php @@ -12,12 +12,12 @@ use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; use Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface; use Psr\Log\LoggerInterface; -use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; +use Magento\AdvancedSearch\Model\Client\ClientInterface as ElasticsearchClient; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver; /** - * Class ElasticsearchTest + * Test for Elasticsearch client * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -73,6 +73,11 @@ class ElasticsearchTest extends \PHPUnit\Framework\TestCase */ protected $indexNameResolver; + /** + * @var \Magento\Elasticsearch\Model\Adapter\DataMapperInterface|PHPUnit_Framework_MockObject_MockObject + */ + private $documentDataMapper; + /** * Setup * @@ -135,7 +140,7 @@ protected function setUp() $elasticsearchClientMock->expects($this->any()) ->method('indices') ->willReturn($indicesMock); - $this->client = $this->getMockBuilder(\Magento\Elasticsearch\Model\Client\Elasticsearch::class) + $this->client = $this->getMockBuilder(\Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch::class) ->setConstructorArgs( [ 'options' => $this->getClientOptions(), diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Index/IndexNameResolverTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Index/IndexNameResolverTest.php index 3a294ba20e206..0491517fbe979 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Index/IndexNameResolverTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Index/IndexNameResolverTest.php @@ -9,7 +9,7 @@ use Psr\Log\LoggerInterface; use Magento\Elasticsearch\SearchAdapter\ConnectionManager; use Magento\AdvancedSearch\Model\Client\ClientOptionsInterface; -use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; +use Magento\AdvancedSearch\Model\Client\ClientInterface as ElasticsearchClient; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; /** @@ -112,7 +112,7 @@ protected function setUp() $elasticsearchClientMock->expects($this->any()) ->method('indices') ->willReturn($indicesMock); - $this->client = $this->getMockBuilder(\Magento\Elasticsearch\Model\Client\Elasticsearch::class) + $this->client = $this->getMockBuilder(\Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch::class) ->setConstructorArgs([ 'options' => $this->getClientOptions(), 'elasticsearchClient' => $elasticsearchClientMock diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php similarity index 97% rename from app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php rename to app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php index b3c60b70ffa8e..f948cd0ed194d 100644 --- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/Base/SuggestionsTest.php @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Elasticsearch6\Test\Unit\Model\DataProvider; +namespace Magento\Elasticsearch\Test\Unit\Model\DataProvider\Base; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Elasticsearch\Model\DataProvider\Suggestions; @@ -103,7 +103,7 @@ protected function setUp() $objectManager = new ObjectManagerHelper($this); $this->model = $objectManager->getObject( - \Magento\Elasticsearch6\Model\DataProvider\Suggestions::class, + \Magento\Elasticsearch\Model\DataProvider\Base\Suggestions::class, [ 'queryResultFactory' => $this->queryResultFactory, 'connectionManager' => $this->connectionManager, diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/SuggestionsTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/SuggestionsTest.php index 584b89545499d..1be84a3ec21d2 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/SuggestionsTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/DataProvider/SuggestionsTest.php @@ -148,9 +148,10 @@ public function testGetItems() ->method('getQueryText') ->willReturn('query'); - $client = $this->getMockBuilder(\Magento\Elasticsearch\Model\Client\Elasticsearch::class) + $client = $this->getMockBuilder(\Magento\AdvancedSearch\Model\Client\ClientInterface::class) ->disableOriginalConstructor() - ->getMock(); + ->setMethods(['suggest']) + ->getMockForAbstractClass(); $this->connectionManager->expects($this->any()) ->method('getConnection') diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/IndexerHandlerTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/IndexerHandlerTest.php index 7b86a13563b1e..426e23c11844d 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/IndexerHandlerTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Indexer/IndexerHandlerTest.php @@ -8,6 +8,10 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Elasticsearch\Model\Indexer\IndexerHandler; +/** + * Test for \Magento\Elasticsearch\Model\Indexer\IndexerHandler + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class IndexerHandlerTest extends \PHPUnit\Framework\TestCase { /** @@ -41,7 +45,7 @@ class IndexerHandlerTest extends \PHPUnit\Framework\TestCase private $indexNameResolver; /** - * @var \Magento\Elasticsearch\Model\Client\Elasticsearch|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\AdvancedSearch\Model\Client\ClientInterface|\PHPUnit_Framework_MockObject_MockObject */ private $client; @@ -89,8 +93,8 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->client = $this->getMockBuilder(\Magento\Elasticsearch\Model\Client\Elasticsearch::class) - ->setMethods(['ping']) + $this->client = $this->getMockBuilder(\Magento\AdvancedSearch\Model\Client\ClientInterface::class) + ->setMethods(['ping', 'testConnection','prepareDocsPerStore','addDocs', 'cleanIndex']) ->disableOriginalConstructor() ->getMock(); diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/AdapterTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/AdapterTest.php deleted file mode 100644 index 424edd4768a81..0000000000000 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/AdapterTest.php +++ /dev/null @@ -1,173 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Test\Unit\SearchAdapter; - -use Magento\Elasticsearch\SearchAdapter\Adapter; -use Magento\Elasticsearch\SearchAdapter\QueryContainer; -use Magento\Elasticsearch\SearchAdapter\QueryContainerFactory; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class AdapterTest - */ -class AdapterTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var QueryContainerFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $queryContainerFactory; - - /** - * @var Adapter - */ - protected $model; - - /** - * @var \Magento\Elasticsearch\SearchAdapter\ConnectionManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $connectionManager; - - /** - * @var \Magento\Elasticsearch\SearchAdapter\Mapper|\PHPUnit_Framework_MockObject_MockObject - */ - protected $mapper; - - /** - * @var \Magento\Elasticsearch\SearchAdapter\ResponseFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseFactory; - - /** - * @var \Magento\Framework\Search\RequestInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $request; - - /** - * @var \Magento\Elasticsearch\SearchAdapter\Aggregation\Builder|\PHPUnit_Framework_MockObject_MockObject - */ - protected $aggregationBuilder; - - /** - * Setup method - * @return void - */ - protected function setUp() - { - $this->connectionManager = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\ConnectionManager::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->mapper = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\Mapper::class) - ->setMethods([ - 'buildQuery', - ]) - ->disableOriginalConstructor() - ->getMock(); - - $this->responseFactory = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\ResponseFactory::class) - ->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); - - $this->request = $this->getMockBuilder(\Magento\Framework\Search\RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->aggregationBuilder = $this->getMockBuilder( - \Magento\Elasticsearch\SearchAdapter\Aggregation\Builder::class - ) - ->setMethods([ - 'build', - 'setQuery' - ]) - ->disableOriginalConstructor() - ->getMock(); - - $this->queryContainerFactory = $this->getMockBuilder(QueryContainerFactory::class) - ->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); - - $objectManager = new ObjectManagerHelper($this); - $this->model = $objectManager->getObject( - \Magento\Elasticsearch\SearchAdapter\Adapter::class, - [ - 'connectionManager' => $this->connectionManager, - 'mapper' => $this->mapper, - 'responseFactory' => $this->responseFactory, - 'aggregationBuilder' => $this->aggregationBuilder, - 'queryContainerFactory' => $this->queryContainerFactory, - ] - ); - } - - /** - * Test query() method - * - * @return void - */ - public function testQuery() - { - $searchQuery = [ - 'index' => 'indexName', - 'type' => 'product', - 'body' => [ - 'from' => 0, - 'size' => 1000, - 'fields' => ['_id', '_score'], - 'query' => [], - ], - ]; - - $client = $this->getMockBuilder(\Magento\Elasticsearch\Model\Client\Elasticsearch::class) - ->setMethods(['query']) - ->disableOriginalConstructor() - ->getMock(); - - $this->connectionManager->expects($this->once()) - ->method('getConnection') - ->willReturn($client); - - $queryContainer = $this->getMockBuilder(QueryContainer::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->queryContainerFactory->expects($this->once()) - ->method('create') - ->with(['query' => $searchQuery]) - ->willReturn($queryContainer); - - $this->aggregationBuilder->expects($this->once()) - ->method('setQuery') - ->with($queryContainer); - - $this->mapper->expects($this->once()) - ->method('buildQuery') - ->with($this->request) - ->willReturn($searchQuery); - - $client->expects($this->once()) - ->method('query') - ->willReturn([ - 'hits' => [ - 'total' => 1, - 'hits' => [ - [ - '_index' => 'indexName', - '_type' => 'product', - '_id' => 1, - '_score' => 1.0, - ], - ], - ], - ]); - $this->aggregationBuilder->expects($this->once()) - ->method('build') - ->willReturn($client); - - $this->model->query($this->request); - } -} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Aggregation/IntervalTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Aggregation/IntervalTest.php deleted file mode 100644 index 169c6d71062ba..0000000000000 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Aggregation/IntervalTest.php +++ /dev/null @@ -1,358 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Test\Unit\SearchAdapter\Aggregation; - -use Magento\Elasticsearch\SearchAdapter\Aggregation\Interval; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\Elasticsearch\SearchAdapter\ConnectionManager; -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Model\Session as CustomerSession; -use Magento\Elasticsearch\Model\Config; -use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; -use Magento\Store\Api\Data\StoreInterface; -use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class IntervalTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var Interval - */ - protected $model; - - /** - * @var ConnectionManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $connectionManager; - - /** - * @var FieldMapperInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $fieldMapper; - - /** - * @var Config|\PHPUnit_Framework_MockObject_MockObject - */ - protected $clientConfig; - - /** - * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $storeManager; - - /** - * @var CustomerSession|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerSession; - - /** - * @var ElasticsearchClient|\PHPUnit_Framework_MockObject_MockObject - */ - protected $clientMock; - - /** - * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $storeMock; - - /** - * @var SearchIndexNameResolver|\PHPUnit_Framework_MockObject_MockObject - */ - protected $searchIndexNameResolver; - - /** - * Set up test environment. - * - * @return void - */ - protected function setUp() - { - $this->connectionManager = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\ConnectionManager::class) - ->setMethods(['getConnection']) - ->disableOriginalConstructor() - ->getMock(); - $this->fieldMapper = $this->getMockBuilder(\Magento\Elasticsearch\Model\Adapter\FieldMapperInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->clientConfig = $this->getMockBuilder(\Magento\Elasticsearch\Model\Config::class) - ->setMethods([ - 'getIndexName', - 'getEntityType', - ]) - ->disableOriginalConstructor() - ->getMock(); - $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->customerSession = $this->getMockBuilder(\Magento\Customer\Model\Session::class) - ->setMethods(['getCustomerGroupId']) - ->disableOriginalConstructor() - ->getMock(); - $this->customerSession->expects($this->any()) - ->method('getCustomerGroupId') - ->willReturn(1); - $this->storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->searchIndexNameResolver = $this - ->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storeMock->expects($this->any()) - ->method('getWebsiteId') - ->willReturn(1); - $this->storeMock->expects($this->any()) - ->method('getId') - ->willReturn(1); - $this->clientConfig->expects($this->any()) - ->method('getIndexName') - ->willReturn('indexName'); - $this->clientConfig->expects($this->any()) - ->method('getEntityType') - ->willReturn('product'); - $this->clientMock = $this->getMockBuilder(\Magento\Elasticsearch\Model\Client\Elasticsearch::class) - ->setMethods(['query']) - ->disableOriginalConstructor() - ->getMock(); - $this->connectionManager->expects($this->any()) - ->method('getConnection') - ->willReturn($this->clientMock); - - $objectManagerHelper = new ObjectManagerHelper($this); - $this->model = $objectManagerHelper->getObject( - \Magento\Elasticsearch\SearchAdapter\Aggregation\Interval::class, - [ - 'connectionManager' => $this->connectionManager, - 'fieldMapper' => $this->fieldMapper, - 'clientConfig' => $this->clientConfig, - 'searchIndexNameResolver' => $this->searchIndexNameResolver, - 'fieldName' => 'price_0_1', - 'storeId' => 1, - 'entityIds' => [265, 313, 281] - ] - ); - } - - /** - * @dataProvider loadParamsProvider - * @param string $limit - * @param string $offset - * @param string $lower - * @param string $upper - * Test load() method - */ - public function testLoad($limit, $offset, $lower, $upper) - { - $this->storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->searchIndexNameResolver->expects($this->any()) - ->method('getIndexName') - ->willReturn('magento2_product_1'); - $this->clientConfig->expects($this->any()) - ->method('getEntityType') - ->willReturn('document'); - - $expectedResult = [25]; - - $this->clientMock->expects($this->once()) - ->method('query') - ->willReturn([ - 'hits' => [ - 'hits' => [ - [ - 'fields' => [ - - 'price_0_1' => [25], - - ], - ], - ], - ], - ]); - $this->assertEquals( - $expectedResult, - $this->model->load($limit, $offset, $lower, $upper) - ); - } - - /** - * @dataProvider loadPrevParamsProvider - * @param string $data - * @param string $index - * @param string $lower - * Test loadPrevious() method with offset - */ - public function testLoadPrevArray($data, $index, $lower) - { - $queryResult = [ - 'hits' => [ - 'total'=> '1', - 'hits' => [ - [ - 'fields' => [ - 'price_0_1' => ['25'] - ] - ], - ], - ], - ]; - - $this->storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->searchIndexNameResolver->expects($this->any()) - ->method('getIndexName') - ->willReturn('magento2_product_1'); - $this->clientConfig->expects($this->any()) - ->method('getEntityType') - ->willReturn('document'); - - $expectedResult = ['25.0']; - - $this->clientMock->expects($this->any()) - ->method('query') - ->willReturn($queryResult); - $this->assertEquals( - $expectedResult, - $this->model->loadPrevious($data, $index, $lower) - ); - } - - /** - * @dataProvider loadPrevParamsProvider - * @param string $data - * @param string $index - * @param string $lower - * Test loadPrevious() method without offset - */ - public function testLoadPrevFalse($data, $index, $lower) - { - $queryResult = ['hits' => ['total'=> '0']]; - - $this->storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->searchIndexNameResolver->expects($this->any()) - ->method('getIndexName') - ->willReturn('magento2_product_1'); - $this->clientConfig->expects($this->any()) - ->method('getEntityType') - ->willReturn('document'); - - $this->clientMock->expects($this->any()) - ->method('query') - ->willReturn($queryResult); - $this->assertFalse( - $this->model->loadPrevious($data, $index, $lower) - ); - } - - /** - * @dataProvider loadNextParamsProvider - * @param string $data - * @param string $rightIndex - * @param string $upper - * Test loadNext() method with offset - */ - public function testLoadNextArray($data, $rightIndex, $upper) - { - $queryResult = [ - 'hits' => [ - 'total'=> '1', - 'hits' => [ - [ - 'fields' => [ - 'price_0_1' => ['25'] - ] - ], - ], - ] - ]; - - $this->storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->searchIndexNameResolver->expects($this->any()) - ->method('getIndexName') - ->willReturn('magento2_product_1'); - $this->clientConfig->expects($this->any()) - ->method('getEntityType') - ->willReturn('document'); - - $expectedResult = ['25.0']; - - $this->clientMock->expects($this->any()) - ->method('query') - ->willReturn($queryResult); - $this->assertEquals( - $expectedResult, - $this->model->loadNext($data, $rightIndex, $upper) - ); - } - - /** - * @dataProvider loadNextParamsProvider - * @param string $data - * @param string $rightIndex - * @param string $upper - * Test loadNext() method without offset - */ - public function testLoadNextFalse($data, $rightIndex, $upper) - { - $queryResult = ['hits' => ['total'=> '0']]; - - $this->storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->searchIndexNameResolver->expects($this->any()) - ->method('getIndexName') - ->willReturn('magento2_product_1'); - $this->clientConfig->expects($this->any()) - ->method('getEntityType') - ->willReturn('document'); - - $this->clientMock->expects($this->any()) - ->method('query') - ->willReturn($queryResult); - $this->assertFalse( - $this->model->loadNext($data, $rightIndex, $upper) - ); - } - - /** - * @return array - */ - public static function loadParamsProvider() - { - return [ - ['6', '2', '24', '42'], - ]; - } - - /** - * @return array - */ - public static function loadPrevParamsProvider() - { - return [ - ['24', '1', '24'], - ]; - } - - /** - * @return array - */ - public static function loadNextParamsProvider() - { - return [ - ['24', '2', '42'], - ]; - } -} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ConnectionManagerTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ConnectionManagerTest.php index 5d80ef62a5d20..7d5865650e54a 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ConnectionManagerTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ConnectionManagerTest.php @@ -12,7 +12,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; /** - * Class ConnectionManagerTest + * Test for Magento\Elasticsearch\SearchAdapter\ConnectionManager */ class ConnectionManagerTest extends \PHPUnit\Framework\TestCase { @@ -81,7 +81,7 @@ protected function setUp() */ public function testGetConnectionSuccessfull() { - $client = $this->getMockBuilder(\Magento\Elasticsearch\Model\Client\Elasticsearch::class) + $client = $this->getMockBuilder(\Magento\AdvancedSearch\Model\Client\ClientInterface::class) ->disableOriginalConstructor() ->getMock(); $this->clientFactory->expects($this->once()) diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Dynamic/DataProviderTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Dynamic/DataProviderTest.php index 6258a4a20d694..9e2d3c3b35a98 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Dynamic/DataProviderTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Dynamic/DataProviderTest.php @@ -66,7 +66,7 @@ class DataProviderTest extends \PHPUnit\Framework\TestCase protected $storeMock; /** - * @var \Magento\Elasticsearch\Model\Client\Elasticsearch|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\AdvancedSearch\Model\Client\ClientInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $clientMock; @@ -145,8 +145,8 @@ private function setUpMockObjects() $this->clientConfig->expects($this->any()) ->method('getEntityType') ->willReturn('product'); - $this->clientMock = $this->getMockBuilder(\Magento\Elasticsearch\Model\Client\Elasticsearch::class) - ->setMethods(['query']) + $this->clientMock = $this->getMockBuilder(\Magento\AdvancedSearch\Model\Client\ClientInterface::class) + ->setMethods(['query', 'testConnection', ]) ->disableOriginalConstructor() ->getMock(); $this->connectionManager->expects($this->any()) diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/MapperTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/MapperTest.php deleted file mode 100644 index 845e1acb2d646..0000000000000 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/MapperTest.php +++ /dev/null @@ -1,202 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Test\Unit\SearchAdapter; - -use Magento\Elasticsearch\SearchAdapter\Mapper; -use Magento\Elasticsearch\SearchAdapter\Query\Builder as QueryBuilder; -use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder; -use Magento\Elasticsearch\SearchAdapter\Filter\Builder as FilterBuilder; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MapperTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var Mapper - */ - protected $model; - - /** - * @var QueryBuilder|\PHPUnit_Framework_MockObject_MockObject - */ - protected $queryBuilder; - - /** - * @var MatchQueryBuilder|\PHPUnit_Framework_MockObject_MockObject - */ - protected $matchQueryBuilder; - - /** - * @var FilterBuilder|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterBuilder; - - /** - * Setup method - * @return void - */ - protected function setUp() - { - $this->queryBuilder = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\Query\Builder::class) - ->setMethods([ - 'initQuery', - 'initAggregations', - ]) - ->disableOriginalConstructor() - ->getMock(); - $this->matchQueryBuilder = $this->getMockBuilder( - \Magento\Elasticsearch\SearchAdapter\Query\Builder\Match::class - ) - ->setMethods(['build']) - ->disableOriginalConstructor() - ->getMock(); - $this->filterBuilder = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\Filter\Builder::class) - ->disableOriginalConstructor() - ->getMock(); - $this->queryBuilder->expects($this->any()) - ->method('initQuery') - ->willReturn([ - 'body' => [ - 'query' => [], - ], - ]); - $this->queryBuilder->expects($this->any()) - ->method('initAggregations') - ->willReturn([ - 'body' => [ - 'query' => [], - ], - ]); - $this->matchQueryBuilder->expects($this->any()) - ->method('build') - ->willReturn([]); - - $objectManagerHelper = new ObjectManagerHelper($this); - $this->model = $objectManagerHelper->getObject( - \Magento\Elasticsearch\SearchAdapter\Mapper::class, - [ - 'queryBuilder' => $this->queryBuilder, - 'matchQueryBuilder' => $this->matchQueryBuilder, - 'filterBuilder' => $this->filterBuilder - ] - ); - } - - /** - * Test buildQuery() method with exception - * @expectedException \InvalidArgumentException - */ - public function testBuildQueryFailure() - { - $request = $this->getMockBuilder(\Magento\Framework\Search\RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $query = $this->getMockBuilder(\Magento\Framework\Search\Request\QueryInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->once()) - ->method('getQuery') - ->willReturn($query); - $query->expects($this->atLeastOnce()) - ->method('getType') - ->willReturn('unknown'); - - $this->model->buildQuery($request); - } - - /** - * Test buildQuery() method - * - * @param string $queryType - * @param string $queryMock - * @param string $referenceType - * @param string $filterMock - * @dataProvider buildQueryDataProvider - */ - public function testBuildQuery($queryType, $queryMock, $referenceType, $filterMock) - { - $request = $this->getMockBuilder(\Magento\Framework\Search\RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $query = $this->getMockBuilder($queryMock) - ->setMethods(['getMust', 'getMustNot', 'getType', 'getShould', 'getReferenceType', 'getReference']) - ->disableOriginalConstructor() - ->getMock(); - $matchQuery = $this->getMockBuilder(\Magento\Framework\Search\Request\Query\Match::class) - ->disableOriginalConstructor() - ->getMock(); - $filterQuery = $this->getMockBuilder($filterMock) - ->disableOriginalConstructor() - ->getMock(); - $request->expects($this->once()) - ->method('getQuery') - ->willReturn($query); - - $query->expects($this->atLeastOnce()) - ->method('getType') - ->willReturn($queryType); - $query->expects($this->any()) - ->method('getMust') - ->willReturn([$matchQuery]); - $query->expects($this->any()) - ->method('getShould') - ->willReturn([]); - $query->expects($this->any()) - ->method('getMustNot') - ->willReturn([]); - $query->expects($this->any()) - ->method('getReferenceType') - ->willReturn($referenceType); - $query->expects($this->any()) - ->method('getReference') - ->willReturn($filterQuery); - $matchQuery->expects($this->any()) - ->method('getType') - ->willReturn('matchQuery'); - $filterQuery->expects($this->any()) - ->method('getType') - ->willReturn('matchQuery'); - $filterQuery->expects($this->any()) - ->method('getType') - ->willReturn('matchQuery'); - $this->filterBuilder->expects(($this->any())) - ->method('build') - ->willReturn([ - 'bool' => [ - 'must' => [], - ], - ]); - - $this->model->buildQuery($request); - } - - /** - * @return array - */ - public function buildQueryDataProvider() - { - return [ - [ - 'matchQuery', \Magento\Framework\Search\Request\Query\Match::class, - 'query', \Magento\Framework\Search\Request\QueryInterface::class, - ], - [ - 'boolQuery', \Magento\Framework\Search\Request\Query\BoolExpression::class, - 'query', \Magento\Framework\Search\Request\QueryInterface::class, - ], - [ - 'filteredQuery', \Magento\Framework\Search\Request\Query\Filter::class, - 'query', \Magento\Framework\Search\Request\QueryInterface::class, - ], - [ - 'filteredQuery', \Magento\Framework\Search\Request\Query\Filter::class, - 'filter', \Magento\Framework\Search\Request\FilterInterface::class, - ], - ]; - } -} diff --git a/app/code/Magento/Elasticsearch/composer.json b/app/code/Magento/Elasticsearch/composer.json index 3cf5444d86223..9a4898ee330e2 100644 --- a/app/code/Magento/Elasticsearch/composer.json +++ b/app/code/Magento/Elasticsearch/composer.json @@ -12,7 +12,7 @@ "magento/module-store": "*", "magento/module-catalog-inventory": "*", "magento/framework": "*", - "elasticsearch/elasticsearch": "~2.0||~5.1||~6.1" + "elasticsearch/elasticsearch": "~7.6" }, "suggest": { "magento/module-config": "*" diff --git a/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml index 6d87c4948a9d9..1727e51371383 100644 --- a/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml +++ b/app/code/Magento/Elasticsearch/etc/adminhtml/system.xml @@ -9,68 +9,6 @@ <system> <section id="catalog"> <group id="search"> - <!-- Elasticsearch 2.0+ --> - <field id="elasticsearch_server_hostname" translate="label" type="text" sortOrder="61" showInDefault="1"> - <label>Elasticsearch Server Hostname</label> - <depends> - <field id="engine">elasticsearch</field> - </depends> - </field> - <field id="elasticsearch_server_port" translate="label" type="text" sortOrder="62" showInDefault="1"> - <label>Elasticsearch Server Port</label> - <depends> - <field id="engine">elasticsearch</field> - </depends> - </field> - <field id="elasticsearch_index_prefix" translate="label" type="text" sortOrder="63" showInDefault="1"> - <label>Elasticsearch Index Prefix</label> - <depends> - <field id="engine">elasticsearch</field> - </depends> - </field> - <field id="elasticsearch_enable_auth" translate="label" type="select" sortOrder="64" showInDefault="1"> - <label>Enable Elasticsearch HTTP Auth</label> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <depends> - <field id="engine">elasticsearch</field> - </depends> - </field> - <field id="elasticsearch_username" translate="label" type="text" sortOrder="65" showInDefault="1"> - <label>Elasticsearch HTTP Username</label> - <depends> - <field id="engine">elasticsearch</field> - <field id="elasticsearch_enable_auth">1</field> - </depends> - </field> - <field id="elasticsearch_password" translate="label" type="text" sortOrder="66" showInDefault="1"> - <label>Elasticsearch HTTP Password</label> - <depends> - <field id="engine">elasticsearch</field> - <field id="elasticsearch_enable_auth">1</field> - </depends> - </field> - <field id="elasticsearch_server_timeout" translate="label" type="text" sortOrder="67" showInDefault="1"> - <label>Elasticsearch Server Timeout</label> - <depends> - <field id="engine">elasticsearch</field> - </depends> - </field> - <field id="elasticsearch_test_connect_wizard" translate="button_label" sortOrder="68" showInDefault="1"> - <label/> - <button_label>Test Connection</button_label> - <frontend_model>Magento\Elasticsearch\Block\Adminhtml\System\Config\TestConnection</frontend_model> - <depends> - <field id="engine">elasticsearch</field> - </depends> - </field> - <field id="elasticsearch_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1"> - <label>Minimum Terms to Match</label> - <depends> - <field id="engine">elasticsearch</field> - </depends> - <comment><![CDATA[<a href="https://docs.magento.com/m2/ce/user_guide/catalog/search-elasticsearch.html">Learn more</a> about valid syntax.]]></comment> - <backend_model>Magento\Elasticsearch\Model\Config\Backend\MinimumShouldMatch</backend_model> - </field> <!-- Elasticsearch 5.x --> <field id="elasticsearch5_server_hostname" translate="label" type="text" sortOrder="61" showInDefault="1"> <label>Elasticsearch Server Hostname</label> diff --git a/app/code/Magento/Elasticsearch/etc/config.xml b/app/code/Magento/Elasticsearch/etc/config.xml index 9df21978b5414..1d55730fce378 100644 --- a/app/code/Magento/Elasticsearch/etc/config.xml +++ b/app/code/Magento/Elasticsearch/etc/config.xml @@ -9,12 +9,12 @@ <default> <catalog> <search> - <elasticsearch_server_hostname>localhost</elasticsearch_server_hostname> - <elasticsearch_server_port>9200</elasticsearch_server_port> - <elasticsearch_index_prefix>magento2</elasticsearch_index_prefix> - <elasticsearch_enable_auth>0</elasticsearch_enable_auth> - <elasticsearch_server_timeout>15</elasticsearch_server_timeout> - <elasticsearch_minimum_should_match></elasticsearch_minimum_should_match> +<!-- <elasticsearch_server_hostname>localhost</elasticsearch_server_hostname>--> +<!-- <elasticsearch_server_port>9200</elasticsearch_server_port>--> +<!-- <elasticsearch_index_prefix>magento2</elasticsearch_index_prefix>--> +<!-- <elasticsearch_enable_auth>0</elasticsearch_enable_auth>--> +<!-- <elasticsearch_server_timeout>15</elasticsearch_server_timeout>--> +<!-- <elasticsearch_minimum_should_match></elasticsearch_minimum_should_match>--> <elasticsearch5_server_hostname>localhost</elasticsearch5_server_hostname> <elasticsearch5_server_port>9200</elasticsearch5_server_port> diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index bb16bba127b56..fa8686b6262df 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -16,7 +16,6 @@ <type name="Magento\Elasticsearch\Model\Config"> <arguments> <argument name="engineList" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">elasticsearch</item> <item name="elasticsearch5" xsi:type="string">elasticsearch5</item> </argument> </arguments> @@ -61,7 +60,6 @@ <arguments> <argument name="factories" xsi:type="array"> <item name="mysql" xsi:type="object">Magento\CatalogSearch\Model\ResourceModel\Fulltext\SearchCollectionFactory</item> - <item name="elasticsearch" xsi:type="object">elasticsearchFulltextSearchCollectionFactory</item> <item name="elasticsearch5" xsi:type="object">elasticsearchFulltextSearchCollectionFactory</item> </argument> </arguments> @@ -84,7 +82,6 @@ <arguments> <argument name="factories" xsi:type="array"> <item name="mysql" xsi:type="object">Magento\CatalogSearch\Model\ResourceModel\Fulltext\CollectionFactory</item> - <item name="elasticsearch" xsi:type="object">elasticsearchCategoryCollectionFactory</item> <item name="elasticsearch5" xsi:type="object">elasticsearchCategoryCollectionFactory</item> </argument> </arguments> @@ -106,7 +103,6 @@ <type name="Magento\CatalogSearch\Model\Search\ItemCollectionProvider"> <arguments> <argument name="factories" xsi:type="array"> - <item name="elasticsearch" xsi:type="object">elasticsearchAdvancedCollectionFactory</item> <item name="elasticsearch5" xsi:type="object">elasticsearchAdvancedCollectionFactory</item> </argument> </arguments> @@ -114,7 +110,6 @@ <type name="Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategyProvider"> <arguments> <argument name="strategies" xsi:type="array"> - <item name="elasticsearch" xsi:type="object">Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy</item> <item name="elasticsearch5" xsi:type="object">Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy</item> </argument> </arguments> @@ -168,15 +163,13 @@ <type name="Magento\Search\Model\Adminhtml\System\Config\Source\Engine"> <arguments> <argument name="engines" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">Elasticsearch</item> - <item name="elasticsearch5" xsi:type="string">Elasticsearch 5.x</item> + <item name="elasticsearch5" xsi:type="string">Elasticsearch 5.0+ (Deprecated)</item> </argument> </arguments> </type> <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProviderProxy"> <arguments> <argument name="categoryFieldsProviders" xsi:type="array"> - <item name="elasticsearch" xsi:type="object">Magento\Elasticsearch\Model\Adapter\BatchDataMapper\CategoryFieldsProvider</item> <item name="elasticsearch5" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProvider</item> </argument> </arguments> @@ -184,7 +177,6 @@ <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy"> <arguments> <argument name="dataMappers" xsi:type="array"> - <item name="elasticsearch" xsi:type="object">Magento\Elasticsearch\Model\Adapter\DataMapper\ProductDataMapper</item> <item name="elasticsearch5" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper</item> </argument> </arguments> @@ -192,7 +184,6 @@ <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> <arguments> <argument name="productFieldMappers" xsi:type="array"> - <item name="elasticsearch" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper</item> <item name="elasticsearch5" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapper</item> </argument> </arguments> @@ -200,19 +191,16 @@ <type name="Magento\AdvancedSearch\Model\Client\ClientResolver"> <arguments> <argument name="clientFactories" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">\Magento\Elasticsearch\Model\Client\ElasticsearchFactory</item> <item name="elasticsearch5" xsi:type="string">\Magento\Elasticsearch\Elasticsearch5\Model\Client\ElasticsearchFactory</item> </argument> <argument name="clientOptions" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">\Magento\Elasticsearch\Model\Config</item> - <item name="elasticsearch5" xsi:type="string">\Magento\Elasticsearch\Model\Config</item> + <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\Model\Config</item> </argument> </arguments> </type> <type name="Magento\CatalogSearch\Model\Indexer\IndexerHandlerFactory"> <arguments> <argument name="handlers" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">Magento\Elasticsearch\Model\Indexer\IndexerHandler</item> <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\Model\Indexer\IndexerHandler</item> </argument> </arguments> @@ -220,7 +208,6 @@ <type name="Magento\CatalogSearch\Model\Indexer\IndexStructureFactory"> <arguments> <argument name="structures" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">Magento\Elasticsearch\Model\Indexer\IndexStructure</item> <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\Model\Indexer\IndexStructure</item> </argument> </arguments> @@ -228,7 +215,6 @@ <type name="Magento\CatalogSearch\Model\ResourceModel\EngineProvider"> <arguments> <argument name="engines" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">Magento\Elasticsearch\Model\ResourceModel\Engine</item> <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\Model\ResourceModel\Engine</item> </argument> </arguments> @@ -236,7 +222,6 @@ <type name="Magento\Search\Model\AdapterFactory"> <arguments> <argument name="adapters" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">Magento\Elasticsearch\SearchAdapter\Adapter</item> <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Adapter</item> </argument> </arguments> @@ -244,7 +229,6 @@ <type name="Magento\Search\Model\EngineResolver"> <arguments> <argument name="engines" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">elasticsearch</item> <item name="elasticsearch5" xsi:type="string">elasticsearch5</item> </argument> </arguments> @@ -279,7 +263,6 @@ <type name="Magento\Elasticsearch\Elasticsearch5\Model\Client\ClientFactoryProxy"> <arguments> <argument name="clientFactories" xsi:type="array"> - <item name="elasticsearch" xsi:type="object">Magento\Elasticsearch\Model\Client\ElasticsearchFactory</item> <item name="elasticsearch5" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Client\ElasticsearchFactory</item> </argument> </arguments> @@ -292,7 +275,6 @@ <type name="Magento\Framework\Search\Dynamic\IntervalFactory"> <arguments> <argument name="intervals" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">Magento\Elasticsearch\SearchAdapter\Aggregation\Interval</item> <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval</item> </argument> </arguments> @@ -300,7 +282,6 @@ <type name="Magento\Framework\Search\Dynamic\DataProviderFactory"> <arguments> <argument name="dataProviders" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">Magento\Elasticsearch\SearchAdapter\Dynamic\DataProvider</item> <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\SearchAdapter\Dynamic\DataProvider</item> </argument> </arguments> @@ -347,15 +328,9 @@ <argument name="cacheId" xsi:type="string">elasticsearch_index_config</argument> </arguments> </type> - <virtualType name="Magento\Elasticsearch\Model\Client\ElasticsearchFactory" type="Magento\AdvancedSearch\Model\Client\ClientFactory"> - <arguments> - <argument name="clientClass" xsi:type="string">Magento\Elasticsearch\Model\Client\Elasticsearch</argument> - </arguments> - </virtualType> <type name="Magento\AdvancedSearch\Model\SuggestedQueries"> <arguments> <argument name="data" xsi:type="array"> - <item name="elasticsearch" xsi:type="string">Magento\Elasticsearch\Model\DataProvider\Suggestions</item> <item name="elasticsearch5" xsi:type="string">Magento\Elasticsearch\Model\DataProvider\Suggestions</item> </argument> </arguments> @@ -368,22 +343,11 @@ <type name="Magento\Config\Model\Config\TypePool"> <arguments> <argument name="sensitive" xsi:type="array"> - <item name="catalog/search/elasticsearch_password" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch_server_hostname" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch_username" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch5_password" xsi:type="string">1</item> <item name="catalog/search/elasticsearch5_server_hostname" xsi:type="string">1</item> <item name="catalog/search/elasticsearch5_username" xsi:type="string">1</item> </argument> <argument name="environment" xsi:type="array"> - <item name="catalog/search/elasticsearch_enable_auth" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch_index_prefix" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch_password" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch_server_hostname" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch_server_port" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch_username" xsi:type="string">1</item> - <item name="catalog/search/elasticsearch_server_timeout" xsi:type="string">1</item> <item name="catalog/search/elasticsearch5_enable_auth" xsi:type="string">1</item> <item name="catalog/search/elasticsearch5_index_prefix" xsi:type="string">1</item> @@ -405,6 +369,7 @@ <argument name="fieldNameResolver" xsi:type="object">elasticsearch5FieldNameResolver</argument> </arguments> </type> +<!-- todo check if we need this configuration--> <type name="Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> <arguments> <argument name="items" xsi:type="array"> @@ -518,13 +483,6 @@ <argument name="fieldNameResolver" xsi:type="object">elasticsearch5FieldNameResolver</argument> </arguments> </type> - <type name="Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper"> - <arguments> - <argument name="attributeAdapterProvider" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider</argument> - <argument name="fieldProvider" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface</argument> - <argument name="fieldNameResolver" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface</argument> - </arguments> - </type> <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\IndexResolver"> <arguments> <argument name="converter" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\Converter</argument> @@ -535,7 +493,6 @@ <type name="Magento\Search\Model\Search\PageSizeProvider"> <arguments> <argument name="pageSizeBySearchEngine" xsi:type="array"> - <item name="elasticsearch" xsi:type="number">10000</item> <item name="elasticsearch5" xsi:type="number">10000</item> </argument> </arguments> diff --git a/app/code/Magento/Elasticsearch/etc/search_engine.xml b/app/code/Magento/Elasticsearch/etc/search_engine.xml index 51af3038b9c8d..72dd49504fe81 100644 --- a/app/code/Magento/Elasticsearch/etc/search_engine.xml +++ b/app/code/Magento/Elasticsearch/etc/search_engine.xml @@ -6,9 +6,6 @@ */ --> <engines xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_engine.xsd"> - <engine name="elasticsearch"> - <feature name="synonyms" support="true" /> - </engine> <engine name="elasticsearch5"> <feature name="synonyms" support="true" /> </engine> diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml index 71e0401a1c30a..0929c691257e5 100644 --- a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml +++ b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml @@ -18,13 +18,14 @@ <testCaseId value="MC-6310"/> <useCaseId value="MAGETWO-91625"/> <group value="elasticsearch"/> + <group value="SearchEngineElasticsearch"/> </annotations> <before> <!-- Set search engine to Elastic 6, set Locale to China, create category and product, then go to Storefront --> <actionGroup ref="LoginAsAdmin" stepKey="login"/> <magentoCLI command="config:set --scope={{GeneralLocalCodeConfigsForChina.scope}} --scope-code={{GeneralLocalCodeConfigsForChina.scope_code}} {{GeneralLocalCodeConfigsForChina.path}} {{GeneralLocalCodeConfigsForChina.value}}" stepKey="setLocaleToChina"/> - <magentoCLI command="config:set {{EnableElasticSearch6Config.path}} {{EnableElasticSearch6Config.value}}" stepKey="enableElasticsearch6"/> - <actionGroup ref="AdminElasticConnectionTestActionGroup" stepKey="checkConnection"/> + <comment userInput="Moved to appropriate test suite" stepKey="enableElasticsearch6"/> + <comment userInput="Moved to appropriate test suite" stepKey="checkConnection"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> <createData entity="ApiCategory" stepKey="createCategory"/> <createData entity="ApiSimpleProduct" stepKey="createProduct"> @@ -35,7 +36,7 @@ <after> <!-- Delete created data and reset initial configuration --> <magentoCLI command="config:set --scope={{GeneralLocalCodeConfigsForUS.scope}} --scope-code={{GeneralLocalCodeConfigsForUS.scope_code}} {{GeneralLocalCodeConfigsForUS.path}} {{GeneralLocalCodeConfigsForUS.value}}" stepKey="setLocaleToUS"/> - <magentoCLI command="config:set {{SetDefaultSearchEngineConfig.path}} {{SetDefaultSearchEngineConfig.value}}" stepKey="resetSearchEnginePreviousState"/> + <comment userInput="Moved to appropriate test suite" stepKey="resetSearchEnginePreviousState"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php index 3d840d5a808af..e3bcc3d219538 100644 --- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php @@ -6,7 +6,7 @@ namespace Magento\Elasticsearch6\Test\Unit\Model\Client; -use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; +use Magento\AdvancedSearch\Model\Client\ClientInterface as ElasticsearchClient; use Magento\Elasticsearch6\Model\Adapter\FieldMapper\AddDefaultSearchField; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Elasticsearch6\Model\Client\Elasticsearch; @@ -593,7 +593,7 @@ public function testDeleteMappingFailure() */ public function testQuery() { - $query = 'test phrase query'; + $query = ['test phrase query']; $this->elasticsearchClientMock->expects($this->once()) ->method('search') ->with($query) diff --git a/app/code/Magento/Elasticsearch6/composer.json b/app/code/Magento/Elasticsearch6/composer.json index b2411f6740fae..26cd5b1ad8305 100644 --- a/app/code/Magento/Elasticsearch6/composer.json +++ b/app/code/Magento/Elasticsearch6/composer.json @@ -7,9 +7,8 @@ "magento/module-advanced-search": "*", "magento/module-catalog-search": "*", "magento/module-search": "*", - "magento/module-store": "*", "magento/module-elasticsearch": "*", - "elasticsearch/elasticsearch": "~2.0||~5.1||~6.1" + "elasticsearch/elasticsearch": "~7.6" }, "suggest": { "magento/module-config": "*" diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml index 69efe1a4a4f59..f7b22c05027b1 100644 --- a/app/code/Magento/Elasticsearch6/etc/di.xml +++ b/app/code/Magento/Elasticsearch6/etc/di.xml @@ -145,11 +145,11 @@ </arguments> </type> - <type name="Magento\Elasticsearch6\Model\DataProvider\Suggestions"> + <virtualType name="Magento\Elasticsearch6\Model\DataProvider\Suggestions" type="Magento\Elasticsearch\Model\DataProvider\Base\Suggestions"> <arguments> <argument name="fieldProvider" xsi:type="object">elasticsearch5FieldProvider</argument> </arguments> - </type> + </virtualType> <virtualType name="elasticsearch6FieldNameResolver" type="\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> <arguments> diff --git a/app/code/Magento/Elasticsearch7/Block/Adminhtml/System/Config/TestConnection.php b/app/code/Magento/Elasticsearch7/Block/Adminhtml/System/Config/TestConnection.php new file mode 100644 index 0000000000000..e35f292778ab1 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/Block/Adminhtml/System/Config/TestConnection.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch7\Block\Adminhtml\System\Config; + +/** + * Elasticsearch 7.x test connection block + */ +class TestConnection extends \Magento\AdvancedSearch\Block\Adminhtml\System\Config\TestConnection +{ + /** + * @inheritdoc + */ + protected function _getFieldMapping(): array + { + $fields = [ + 'engine' => 'catalog_search_engine', + 'hostname' => 'catalog_search_elasticsearch7_server_hostname', + 'port' => 'catalog_search_elasticsearch7_server_port', + 'index' => 'catalog_search_elasticsearch7_index_prefix', + 'enableAuth' => 'catalog_search_elasticsearch7_enable_auth', + 'username' => 'catalog_search_elasticsearch7_username', + 'password' => 'catalog_search_elasticsearch7_password', + 'timeout' => 'catalog_search_elasticsearch7_server_timeout', + ]; + + return array_merge(parent::_getFieldMapping(), $fields); + } +} diff --git a/app/code/Magento/Elasticsearch7/LICENSE.txt b/app/code/Magento/Elasticsearch7/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/Elasticsearch7/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/Elasticsearch7/LICENSE_AFL.txt b/app/code/Magento/Elasticsearch7/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/Elasticsearch7/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/Elasticsearch7/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php new file mode 100644 index 0000000000000..8387bcf0296ff --- /dev/null +++ b/app/code/Magento/Elasticsearch7/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch7\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver; + +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver + as DefaultFiledNameResolver; + +/** + * Default name resolver for Elasticsearch 7 + */ +class DefaultResolver implements ResolverInterface +{ + /** + * @var DefaultFiledNameResolver + */ + private $baseResolver; + + /** + * DefaultResolver constructor. + * @param DefaultFiledNameResolver $baseResolver + */ + public function __construct(DefaultFiledNameResolver $baseResolver) + { + $this->baseResolver = $baseResolver; + } + + /** + * Get field name. + * + * @param AttributeAdapter $attribute + * @param array $context + * @return string|null + */ + public function getFieldName(AttributeAdapter $attribute, $context = []): ?string + { + $fieldName = $this->baseResolver->getFieldName($attribute, $context); + if ($fieldName === '_all') { + $fieldName = '_search'; + } + + return $fieldName; + } +} diff --git a/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php similarity index 60% rename from app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php rename to app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php index d2b677a95c7c0..c6c85b0edc338 100644 --- a/app/code/Magento/Elasticsearch/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php @@ -3,8 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -namespace Magento\Elasticsearch\Model\Client; +namespace Magento\Elasticsearch7\Model\Client; use Magento\Framework\Exception\LocalizedException; use Magento\AdvancedSearch\Model\Client\ClientInterface; @@ -14,6 +15,11 @@ */ class Elasticsearch implements ClientInterface { + /** + * @var array + */ + private $clientOptions; + /** * Elasticsearch Client instances * @@ -21,18 +27,13 @@ class Elasticsearch implements ClientInterface */ private $client; - /** - * @var array - */ - private $clientOptions; - /** * @var bool */ private $pingResult; /** - * Initialize Elasticsearch Client + * Initialize Elasticsearch 7 Client * * @param array $options * @param \Elasticsearch\Client|null $elasticsearchClient @@ -43,65 +44,76 @@ public function __construct( $elasticsearchClient = null ) { if (empty($options['hostname']) || ((!empty($options['enableAuth']) && - ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { + ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { throw new LocalizedException( __('The search failed because of a search engine misconfiguration.') ); } - - if (!($elasticsearchClient instanceof \Elasticsearch\Client)) { - $config = $this->buildConfig($options); - $elasticsearchClient = \Elasticsearch\ClientBuilder::fromConfig($config, true); + // phpstan:ignore + if ($elasticsearchClient instanceof \Elasticsearch\Client) { + $this->client[getmypid()] = $elasticsearchClient; } - $this->client[getmypid()] = $elasticsearchClient; $this->clientOptions = $options; } /** - * Get Elasticsearch Client + * Execute suggest query for Elasticsearch 7 + * + * @param array $query + * @return array + */ + public function suggest(array $query) : array + { + return $this->getElasticsearchClient()->suggest($query); + } + + /** + * Get Elasticsearch 7 Client * * @return \Elasticsearch\Client */ - private function getClient() + private function getElasticsearchClient(): \Elasticsearch\Client { $pid = getmypid(); if (!isset($this->client[$pid])) { - $config = $this->buildConfig($this->clientOptions); + $config = $this->buildESConfig($this->clientOptions); $this->client[$pid] = \Elasticsearch\ClientBuilder::fromConfig($config, true); } return $this->client[$pid]; } /** - * Ping the Elasticsearch client + * Ping the Elasticsearch 7 client * * @return bool */ - public function ping() + public function ping() : bool { if ($this->pingResult === null) { - $this->pingResult = $this->getClient()->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]); + $this->pingResult = $this->getElasticsearchClient() + ->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]); } + return $this->pingResult; } /** - * Validate connection params + * Validate connection params for Elasticsearch 7 * * @return bool */ - public function testConnection() + public function testConnection() : bool { return $this->ping(); } /** - * Build config. + * Build config for Elasticsearch 7 * * @param array $options * @return array */ - private function buildConfig($options = []) + private function buildESConfig(array $options = []) : array { $hostname = preg_replace('/http[s]?:\/\//i', '', $options['hostname']); // @codingStandardsIgnoreStart @@ -129,26 +141,26 @@ private function buildConfig($options = []) } /** - * Performs bulk query over Elasticsearch index + * Performs bulk query over Elasticsearch 7 index * * @param array $query * @return void */ - public function bulkQuery($query) + public function bulkQuery(array $query) { - $this->getClient()->bulk($query); + $this->getElasticsearchClient()->bulk($query); } /** - * Creates an Elasticsearch index. + * Creates an Elasticsearch 7 index. * * @param string $index * @param array $settings * @return void */ - public function createIndex($index, $settings) + public function createIndex(string $index, array $settings) { - $this->getClient()->indices()->create( + $this->getElasticsearchClient()->indices()->create( [ 'index' => $index, 'body' => $settings, @@ -157,14 +169,14 @@ public function createIndex($index, $settings) } /** - * Delete an Elasticsearch index. + * Delete an Elasticsearch 7 index. * * @param string $index * @return void */ - public function deleteIndex($index) + public function deleteIndex(string $index) { - $this->getClient()->indices()->delete(['index' => $index]); + $this->getElasticsearchClient()->indices()->delete(['index' => $index]); } /** @@ -173,10 +185,10 @@ public function deleteIndex($index) * @param string $index * @return bool */ - public function isEmptyIndex($index) + public function isEmptyIndex(string $index) : bool { - $stats = $this->getClient()->indices()->stats(['index' => $index, 'metric' => 'docs']); - if ($stats['indices'][$index]['primaries']['docs']['count'] == 0) { + $stats = $this->getElasticsearchClient()->indices()->stats(['index' => $index, 'metric' => 'docs']); + if ($stats['indices'][$index]['primaries']['docs']['count'] === 0) { return true; } return false; @@ -190,9 +202,13 @@ public function isEmptyIndex($index) * @param string $oldIndex * @return void */ - public function updateAlias($alias, $newIndex, $oldIndex = '') + public function updateAlias(string $alias, string $newIndex, string $oldIndex = '') { - $params['body'] = ['actions' => []]; + $params = [ + 'body' => [ + 'actions' => [] + ] + ]; if ($oldIndex) { $params['body']['actions'][] = ['remove' => ['alias' => $alias, 'index' => $oldIndex]]; } @@ -200,34 +216,34 @@ public function updateAlias($alias, $newIndex, $oldIndex = '') $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]]; } - $this->getClient()->indices()->updateAliases($params); + $this->getElasticsearchClient()->indices()->updateAliases($params); } /** - * Checks whether Elasticsearch index exists + * Checks whether Elasticsearch 7 index exists * * @param string $index * @return bool */ - public function indexExists($index) + public function indexExists(string $index) : bool { - return $this->getClient()->indices()->exists(['index' => $index]); + return $this->getElasticsearchClient()->indices()->exists(['index' => $index]); } /** - * Check if alias exists. + * Exists alias. * * @param string $alias * @param string $index * @return bool */ - public function existsAlias($alias, $index = '') + public function existsAlias(string $alias, string $index = '') : bool { $params = ['name' => $alias]; if ($index) { $params['index'] = $index; } - return $this->getClient()->indices()->existsAlias($params); + return $this->getElasticsearchClient()->indices()->existsAlias($params); } /** @@ -236,87 +252,74 @@ public function existsAlias($alias, $index = '') * @param string $alias * @return array */ - public function getAlias($alias) + public function getAlias(string $alias) : array { - return $this->getClient()->indices()->getAlias(['name' => $alias]); + return $this->getElasticsearchClient()->indices()->getAlias(['name' => $alias]); } /** - * Add mapping to Elasticsearch index + * Add mapping to Elasticsearch 7 index * * @param array $fields * @param string $index * @param string $entityType * @return void */ - public function addFieldsMapping(array $fields, $index, $entityType) + public function addFieldsMapping(array $fields, string $index, string $entityType) { $params = [ 'index' => $index, 'type' => $entityType, + 'include_type_name' => true, 'body' => [ $entityType => [ - '_all' => [ - 'enabled' => true, - 'type' => 'string' + 'properties' => [ + '_search' => [ + 'type' => 'text' + ], ], - 'properties' => [], 'dynamic_templates' => [ [ 'price_mapping' => [ 'match' => 'price_*', - 'match_mapping' => 'string', + 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'float', - 'store' => true + 'store' => true, ], ], ], [ 'position_mapping' => [ 'match' => 'position_*', - 'match_mapping' => 'string', + 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => 'not_analyzed', + 'index' => true, ], ], ], [ 'string_mapping' => [ 'match' => '*', - 'match_mapping' => 'string', + 'match_mapping_type' => 'string', 'mapping' => [ - 'type' => 'string', - 'index' => 'not_analyzed', + 'type' => 'text', + 'index' => true, + 'copy_to' => '_search' ], ], - ] + ], ], ], ], ]; + foreach ($fields as $field => $fieldInfo) { $params['body'][$entityType]['properties'][$field] = $fieldInfo; } - $this->getClient()->indices()->putMapping($params); - } - /** - * Delete mapping in Elasticsearch index - * - * @param string $index - * @param string $entityType - * @return void - */ - public function deleteMapping($index, $entityType) - { - $this->getClient()->indices()->deleteMapping( - [ - 'index' => $index, - 'type' => $entityType, - ] - ); + $this->getElasticsearchClient()->indices()->putMapping($params); } /** @@ -325,19 +328,25 @@ public function deleteMapping($index, $entityType) * @param array $query * @return array */ - public function query($query) + public function query(array $query) : array { - return $this->getClient()->search($query); + return $this->getElasticsearchClient()->search($query); } /** - * Execute suggest query + * Delete mapping in Elasticsearch 7 index * - * @param array $query - * @return array + * @param string $index + * @param string $entityType + * @return void */ - public function suggest($query) + public function deleteMapping(string $index, string $entityType) { - return $this->getClient()->suggest($query); + $this->getElasticsearchClient()->indices()->deleteMapping( + [ + 'index' => $index, + 'type' => $entityType, + ] + ); } } diff --git a/app/code/Magento/Elasticsearch7/README.md b/app/code/Magento/Elasticsearch7/README.md new file mode 100644 index 0000000000000..f8331665360c5 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/README.md @@ -0,0 +1,2 @@ +Magento\Elasticsearch7 module allows to use Elastic search engine (v7) for product searching capabilities. +The module implements Magento\Search library interfaces. diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch7/SearchAdapter/Adapter.php similarity index 53% rename from app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php rename to app/code/Magento/Elasticsearch7/SearchAdapter/Adapter.php index 6f9ef552351fd..bbc7985f4519d 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php +++ b/app/code/Magento/Elasticsearch7/SearchAdapter/Adapter.php @@ -3,13 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Elasticsearch\SearchAdapter; +declare(strict_types=1); + +namespace Magento\Elasticsearch7\SearchAdapter; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Search\AdapterInterface; use Magento\Framework\Search\RequestInterface; use Magento\Framework\Search\Response\QueryResponse; use Magento\Elasticsearch\SearchAdapter\Aggregation\Builder as AggregationBuilder; +use Magento\Elasticsearch\SearchAdapter\ConnectionManager; +use Magento\Elasticsearch\SearchAdapter\ResponseFactory; +use Psr\Log\LoggerInterface; +use Magento\Framework\Search\AdapterInterface; +use Magento\Elasticsearch\SearchAdapter\QueryContainerFactory; /** * Elasticsearch Search Adapter @@ -21,71 +26,107 @@ class Adapter implements AdapterInterface * * @var Mapper */ - protected $mapper; + private $mapper; /** * Response Factory * * @var ResponseFactory */ - protected $responseFactory; + private $responseFactory; /** * @var ConnectionManager */ - protected $connectionManager; + private $connectionManager; /** * @var AggregationBuilder */ - protected $aggregationBuilder; + private $aggregationBuilder; /** * @var QueryContainerFactory */ private $queryContainerFactory; + /** + * Empty response from Elasticsearch + * + * @var array + */ + private static $emptyRawResponse = [ + "hits" => + [ + "hits" => [] + ], + "aggregations" => + [ + "price_bucket" => [], + "category_bucket" => + [ + "buckets" => [] + + ] + ] + ]; + + /** + * @var LoggerInterface + */ + private $logger; + /** * @param ConnectionManager $connectionManager * @param Mapper $mapper * @param ResponseFactory $responseFactory * @param AggregationBuilder $aggregationBuilder * @param QueryContainerFactory $queryContainerFactory + * @param LoggerInterface $logger */ public function __construct( ConnectionManager $connectionManager, Mapper $mapper, ResponseFactory $responseFactory, AggregationBuilder $aggregationBuilder, - QueryContainerFactory $queryContainerFactory = null + QueryContainerFactory $queryContainerFactory, + LoggerInterface $logger ) { $this->connectionManager = $connectionManager; $this->mapper = $mapper; $this->responseFactory = $responseFactory; $this->aggregationBuilder = $aggregationBuilder; - $this->queryContainerFactory = $queryContainerFactory - ?: ObjectManager::getInstance()->get(QueryContainerFactory::class); + $this->queryContainerFactory = $queryContainerFactory; + $this->logger = $logger; } /** - * @inheritdoc + * Search query + * + * @param RequestInterface $request + * @return QueryResponse */ - public function query(RequestInterface $request) + public function query(RequestInterface $request) : QueryResponse { $client = $this->connectionManager->getConnection(); $aggregationBuilder = $this->aggregationBuilder; - $query = $this->mapper->buildQuery($request); $aggregationBuilder->setQuery($this->queryContainerFactory->create(['query' => $query])); - $rawResponse = $client->query($query); - $rawDocuments = isset($rawResponse['hits']['hits']) ? $rawResponse['hits']['hits'] : []; + try { + $rawResponse = $client->query($query); + } catch (\Exception $e) { + $this->logger->critical($e); + // return empty search result in case an exception is thrown from Elasticsearch + $rawResponse = self::$emptyRawResponse; + } + $rawDocuments = $rawResponse['hits']['hits'] ?? []; $queryResponse = $this->responseFactory->create( [ 'documents' => $rawDocuments, 'aggregations' => $aggregationBuilder->build($request, $rawResponse), - 'total' => isset($rawResponse['hits']['total']) ? $rawResponse['hits']['total'] : 0 + 'total' => $rawResponse['hits']['total']['value'] ?? 0 ] ); return $queryResponse; diff --git a/app/code/Magento/Elasticsearch7/SearchAdapter/Mapper.php b/app/code/Magento/Elasticsearch7/SearchAdapter/Mapper.php new file mode 100644 index 0000000000000..a47d9b6b19cca --- /dev/null +++ b/app/code/Magento/Elasticsearch7/SearchAdapter/Mapper.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Elasticsearch7\SearchAdapter; + +use Magento\Framework\Search\RequestInterface; + +/** + * Elasticsearch7 mapper class + */ +class Mapper +{ + /** + * @var \Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Mapper + */ + private $mapper; + + /** + * Mapper constructor. + * @param \Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Mapper $mapper + */ + public function __construct(\Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Mapper $mapper) + { + $this->mapper = $mapper; + } + + /** + * Build adapter dependent query + * + * @param RequestInterface $request + * @return array + */ + public function buildQuery(RequestInterface $request) : array + { + $searchQuery = $this->mapper->buildQuery($request); + $searchQuery['track_total_hits'] = true; + return $searchQuery; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php similarity index 93% rename from app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php rename to app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php index 5a735da96b754..cb07cfb7bf83d 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php @@ -4,43 +4,45 @@ * See COPYING.txt for license details. */ -namespace Magento\Elasticsearch\Test\Unit\Elasticsearch5\Model\Client; +declare(strict_types=1); -use Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch; -use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; +namespace Magento\Elasticsearch7\Test\Unit\Model\Client; + +use Magento\AdvancedSearch\Model\Client\ClientInterface as ElasticsearchClient; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Elasticsearch7\Model\Client\Elasticsearch; /** - * Test elasticsearch client methods. + * Class ElasticsearchTest to test Elasticsearch 7 */ class ElasticsearchTest extends \PHPUnit\Framework\TestCase { /** * @var ElasticsearchClient */ - protected $model; + private $model; /** * @var \Elasticsearch\Client|\PHPUnit_Framework_MockObject_MockObject */ - protected $elasticsearchClientMock; + private $elasticsearchClientMock; /** * @var \Elasticsearch\Namespaces\IndicesNamespace|\PHPUnit_Framework_MockObject_MockObject */ - protected $indicesMock; + private $indicesMock; /** * @var ObjectManagerHelper */ - protected $objectManager; + private $objectManager; /** * Setup * * @return void */ - protected function setUp(): void + protected function setUp() { $this->elasticsearchClientMock = $this->getMockBuilder(\Elasticsearch\Client::class) ->setMethods( @@ -81,11 +83,11 @@ protected function setUp(): void ->willReturn(true); $this->elasticsearchClientMock->expects($this->any()) ->method('info') - ->willReturn(['version' => ['number' => '5.0.0']]); + ->willReturn(['version' => ['number' => '7.0.0']]); $this->objectManager = new ObjectManagerHelper($this); $this->model = $this->objectManager->getObject( - \Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch::class, + Elasticsearch::class, [ 'options' => $this->getOptions(), 'elasticsearchClient' => $this->elasticsearchClientMock @@ -99,7 +101,7 @@ protected function setUp(): void public function testConstructorOptionsException() { $result = $this->objectManager->getObject( - \Magento\Elasticsearch\Model\Client\Elasticsearch::class, + Elasticsearch::class, [ 'options' => [] ] @@ -113,7 +115,7 @@ public function testConstructorOptionsException() public function testConstructorWithOptions() { $result = $this->objectManager->getObject( - \Magento\Elasticsearch\Model\Client\Elasticsearch::class, + \Magento\Elasticsearch7\Model\Client\Elasticsearch::class, [ 'options' => $this->getOptions() ] @@ -121,6 +123,69 @@ public function testConstructorWithOptions() $this->assertNotNull($result); } + /** + * Ensure that configuration returns correct url. + * + * @param array $options + * @param string $expectedResult + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \ReflectionException + * @dataProvider getOptionsDataProvider + */ + public function testBuildConfig(array $options, $expectedResult): void + { + $buildConfig = new Elasticsearch($options); + $config = $this->getPrivateMethod(Elasticsearch::class, 'buildESConfig'); + $result = $config->invoke($buildConfig, $options); + $this->assertEquals($expectedResult, $result['hosts'][0]); + } + + /** + * Return private method for elastic search class. + * + * @param $className + * @param $methodName + * @return \ReflectionMethod + * @throws \ReflectionException + */ + private function getPrivateMethod($className, $methodName) + { + $reflector = new \ReflectionClass($className); + $method = $reflector->getMethod($methodName); + $method->setAccessible(true); + + return $method; + } + + /** + * Get options data provider. + */ + public function getOptionsDataProvider() + { + return [ + [ + 'without_protocol' => [ + 'hostname' => 'localhost', + 'port' => '9200', + 'timeout' => 15, + 'index' => 'magento2', + 'enableAuth' => 0, + ], + 'expected_result' => 'http://localhost:9200' + ], + [ + 'with_protocol' => [ + 'hostname' => 'https://localhost', + 'port' => '9200', + 'timeout' => 15, + 'index' => 'magento2', + 'enableAuth' => 0, + ], + 'expected_result' => 'https://localhost:9200' + ] + ]; + } + /** * Test ping functionality */ @@ -154,7 +219,7 @@ public function testTestConnectionFalse() public function testTestConnectionPing() { $this->model = $this->objectManager->getObject( - \Magento\Elasticsearch\Model\Client\Elasticsearch::class, + \Magento\Elasticsearch7\Model\Client\Elasticsearch::class, [ 'options' => $this->getEmptyIndexOption(), 'elasticsearchClient' => $this->elasticsearchClientMock @@ -351,13 +416,13 @@ public function testAddFieldsMapping() [ 'index' => 'indexName', 'type' => 'product', + 'include_type_name' => true, 'body' => [ 'product' => [ - '_all' => [ - 'enabled' => true, - 'type' => 'text', - ], 'properties' => [ + '_search' => [ + 'type' => 'text', + ], 'name' => [ 'type' => 'text', ], @@ -379,7 +444,7 @@ public function testAddFieldsMapping() 'match_mapping_type' => 'string', 'mapping' => [ 'type' => 'integer', - 'index' => true + 'index' => true, ], ], ], @@ -390,9 +455,10 @@ public function testAddFieldsMapping() 'mapping' => [ 'type' => 'text', 'index' => true, + 'copy_to' => '_search' ], ], - ], + ] ], ], ], @@ -421,13 +487,13 @@ public function testAddFieldsMappingFailure() [ 'index' => 'indexName', 'type' => 'product', + 'include_type_name' => true, 'body' => [ 'product' => [ - '_all' => [ - 'enabled' => true, - 'type' => 'text', - ], 'properties' => [ + '_search' => [ + 'type' => 'text', + ], 'name' => [ 'type' => 'text', ], @@ -460,6 +526,7 @@ public function testAddFieldsMappingFailure() 'mapping' => [ 'type' => 'text', 'index' => true, + 'copy_to' => '_search' ], ], ] @@ -499,40 +566,6 @@ public function testDeleteMapping() ); } - /** - * Ensure that configuration returns correct url. - * - * @param array $options - * @param string $expectedResult - * @throws \Magento\Framework\Exception\LocalizedException - * @throws \ReflectionException - * @dataProvider getOptionsDataProvider - */ - public function testBuildConfig(array $options, $expectedResult): void - { - $buildConfig = new Elasticsearch($options); - $config = $this->getPrivateMethod(Elasticsearch::class, 'buildConfig'); - $result = $config->invoke($buildConfig, $options); - $this->assertEquals($expectedResult, $result['hosts'][0]); - } - - /** - * Return private method for elastic search class. - * - * @param $className - * @param $methodName - * @return \ReflectionMethod - * @throws \ReflectionException - */ - private function getPrivateMethod($className, $methodName) - { - $reflector = new \ReflectionClass($className); - $method = $reflector->getMethod($methodName); - $method->setAccessible(true); - - return $method; - } - /** * Test deleteMapping() method * @expectedException \Exception @@ -560,7 +593,7 @@ public function testDeleteMappingFailure() */ public function testQuery() { - $query = 'test phrase query'; + $query = ['test phrase query']; $this->elasticsearchClientMock->expects($this->once()) ->method('search') ->with($query) @@ -574,42 +607,13 @@ public function testQuery() */ public function testSuggest() { - $query = 'query'; + $query = ['query']; $this->elasticsearchClientMock->expects($this->once()) ->method('suggest') ->willReturn([]); $this->assertEquals([], $this->model->suggest($query)); } - /** - * Get options data provider. - */ - public function getOptionsDataProvider() - { - return [ - [ - 'without_protocol' => [ - 'hostname' => 'localhost', - 'port' => '9200', - 'timeout' => 15, - 'index' => 'magento2', - 'enableAuth' => 0, - ], - 'expected_result' => 'http://localhost:9200' - ], - [ - 'with_protocol' => [ - 'hostname' => 'https://localhost', - 'port' => '9200', - 'timeout' => 15, - 'index' => 'magento2', - 'enableAuth' => 0, - ], - 'expected_result' => 'https://localhost:9200' - ] - ]; - } - /** * Get elasticsearch client options * @@ -631,7 +635,7 @@ protected function getOptions() /** * @return array */ - protected function getEmptyIndexOption() + private function getEmptyIndexOption() { return [ 'hostname' => 'localhost', diff --git a/app/code/Magento/Elasticsearch7/composer.json b/app/code/Magento/Elasticsearch7/composer.json new file mode 100644 index 0000000000000..c6af833231be1 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/composer.json @@ -0,0 +1,29 @@ +{ + "name": "magento/module-elasticsearch-7", + "description": "N/A", + "require": { + "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/framework": "*", + "magento/module-advanced-search": "*", + "magento/module-catalog-search": "*", + "magento/module-search": "*", + "magento/module-elasticsearch": "*", + "elasticsearch/elasticsearch": "~7.6" + }, + "suggest": { + "magento/module-config": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Elasticsearch7\\": "" + } + } +} diff --git a/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml new file mode 100644 index 0000000000000..cb7cdc2a5b531 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd"> + <system> + <section id="catalog"> + <group id="search"> + <!-- Elasticsearch 7.0+ --> + <field id="elasticsearch7_server_hostname" translate="label" type="text" sortOrder="81" + showInDefault="1" showInWebsite="0" showInStore="0"> + <label>Elasticsearch Server Hostname</label> + <depends> + <field id="engine">elasticsearch7</field> + </depends> + </field> + + <field id="elasticsearch7_server_port" translate="label" type="text" sortOrder="82" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch Server Port</label> + <depends> + <field id="engine">elasticsearch7</field> + </depends> + </field> + + <field id="elasticsearch7_index_prefix" translate="label" type="text" sortOrder="83" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch Index Prefix</label> + <depends> + <field id="engine">elasticsearch7</field> + </depends> + </field> + + <field id="elasticsearch7_enable_auth" translate="label" type="select" sortOrder="84" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Enable Elasticsearch HTTP Auth</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <depends> + <field id="engine">elasticsearch7</field> + </depends> + </field> + + <field id="elasticsearch7_username" translate="label" type="text" sortOrder="85" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch HTTP Username</label> + <depends> + <field id="engine">elasticsearch7</field> + <field id="elasticsearch7_enable_auth">1</field> + </depends> + </field> + + <field id="elasticsearch7_password" translate="label" type="text" sortOrder="86" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch HTTP Password</label> + <depends> + <field id="engine">elasticsearch7</field> + <field id="elasticsearch7_enable_auth">1</field> + </depends> + </field> + + <field id="elasticsearch7_server_timeout" translate="label" type="text" sortOrder="87" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label>Elasticsearch Server Timeout</label> + <depends> + <field id="engine">elasticsearch7</field> + </depends> + </field> + + <field id="elasticsearch7_test_connect_wizard" translate="button_label" sortOrder="88" showInDefault="1" + showInWebsite="0" showInStore="0"> + <label/> + <button_label>Test Connection</button_label> + <frontend_model>Magento\Elasticsearch7\Block\Adminhtml\System\Config\TestConnection</frontend_model> + <depends> + <field id="engine">elasticsearch7</field> + </depends> + </field> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/Elasticsearch7/etc/config.xml b/app/code/Magento/Elasticsearch7/etc/config.xml new file mode 100644 index 0000000000000..63d832c8445f5 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/etc/config.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> + <default> + <catalog> + <search> + <elasticsearch7_server_hostname>localhost</elasticsearch7_server_hostname> + <elasticsearch7_server_port>9200</elasticsearch7_server_port> + <elasticsearch7_index_prefix>magento2</elasticsearch7_index_prefix> + <elasticsearch7_enable_auth>0</elasticsearch7_enable_auth> + <elasticsearch7_server_timeout>15</elasticsearch7_server_timeout> + </search> + </catalog> + </default> +</config> diff --git a/app/code/Magento/Elasticsearch7/etc/di.xml b/app/code/Magento/Elasticsearch7/etc/di.xml new file mode 100644 index 0000000000000..1e480894bc630 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/etc/di.xml @@ -0,0 +1,222 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Elasticsearch\Model\Config"> + <arguments> + <argument name="engineList" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">elasticsearch7</item> + </argument> + </arguments> + </type> + + <type name="Magento\Search\Model\Adminhtml\System\Config\Source\Engine"> + <arguments> + <argument name="engines" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">Elasticsearch 7.0+</item> + </argument> + </arguments> + </type> + + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProviderProxy"> + <arguments> + <argument name="categoryFieldsProviders" xsi:type="array"> + <item name="elasticsearch7" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProvider</item> + </argument> + </arguments> + </type> + + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy"> + <arguments> + <argument name="dataMappers" xsi:type="array"> + <item name="elasticsearch7" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper</item> + </argument> + </arguments> + </type> + + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> + <arguments> + <argument name="productFieldMappers" xsi:type="array"> + <item name="elasticsearch7" xsi:type="object">Magento\Elasticsearch7\Model\Adapter\FieldMapper\ProductFieldMapper</item> + </argument> + </arguments> + </type> + + <type name="Magento\AdvancedSearch\Model\Client\ClientResolver"> + <arguments> + <argument name="clientFactories" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">\Magento\Elasticsearch7\Model\Client\ElasticsearchFactory</item> + </argument> + <argument name="clientOptions" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">\Magento\Elasticsearch\Model\Config</item> + </argument> + </arguments> + </type> + + <type name="Magento\CatalogSearch\Model\Indexer\IndexerHandlerFactory"> + <arguments> + <argument name="handlers" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">Magento\Elasticsearch\Model\Indexer\IndexerHandler</item> + </argument> + </arguments> + </type> + + <type name="Magento\CatalogSearch\Model\Indexer\IndexStructureFactory"> + <arguments> + <argument name="structures" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">Magento\Elasticsearch\Model\Indexer\IndexStructure</item> + </argument> + </arguments> + </type> + + <type name="Magento\CatalogSearch\Model\ResourceModel\EngineProvider"> + <arguments> + <argument name="engines" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">Magento\Elasticsearch\Model\ResourceModel\Engine</item> + </argument> + </arguments> + </type> + + <type name="Magento\Search\Model\AdapterFactory"> + <arguments> + <argument name="adapters" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">\Magento\Elasticsearch7\SearchAdapter\Adapter</item> + </argument> + </arguments> + </type> + + <type name="Magento\Search\Model\EngineResolver"> + <arguments> + <argument name="engines" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">elasticsearch7</item> + </argument> + </arguments> + </type> + + <virtualType name="Magento\Elasticsearch7\Model\Client\ElasticsearchFactory" type="Magento\AdvancedSearch\Model\Client\ClientFactory"> + <arguments> + <argument name="clientClass" xsi:type="string">Magento\Elasticsearch7\Model\Client\Elasticsearch</argument> + </arguments> + </virtualType> + + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Client\ClientFactoryProxy"> + <arguments> + <argument name="clientFactories" xsi:type="array"> + <item name="elasticsearch7" xsi:type="object">Magento\Elasticsearch7\Model\Client\ElasticsearchFactory</item> + </argument> + </arguments> + </type> + + <type name="Magento\Framework\Search\Dynamic\IntervalFactory"> + <arguments> + <argument name="intervals" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval</item> + </argument> + </arguments> + </type> + + <type name="Magento\Framework\Search\Dynamic\DataProviderFactory"> + <arguments> + <argument name="dataProviders" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">Magento\Elasticsearch\SearchAdapter\Dynamic\DataProvider</item> + </argument> + </arguments> + </type> + + <virtualType name="Magento\Elasticsearch7\Model\DataProvider\Suggestions" type="Magento\Elasticsearch\Model\DataProvider\Base\Suggestions"> + <arguments> + <argument name="fieldProvider" xsi:type="object">elasticsearch5FieldProvider</argument> + </arguments> + </virtualType> + <type name="Magento\AdvancedSearch\Model\SuggestedQueries"> + <arguments> + <argument name="data" xsi:type="array"> + <item name="elasticsearch7" xsi:type="string">Magento\Elasticsearch7\Model\DataProvider\Suggestions</item> + </argument> + </arguments> + </type> +<!--todo rename!!!--> + <virtualType name="\Magento\Elasticsearch7\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver" type="\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> + <arguments> + <argument name="items" xsi:type="array"> + <item name="notEav" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\NotEavAttribute</item> + <item name="special" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\SpecialAttribute</item> + <item name="price" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Price</item> + <item name="categoryName" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CategoryName</item> + <item name="position" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Position</item> + <item name="default" xsi:type="object">Magento\Elasticsearch7\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver</item> + </argument> + </arguments> + </virtualType> + + <virtualType name="Magento\Elasticsearch7\Model\Adapter\FieldMapper\ProductFieldMapper" + type="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapper"> + <arguments> + <argument name="fieldProvider" xsi:type="object">elasticsearch5FieldProvider</argument> + <argument name="fieldNameResolver" xsi:type="object">\Magento\Elasticsearch7\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver</argument> + </arguments> + </virtualType> + + <type name="Magento\Search\Model\Search\PageSizeProvider"> + <arguments> + <argument name="pageSizeBySearchEngine" xsi:type="array"> + <item name="elasticsearch7" xsi:type="number">10000</item> + </argument> + </arguments> + </type> + + <virtualType name="elasticsearchLayerCategoryItemCollectionProvider" type="Magento\Elasticsearch\Model\Layer\Category\ItemCollectionProvider"> + <arguments> + <argument name="factories" xsi:type="array"> + <item name="elasticsearch7" xsi:type="object">elasticsearchCategoryCollectionFactory</item> + </argument> + </arguments> + </virtualType> + + <type name="Magento\CatalogSearch\Model\Search\ItemCollectionProvider"> + <arguments> + <argument name="factories" xsi:type="array"> + <item name="elasticsearch7" xsi:type="object">elasticsearchAdvancedCollectionFactory</item> + </argument> + </arguments> + </type> + + <type name="Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategyProvider"> + <arguments> + <argument name="strategies" xsi:type="array"> + <item name="elasticsearch7" xsi:type="object">Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy</item> + </argument> + </arguments> + </type> + + <virtualType name="elasticsearchLayerSearchItemCollectionProvider" type="Magento\Elasticsearch\Model\Layer\Search\ItemCollectionProvider"> + <arguments> + <argument name="factories" xsi:type="array"> + <item name="elasticsearch7" xsi:type="object">elasticsearchFulltextSearchCollectionFactory</item> + </argument> + </arguments> + </virtualType> + + <type name="Magento\Config\Model\Config\TypePool"> + <arguments> + <argument name="sensitive" xsi:type="array"> + <item name="catalog/search/elasticsearch7_password" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch7_server_hostname" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch7_username" xsi:type="string">1</item> + </argument> + <argument name="environment" xsi:type="array"> + <item name="catalog/search/elasticsearch7_enable_auth" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch7_index_prefix" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch7_password" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch7_server_hostname" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch7_server_port" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch7_username" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch7_server_timeout" xsi:type="string">1</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Elasticsearch7/etc/module.xml b/app/code/Magento/Elasticsearch7/etc/module.xml new file mode 100644 index 0000000000000..c5ad0d70cd7d1 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/etc/module.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_Elasticsearch7"> + <sequence> + <module name="Magento_CatalogSearch"/> + <module name="Magento_Search"/> + <module name="Magento_AdvancedSearch"/> + <module name="Magento_Elasticsearch"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/Elasticsearch7/etc/search_engine.xml b/app/code/Magento/Elasticsearch7/etc/search_engine.xml new file mode 100644 index 0000000000000..9633d18669141 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/etc/search_engine.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<engines xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_engine.xsd"> + <engine name="elasticsearch7"> + <feature name="synonyms" support="true" /> + </engine> +</engines> diff --git a/app/code/Magento/Elasticsearch7/registration.php b/app/code/Magento/Elasticsearch7/registration.php new file mode 100644 index 0000000000000..63e13cfbed8f0 --- /dev/null +++ b/app/code/Magento/Elasticsearch7/registration.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +\Magento\Framework\Component\ComponentRegistrar::register( + \Magento\Framework\Component\ComponentRegistrar::MODULE, + 'Magento_Elasticsearch7', + __DIR__ +); diff --git a/composer.json b/composer.json index ac005f9da6a1e..c3e799e138675 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "colinmollenhour/credis": "1.10.0", "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.6", - "elasticsearch/elasticsearch": "~2.0||~5.1||~6.1", + "elasticsearch/elasticsearch": "~7.6", "guzzlehttp/guzzle": "^6.3.3", "league/flysystem": "^1.0", "league/flysystem-aws-s3-v3": "^1.0", @@ -167,6 +167,7 @@ "magento/module-eav": "*", "magento/module-elasticsearch": "*", "magento/module-elasticsearch-6": "*", + "magento/module-elasticsearch-7": "*", "magento/module-email": "*", "magento/module-encryption-key": "*", "magento/module-fedex": "*", diff --git a/composer.lock b/composer.lock index 1236d2b0ae82f..0c5b414eba0b7 100644 --- a/composer.lock +++ b/composer.lock @@ -1,23 +1,23 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "522d676db5baf5864a824409c54948fc", + "content-hash": "637866c2490d86265bb99d3e159d3771", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.133.24", + "version": "3.133.29", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "726426e1514be5220d55ecf02eb1f938a3b4a105" + "reference": "ec8f628041e0d1de4fad76ac041c6025bd38d2da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/726426e1514be5220d55ecf02eb1f938a3b4a105", - "reference": "726426e1514be5220d55ecf02eb1f938a3b4a105", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/ec8f628041e0d1de4fad76ac041c6025bd38d2da", + "reference": "ec8f628041e0d1de4fad76ac041c6025bd38d2da", "shasum": "" }, "require": { @@ -88,7 +88,7 @@ "s3", "sdk" ], - "time": "2020-02-27T19:13:45+00:00" + "time": "2020-03-04T19:10:59+00:00" }, { "name": "braintree/braintree_php", @@ -542,16 +542,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "cbe23383749496fe0f373345208b79568e4bc248" + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/cbe23383749496fe0f373345208b79568e4bc248", - "reference": "cbe23383749496fe0f373345208b79568e4bc248", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1ab9842d69e64fb3a01be6b656501032d1b78cb7", + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7", "shasum": "" }, "require": { @@ -582,7 +582,7 @@ "Xdebug", "performance" ], - "time": "2019-11-06T16:40:04+00:00" + "time": "2020-03-01T12:26:26+00:00" }, { "name": "container-interop/container-interop", @@ -618,33 +618,33 @@ }, { "name": "elasticsearch/elasticsearch", - "version": "v6.7.2", + "version": "v7.6.1", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "9ba89f905ebf699e72dacffa410331c7fecc8255" + "reference": "d4f24bc43c2af60aece3df20eb689d322f9c8acf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/9ba89f905ebf699e72dacffa410331c7fecc8255", - "reference": "9ba89f905ebf699e72dacffa410331c7fecc8255", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/d4f24bc43c2af60aece3df20eb689d322f9c8acf", + "reference": "d4f24bc43c2af60aece3df20eb689d322f9c8acf", "shasum": "" }, "require": { "ext-json": ">=1.3.7", - "guzzlehttp/ringphp": "~1.0", - "php": "^7.0", + "ezimuel/ringphp": "^1.1.2", + "php": "^7.1", "psr/log": "~1.0" }, "require-dev": { - "cpliakas/git-wrapper": "^1.7 || ^2.1", - "doctrine/inflector": "^1.1", + "cpliakas/git-wrapper": "~2.0", + "doctrine/inflector": "^1.3", "mockery/mockery": "^1.2", - "phpstan/phpstan-shim": "^0.9 || ^0.11", - "phpunit/phpunit": "^5.7 || ^6.5", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^7.5", "squizlabs/php_codesniffer": "^3.4", - "symfony/finder": "^2.8", - "symfony/yaml": "^2.8" + "symfony/finder": "~4.0", + "symfony/yaml": "~4.0" }, "suggest": { "ext-curl": "*", @@ -652,6 +652,9 @@ }, "type": "library", "autoload": { + "files": [ + "src/autoload.php" + ], "psr-4": { "Elasticsearch\\": "src/Elasticsearch/" } @@ -674,7 +677,108 @@ "elasticsearch", "search" ], - "time": "2019-07-19T14:48:24+00:00" + "time": "2020-02-15T00:09:00+00:00" + }, + { + "name": "ezimuel/guzzlestreams", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/ezimuel/guzzlestreams.git", + "reference": "abe3791d231167f14eb80d413420d1eab91163a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezimuel/guzzlestreams/zipball/abe3791d231167f14eb80d413420d1eab91163a8", + "reference": "abe3791d231167f14eb80d413420d1eab91163a8", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Fork of guzzle/streams (abandoned) to be used with elasticsearch-php", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "Guzzle", + "stream" + ], + "time": "2020-02-14T23:11:50+00:00" + }, + { + "name": "ezimuel/ringphp", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/ezimuel/ringphp.git", + "reference": "0b78f89d8e0bb9e380046c31adfa40347e9f663b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezimuel/ringphp/zipball/0b78f89d8e0bb9e380046c31adfa40347e9f663b", + "reference": "0b78f89d8e0bb9e380046c31adfa40347e9f663b", + "shasum": "" + }, + "require": { + "ezimuel/guzzlestreams": "^3.0.1", + "php": ">=5.4.0", + "react/promise": "~2.0" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-curl": "Guzzle will use specific adapters if cURL is present" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Ring\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Fork of guzzle/RingPHP (abandoned) to be used with elasticsearch-php", + "time": "2020-02-14T23:51:21+00:00" }, { "name": "guzzlehttp/guzzle", @@ -865,109 +969,6 @@ ], "time": "2019-07-01T23:21:34+00:00" }, - { - "name": "guzzlehttp/ringphp", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/RingPHP.git", - "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/5e2a174052995663dd68e6b5ad838afd47dd615b", - "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b", - "shasum": "" - }, - "require": { - "guzzlehttp/streams": "~3.0", - "php": ">=5.4.0", - "react/promise": "~2.0" - }, - "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-curl": "Guzzle will use specific adapters if cURL is present" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Ring\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", - "abandoned": true, - "time": "2018-07-31T13:22:33+00:00" - }, - { - "name": "guzzlehttp/streams", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/streams.git", - "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", - "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Provides a simple abstraction over streams of data", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "Guzzle", - "stream" - ], - "abandoned": true, - "time": "2014-10-12T19:18:40+00:00" - }, { "name": "justinrainbow/json-schema", "version": "5.2.9", @@ -2415,16 +2416,16 @@ }, { "name": "symfony/console", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f512001679f37e6a042b51897ed24a2f05eba656" + "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f512001679f37e6a042b51897ed24a2f05eba656", - "reference": "f512001679f37e6a042b51897ed24a2f05eba656", + "url": "https://api.github.com/repos/symfony/console/zipball/4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", + "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", "shasum": "" }, "require": { @@ -2487,20 +2488,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2020-01-25T12:44:29+00:00" + "time": "2020-02-24T13:10:00+00:00" }, { "name": "symfony/css-selector", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "a167b1860995b926d279f9bb538f873e3bfa3465" + "reference": "d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/a167b1860995b926d279f9bb538f873e3bfa3465", - "reference": "a167b1860995b926d279f9bb538f873e3bfa3465", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22", + "reference": "d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22", "shasum": "" }, "require": { @@ -2540,20 +2541,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-02-04T09:01:01+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9e3de195e5bc301704dd6915df55892f6dfc208b" + "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9e3de195e5bc301704dd6915df55892f6dfc208b", - "reference": "9e3de195e5bc301704dd6915df55892f6dfc208b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4ad8e149799d3128621a3a1f70e92b9897a8930d", + "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d", "shasum": "" }, "require": { @@ -2610,7 +2611,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2020-01-10T21:54:01+00:00" + "time": "2020-02-04T09:32:40+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -2672,7 +2673,7 @@ }, { "name": "symfony/filesystem", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -2722,16 +2723,16 @@ }, { "name": "symfony/finder", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "3a50be43515590faf812fbd7708200aabc327ec3" + "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/3a50be43515590faf812fbd7708200aabc327ec3", - "reference": "3a50be43515590faf812fbd7708200aabc327ec3", + "url": "https://api.github.com/repos/symfony/finder/zipball/ea69c129aed9fdeca781d4b77eb20b62cf5d5357", + "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357", "shasum": "" }, "require": { @@ -2767,7 +2768,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-02-14T07:42:58+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2946,16 +2947,16 @@ }, { "name": "symfony/process", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "f5697ab4cb14a5deed7473819e63141bf5352c36" + "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f5697ab4cb14a5deed7473819e63141bf5352c36", - "reference": "f5697ab4cb14a5deed7473819e63141bf5352c36", + "url": "https://api.github.com/repos/symfony/process/zipball/bf9166bac906c9e69fb7a11d94875e7ced97bcd7", + "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7", "shasum": "" }, "require": { @@ -2991,7 +2992,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2020-01-09T09:50:08+00:00" + "time": "2020-02-07T20:06:44+00:00" }, { "name": "symfony/service-contracts", @@ -7912,16 +7913,16 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "262ea0d209c292e0330be1041424887bbbffef04" + "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/262ea0d209c292e0330be1041424887bbbffef04", - "reference": "262ea0d209c292e0330be1041424887bbbffef04", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", + "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", "shasum": "" }, "require": { @@ -7973,7 +7974,7 @@ "selenium", "webdriver" ], - "time": "2020-02-17T08:14:38+00:00" + "time": "2020-03-04T14:40:12+00:00" }, { "name": "phpcollection/phpcollection", @@ -8188,26 +8189,25 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", "shasum": "" }, "require": { - "php": "^7.1", + "php": "^7.2", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "^7.1", - "mockery/mockery": "~1", - "phpunit/phpunit": "^7.0" + "ext-tokenizer": "^7.2", + "mockery/mockery": "~1" }, "type": "library", "extra": { @@ -8231,7 +8231,7 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2019-08-22T18:11:29+00:00" + "time": "2020-02-18T18:59:58+00:00" }, { "name": "phpmd/phpmd", @@ -8358,16 +8358,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.10.2", + "version": "v1.10.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9" + "reference": "451c3cd1418cf640de218914901e51b064abb093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9", - "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", "shasum": "" }, "require": { @@ -8417,20 +8417,20 @@ "spy", "stub" ], - "time": "2020-01-20T15:57:02+00:00" + "time": "2020-03-05T15:02:03+00:00" }, { "name": "phpstan/phpstan", - "version": "0.12.11", + "version": "0.12.14", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ca5f2b7cf81c6d8fba74f9576970399c5817e03b" + "reference": "37bdd26a80235d0f9045b49f4151102b7831cbe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ca5f2b7cf81c6d8fba74f9576970399c5817e03b", - "reference": "ca5f2b7cf81c6d8fba74f9576970399c5817e03b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37bdd26a80235d0f9045b49f4151102b7831cbe2", + "reference": "37bdd26a80235d0f9045b49f4151102b7831cbe2", "shasum": "" }, "require": { @@ -8456,7 +8456,7 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2020-02-16T14:00:29+00:00" + "time": "2020-03-02T22:29:43+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9650,16 +9650,16 @@ }, { "name": "symfony/browser-kit", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "45cae6dd8683d2de56df7ec23638e9429c70135f" + "reference": "090ce406505149d6852a7c03b0346dec3b8cf612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/45cae6dd8683d2de56df7ec23638e9429c70135f", - "reference": "45cae6dd8683d2de56df7ec23638e9429c70135f", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/090ce406505149d6852a7c03b0346dec3b8cf612", + "reference": "090ce406505149d6852a7c03b0346dec3b8cf612", "shasum": "" }, "require": { @@ -9705,20 +9705,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-02-23T10:00:59+00:00" }, { "name": "symfony/config", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4d3979f54472637169080f802dc82197e21fdcce" + "reference": "cbfef5ae91ccd3b06621c18d58cd355c68c87ae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4d3979f54472637169080f802dc82197e21fdcce", - "reference": "4d3979f54472637169080f802dc82197e21fdcce", + "url": "https://api.github.com/repos/symfony/config/zipball/cbfef5ae91ccd3b06621c18d58cd355c68c87ae9", + "reference": "cbfef5ae91ccd3b06621c18d58cd355c68c87ae9", "shasum": "" }, "require": { @@ -9769,20 +9769,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-02-04T09:32:40+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "ec60a7d12f5e8ab0f99456adce724717d9c1784a" + "reference": "ebb2e882e8c9e2eb990aa61ddcd389848466e342" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ec60a7d12f5e8ab0f99456adce724717d9c1784a", - "reference": "ec60a7d12f5e8ab0f99456adce724717d9c1784a", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ebb2e882e8c9e2eb990aa61ddcd389848466e342", + "reference": "ebb2e882e8c9e2eb990aa61ddcd389848466e342", "shasum": "" }, "require": { @@ -9842,20 +9842,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2020-01-31T09:49:27+00:00" + "time": "2020-02-29T09:50:10+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "b66fe8ccc850ea11c4cd31677706c1219768bea1" + "reference": "11dcf08f12f29981bf770f097a5d64d65bce5929" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b66fe8ccc850ea11c4cd31677706c1219768bea1", - "reference": "b66fe8ccc850ea11c4cd31677706c1219768bea1", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/11dcf08f12f29981bf770f097a5d64d65bce5929", + "reference": "11dcf08f12f29981bf770f097a5d64d65bce5929", "shasum": "" }, "require": { @@ -9903,20 +9903,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-02-29T10:05:28+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "491a20dfa87e0b3990170593bc2de0bb34d828a5" + "reference": "7e41b4fcad4619535f45f8bfa7744c4f384e1648" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/491a20dfa87e0b3990170593bc2de0bb34d828a5", - "reference": "491a20dfa87e0b3990170593bc2de0bb34d828a5", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7e41b4fcad4619535f45f8bfa7744c4f384e1648", + "reference": "7e41b4fcad4619535f45f8bfa7744c4f384e1648", "shasum": "" }, "require": { @@ -9958,20 +9958,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2020-01-31T09:11:17+00:00" + "time": "2020-02-13T19:40:01+00:00" }, { "name": "symfony/mime", - "version": "v5.0.4", + "version": "v5.0.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "2a3c7fee1f1a0961fa9cf360d5da553d05095e59" + "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2a3c7fee1f1a0961fa9cf360d5da553d05095e59", - "reference": "2a3c7fee1f1a0961fa9cf360d5da553d05095e59", + "url": "https://api.github.com/repos/symfony/mime/zipball/9b3e5b5e58c56bbd76628c952d2b78556d305f3c", + "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c", "shasum": "" }, "require": { @@ -10020,11 +10020,11 @@ "mime", "mime-type" ], - "time": "2020-01-04T14:08:26+00:00" + "time": "2020-02-04T09:41:09+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", @@ -10254,7 +10254,7 @@ }, { "name": "symfony/stopwatch", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -10304,16 +10304,16 @@ }, { "name": "symfony/yaml", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "cd014e425b3668220adb865f53bff64b3ad21767" + "reference": "94d005c176db2080e98825d98e01e8b311a97a88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/cd014e425b3668220adb865f53bff64b3ad21767", - "reference": "cd014e425b3668220adb865f53bff64b3ad21767", + "url": "https://api.github.com/repos/symfony/yaml/zipball/94d005c176db2080e98825d98e01e8b311a97a88", + "reference": "94d005c176db2080e98825d98e01e8b311a97a88", "shasum": "" }, "require": { @@ -10359,7 +10359,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2020-01-21T11:12:16+00:00" + "time": "2020-02-03T10:46:43+00:00" }, { "name": "theseer/fdomdocument", diff --git a/dev/tests/integration/_files/Magento/TestModuleCatalogSearch/Model/ElasticsearchVersionChecker.php b/dev/tests/integration/_files/Magento/TestModuleCatalogSearch/Model/ElasticsearchVersionChecker.php new file mode 100644 index 0000000000000..5e006a0aa1197 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleCatalogSearch/Model/ElasticsearchVersionChecker.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestModuleCatalogSearch\Model; + +use Magento\TestFramework\Helper\Curl; + +/** + * Retrieve elasticsearch version by curl request + */ +class ElasticsearchVersionChecker +{ + /** + * @var int + */ + private $version; + + /** + * @return int + */ + public function getVersion() : int + { + if (!$this->version) { + $curl = new Curl(); + $url = 'http://localhost:9200'; + $curl->get($url); + $curl->addHeader('content-type', 'application/json'); + $data = $curl->getBody(); + $versionData = explode('.', json_decode($data, true)['version']['number']); + $this->version = (int)array_shift($versionData); + } + + return $this->version; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/SortingTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/SortingTest.php index d3c7972453a4b..7bebf9c49d14b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/SortingTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/ListProduct/SortingTest.php @@ -85,6 +85,7 @@ protected function setUp() /** * @magentoDataFixture Magento/Catalog/_files/products_with_not_empty_layered_navigation_attribute.php + * @magentoConfigFixture default/catalog/search/engine mysql * @dataProvider productListSortOrderDataProvider * @param string $sortBy * @param string $direction @@ -100,6 +101,7 @@ public function testProductListSortOrder(string $sortBy, string $direction, arra /** * @magentoDataFixture Magento/Catalog/_files/products_with_not_empty_layered_navigation_attribute.php + * @magentoConfigFixture default/catalog/search/engine mysql * @dataProvider productListSortOrderDataProvider * @param string $sortBy * @param string $direction @@ -172,6 +174,7 @@ public function productListSortOrderDataProvider(): array /** * @magentoDataFixture Magento/Store/_files/second_store.php * @magentoDataFixture Magento/Catalog/_files/products_with_not_empty_layered_navigation_attribute.php + * @magentoConfigFixture default/catalog/search/engine mysql * @dataProvider productListSortOrderDataProviderOnStoreView * @param string $sortBy * @param string $direction @@ -195,6 +198,7 @@ public function testProductListSortOrderOnStoreView( /** * @magentoDataFixture Magento/Store/_files/second_store.php * @magentoDataFixture Magento/Catalog/_files/products_with_not_empty_layered_navigation_attribute.php + * @magentoConfigFixture default/catalog/search/engine mysql * @dataProvider productListSortOrderDataProviderOnStoreView * @param string $sortBy * @param string $direction diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php index 32b7df03f922d..fc24658bb01fa 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php @@ -135,8 +135,8 @@ public function searchStringDataProvider(): array 'description' => '', 'short_description' => '', 'price' => [ - 'from' => '50', - 'to' => '150', + 'from' => 50, + 'to' => 150, ], 'test_searchable_attribute' => '', ], diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/AdapterTest.php index a52c5bb9e21b7..fd0ce8e684665 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/AdapterTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/AdapterTest.php @@ -10,7 +10,7 @@ use Magento\TestFramework\Helper\Bootstrap; /** - * Class AdapterTest + * Class AdapterTest to test Elasticsearch search adapter */ class AdapterTest extends \PHPUnit\Framework\TestCase { @@ -20,7 +20,7 @@ class AdapterTest extends \PHPUnit\Framework\TestCase private $adapter; /** - * @var \Magento\Elasticsearch\Model\Client\Elasticsearch|\PHPUnit\Framework\MockObject\MockObject + * @var \Magento\Elasticsearch6\Model\Client\Elasticsearch|\PHPUnit\Framework\MockObject\MockObject */ private $clientMock; @@ -78,7 +78,6 @@ protected function setUp() /** * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest * @return void */ diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php index 3eea2497daa1f..545a20d4a6475 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Client/ElasticsearchTest.php @@ -13,6 +13,8 @@ use Magento\Elasticsearch6\Model\Client\Elasticsearch as ElasticsearchClient; use Magento\Elasticsearch\Model\Config; use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; +use Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker; +use Magento\Framework\Search\EngineResolverInterface; /** * @magentoDbIsolation enabled @@ -21,6 +23,11 @@ */ class ElasticsearchTest extends \PHPUnit\Framework\TestCase { + /** + * @var string + */ + private $searchEngine; + /** * @var ConnectionManager */ @@ -65,6 +72,15 @@ protected function setUp() $indexer->reindexAll(); } + /** + * Make sure that correct engine is set + */ + protected function assertPreConditions() + { + $currentEngine = Bootstrap::getObjectManager()->get(EngineResolverInterface::class)->getCurrentSearchEngine(); + $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); + } + /** * @param string $text * @return array @@ -95,7 +111,6 @@ private function search($text) } /** - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix composite_product_search */ public function testSearchConfigurableProductBySimpleProductName() @@ -104,7 +119,6 @@ public function testSearchConfigurableProductBySimpleProductName() } /** - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix composite_product_search */ public function testSearchConfigurableProductBySimpleProductAttributeMultiselect() @@ -113,7 +127,6 @@ public function testSearchConfigurableProductBySimpleProductAttributeMultiselect } /** - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix composite_product_search */ public function testSearchConfigurableProductBySimpleProductAttributeSelect() @@ -122,7 +135,6 @@ public function testSearchConfigurableProductBySimpleProductAttributeSelect() } /** - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix composite_product_search */ public function testSearchConfigurableProductBySimpleProductAttributeShortDescription() @@ -146,4 +158,19 @@ private function assertProductWithSkuFound($sku, array $result) } return false; } + + /** + * Returns installed on server search service + * + * @return string + */ + private function getInstalledSearchEngine() + { + if (!$this->searchEngine) { + // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." + $version = Bootstrap::getObjectManager()->get(ElasticsearchVersionChecker::class)->getVersion(); + $this->searchEngine = 'elasticsearch' . $version; + } + return $this->searchEngine; + } } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php index 77533e83b719c..cb94df5ffbcd1 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/IndexHandlerTest.php @@ -17,6 +17,8 @@ use Magento\Elasticsearch\Model\Config; use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; use Magento\Indexer\Model\Indexer; +use Magento\Framework\Search\EngineResolverInterface; +use Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker; /** * Important: Please make sure that each integration test file works with unique elastic search index. In order to @@ -29,6 +31,11 @@ */ class IndexHandlerTest extends \PHPUnit\Framework\TestCase { + /** + * @var string + */ + private $searchEngine; + /** * @var ProductRepositoryInterface */ @@ -87,7 +94,15 @@ protected function setUp() } /** - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 + * Make sure that correct engine is set + */ + protected function assertPreConditions() + { + $currentEngine = Bootstrap::getObjectManager()->get(EngineResolverInterface::class)->getCurrentSearchEngine(); + $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); + } + + /** * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest * @return void */ @@ -106,7 +121,6 @@ public function testReindexAll(): void /** * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest * @return void */ @@ -131,7 +145,6 @@ public function testReindexRowAfterEdit(): void } /** - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest * @return void */ @@ -170,7 +183,6 @@ public function testReindexRowAfterMassAction(): void } /** - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest * @magentoAppArea adminhtml * @return void @@ -192,7 +204,6 @@ public function testReindexRowAfterDelete(): void /** * @magentoDbIsolation enabled * @magentoAppArea adminhtml - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest * @magentoDataFixture Magento/Elasticsearch/_files/configurable_products.php * @return void @@ -254,4 +265,19 @@ private function searchByName(string $text, int $storeId): array return $products; } + + /** + * Returns installed on server search service + * + * @return string + */ + private function getInstalledSearchEngine() + { + if (!$this->searchEngine) { + // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." + $version = Bootstrap::getObjectManager()->get(ElasticsearchVersionChecker::class)->getVersion(); + $this->searchEngine = 'elasticsearch' . $version; + } + return $this->searchEngine; + } } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/ReindexAllTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/ReindexAllTest.php index 031e0d6ad6fd1..828e45953cca1 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/ReindexAllTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Model/Indexer/ReindexAllTest.php @@ -10,9 +10,11 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\Store\Model\StoreManagerInterface; use Magento\Elasticsearch\SearchAdapter\ConnectionManager; -use Magento\Elasticsearch\Model\Client\Elasticsearch as ElasticsearchClient; +use Magento\AdvancedSearch\Model\Client\ClientInterface as ElasticsearchClient; use Magento\Elasticsearch\Model\Config; use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver; +use Magento\Framework\Search\EngineResolverInterface; +use Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker; /** * Important: Please make sure that each integration test file works with unique elastic search index. In order to @@ -22,9 +24,15 @@ * * @magentoDbIsolation disabled * @magentoAppIsolation enabled + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ReindexAllTest extends \PHPUnit\Framework\TestCase { + /** + * @var string + */ + private $searchEngine; + /** * @var ConnectionManager */ @@ -65,10 +73,18 @@ protected function setUp() $this->productRepository = Bootstrap::getObjectManager()->create(ProductRepositoryInterface::class); } + /** + * Make sure that correct engine is set + */ + protected function assertPreConditions() + { + $currentEngine = Bootstrap::getObjectManager()->get(EngineResolverInterface::class)->getCurrentSearchEngine(); + $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); + } + /** * Test search of all products after full reindex * - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest_configurable * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_products.php */ @@ -83,7 +99,6 @@ public function testSearchAll() * Test sorting of all products after full reindex * * @magentoDbIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest_configurable * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_products.php */ @@ -118,7 +133,6 @@ public function testSort() /** * Test search of specific product after full reindex * - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix indexerhandlertest_configurable * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_products.php */ @@ -206,4 +220,19 @@ private function reindexAll() $indexer->load('catalogsearch_fulltext'); $indexer->reindexAll(); } + + /** + * Returns installed on server search service + * + * @return string + */ + private function getInstalledSearchEngine() + { + if (!$this->searchEngine) { + // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." + $version = Bootstrap::getObjectManager()->get(ElasticsearchVersionChecker::class)->getVersion(); + $this->searchEngine = 'elasticsearch' . $version; + } + return $this->searchEngine; + } } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php index a3da32e0d6c40..1a3618965ce02 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Elasticsearch\SearchAdapter; +use Magento\Framework\Search\EngineResolverInterface; +use Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker; + /** * Class AdapterTest * @@ -17,6 +20,7 @@ * * In ElasticSearch, a reindex is required if the test includes a new data fixture with new items to search, see * testAdvancedSearchDateField(). + * phpstan:ignore * */ class AdapterTest extends \Magento\Framework\Search\Adapter\Mysql\AdapterTest @@ -24,7 +28,7 @@ class AdapterTest extends \Magento\Framework\Search\Adapter\Mysql\AdapterTest /** * @var string */ - protected $searchEngine = 'elasticsearch6'; + protected $searchEngine; /** * Get request config path @@ -41,13 +45,22 @@ protected function getRequestConfigPath() */ protected function createAdapter() { - return $this->objectManager->create(\Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Adapter::class); + return $this->objectManager->create(\Magento\Search\Model\AdapterFactory::class)->create(); + } + + /** + * Make sure that correct engine is set + */ + protected function assertPreConditions() + { + $currentEngine = $this->objectManager->get(EngineResolverInterface::class)->getCurrentSearchEngine(); + $this->assertEquals($this->getInstalledSearchEngine(), $currentEngine); } /** * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testMatchQuery() { @@ -56,7 +69,6 @@ public function testMatchQuery() /** * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest */ public function testMatchOrderedQuery() @@ -68,7 +80,6 @@ public function testMatchOrderedQuery() /** * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest */ public function testAggregationsQuery() @@ -78,8 +89,8 @@ public function testAggregationsQuery() /** * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testMatchQueryFilters() { @@ -90,8 +101,8 @@ public function testMatchQueryFilters() * Range filter test with all fields filled * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testRangeFilterWithAllFields() { @@ -102,8 +113,8 @@ public function testRangeFilterWithAllFields() * Range filter test with all fields filled * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testRangeFilterWithoutFromField() { @@ -114,8 +125,8 @@ public function testRangeFilterWithoutFromField() * Range filter test with all fields filled * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testRangeFilterWithoutToField() { @@ -126,8 +137,8 @@ public function testRangeFilterWithoutToField() * Term filter test * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTermFilter() { @@ -138,8 +149,8 @@ public function testTermFilter() * Term filter test * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testTermFilterArray() { @@ -150,8 +161,8 @@ public function testTermFilterArray() * Term filter test * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testWildcardFilter() { @@ -162,8 +173,8 @@ public function testWildcardFilter() * Request limits test * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testSearchLimit() { @@ -174,8 +185,8 @@ public function testSearchLimit() * Bool filter test * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testBoolFilter() { @@ -186,8 +197,8 @@ public function testBoolFilter() * Test bool filter with nested negative bool filter * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testBoolFilterWithNestedNegativeBoolFilter() { @@ -198,8 +209,8 @@ public function testBoolFilterWithNestedNegativeBoolFilter() * Test range inside nested negative bool filter * * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testBoolFilterWithNestedRangeInNegativeBoolFilter() { @@ -211,12 +222,12 @@ public function testBoolFilterWithNestedRangeInNegativeBoolFilter() * * @dataProvider elasticSearchAdvancedSearchDataProvider * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest * @param string $nameQuery * @param string $descriptionQuery * @param array $rangeFilter * @param int $expectedRecordsCount + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testSimpleAdvancedSearch( $nameQuery, @@ -257,7 +268,6 @@ public function elasticSearchAdvancedSearchDataProvider() /** * @magentoAppIsolation enabled * @magentoDataFixture Magento/Framework/Search/_files/filterable_attribute.php - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest */ public function testCustomFilterableAttribute() @@ -272,7 +282,6 @@ public function testCustomFilterableAttribute() * * @magentoAppIsolation enabled * @magentoDataFixture Magento/Framework/Search/_files/filterable_attributes.php - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest * @dataProvider filterByAttributeValuesDataProvider * @param string $requestName @@ -292,7 +301,6 @@ public function testFilterByAttributeValues($requestName, $additionalData) * @param $rangeFilter * @param $expectedRecordsCount * @magentoDataFixture Magento/Framework/Search/_files/date_attribute.php - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest * @magentoAppIsolation enabled * @dataProvider dateDataProvider @@ -307,8 +315,8 @@ public function testAdvancedSearchDateField($rangeFilter, $expectedRecordsCount) /** * @magentoDataFixture Magento/Framework/Search/_files/product_configurable.php * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function testAdvancedSearchCompositeProductWithOutOfStockOption() { @@ -318,7 +326,6 @@ public function testAdvancedSearchCompositeProductWithOutOfStockOption() /** * @magentoDataFixture Magento/Framework/Search/_files/product_configurable_with_disabled_child.php * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest */ public function testAdvancedSearchCompositeProductWithDisabledChild() @@ -331,7 +338,6 @@ public function testAdvancedSearchCompositeProductWithDisabledChild() /** * @magentoDataFixture Magento/Framework/Search/_files/search_weight_products.php * @magentoAppIsolation enabled - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoConfigFixture current_store catalog/search/elasticsearch_index_prefix adaptertest */ public function testSearchQueryBoost() @@ -381,4 +387,19 @@ public function filterByAttributeValuesDataProvider() return $variations; } + + /** + * Returns installed on server search service + * + * @return string + */ + private function getInstalledSearchEngine() + { + if (!$this->searchEngine) { + // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." + $version = $this->objectManager->get(ElasticsearchVersionChecker::class)->getVersion(); + $this->searchEngine = 'elasticsearch' . $version; + } + return $this->searchEngine; + } } diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Controller/Advanced/ResultTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Controller/Advanced/ResultTest.php deleted file mode 100644 index 5f20c1bf82062..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Controller/Advanced/ResultTest.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Elasticsearch6\CatalogSearch\Controller\Advanced; - -use Magento\CatalogSearch\Controller\Advanced\ResultTest as CatalogSearchResultTest; - -/** - * Test cases for catalog advanced search using Elasticsearch 6.0+ search engine. - * - * @magentoDbIsolation disabled - * @magentoAppIsolation enabled - */ -class ResultTest extends CatalogSearchResultTest -{ - /** - * Advanced search test by difference product attributes. - * - * @magentoAppArea frontend - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 - * @magentoDataFixture Magento/CatalogSearch/_files/product_for_search.php - * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php - * @dataProvider searchStringDataProvider - * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod - * - * @param array $searchParams - * @return void - */ - public function testExecute(array $searchParams): void - { - parent::testExecute($searchParams); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Controller/Result/IndexTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Controller/Result/IndexTest.php deleted file mode 100644 index 492983eb8726d..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Controller/Result/IndexTest.php +++ /dev/null @@ -1,37 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Elasticsearch6\CatalogSearch\Controller\Result; - -use Magento\CatalogSearch\Controller\Result\IndexTest as CatalogSearchIndexTest; - -/** - * Test cases for catalog quick search using Elasticsearch 6.0+ search engine. - * - * @magentoDbIsolation disabled - * @magentoAppIsolation enabled - */ -class IndexTest extends CatalogSearchIndexTest -{ - /** - * Quick search test by difference product attributes. - * - * @magentoAppArea frontend - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 - * @magentoDataFixture Magento/CatalogSearch/_files/product_for_search.php - * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php - * @dataProvider searchStringDataProvider - * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod - * - * @param string $searchString - * @return void - */ - public function testExecute(string $searchString): void - { - parent::testExecute($searchString); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Model/Indexer/fulltext/Action/DataProviderTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Model/Indexer/fulltext/Action/DataProviderTest.php deleted file mode 100644 index b50d9034c0f88..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Model/Indexer/fulltext/Action/DataProviderTest.php +++ /dev/null @@ -1,32 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Elasticsearch6\CatalogSearch\Model\Indexer\fulltext\Action; - -use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProviderTest as CatalogSearchDataProviderTest; - -/** - * Search products by attribute value using Elasticsearch 6.0+ search engine. - */ -class DataProviderTest extends CatalogSearchDataProviderTest -{ - /** - * Search product by custom attribute value. - * - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 - * @magentoDataFixture Magento/CatalogSearch/_files/product_for_search.php - * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php - * @magentoDbIsolation disabled - * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod - * - * @return void - */ - public function testSearchProductByAttribute(): void - { - parent::testSearchProductByAttribute(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Model/Search/AttributeSearchWeightTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Model/Search/AttributeSearchWeightTest.php index 71dfebe5a4e84..84fec6dafd089 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Model/Search/AttributeSearchWeightTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch6/CatalogSearch/Model/Search/AttributeSearchWeightTest.php @@ -8,10 +8,11 @@ namespace Magento\Elasticsearch6\CatalogSearch\Model\Search; use Magento\CatalogSearch\Model\Search\AttributeSearchWeightTest as CatalogSearchAttributeSearchWeightTest; +use Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker; +use Magento\TestFramework\Helper\Bootstrap; /** * Test founded products order after quick search with changed attribute search weight - * using Elasticsearch 6.0+ search engine. * * @magentoAppIsolation enabled */ @@ -20,7 +21,6 @@ class AttributeSearchWeightTest extends CatalogSearchAttributeSearchWeightTest /** * Perform search by word and check founded product order in different cases. * - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 * @magentoDataFixture Magento/CatalogSearch/_files/products_for_sku_search_weight_score.php * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php * @dataProvider attributeSearchWeightDataProvider diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/ConfigurableProduct/Model/QuickSearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/ConfigurableProduct/Model/QuickSearchTest.php deleted file mode 100644 index 50cb4974a9cf1..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch6/ConfigurableProduct/Model/QuickSearchTest.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Elasticsearch6\ConfigurableProduct\Model; - -use Magento\ConfigurableProduct\Model\QuickSearchTest as ConfigurableProductQuickSearchTest; - -/** - * Test cases related to find configurable product via quick search using Elasticsearch 6.0+ search engine. - * - * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php - * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php - * - * @magentoDbIsolation disabled - * @magentoAppIsolation enabled - */ -class QuickSearchTest extends ConfigurableProductQuickSearchTest -{ - /** - * Assert that configurable child products has not found by query using Elasticsearch 6.0+ search engine. - * - * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod - * - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 - * - * @return void - */ - public function testChildProductsHasNotFoundedByQuery(): void - { - parent::testChildProductsHasNotFoundedByQuery(); - } - - /** - * Assert that child product of configurable will be available by search after - * set to product visibility by catalog and search using Elasticsearch 6.0+ search engine. - * - * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod - * - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 - * - * @dataProvider productAvailabilityInSearchByVisibilityDataProvider - * - * @param int $visibility - * @param bool $expectedResult - * @return void - */ - public function testOneOfChildIsAvailableBySearch(int $visibility, bool $expectedResult): void - { - parent::testOneOfChildIsAvailableBySearch($visibility, $expectedResult); - } - - /** - * Assert that configurable product was found by option value using Elasticsearch 6.0+ search engine. - * - * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod - * - * @magentoConfigFixture default/catalog/search/engine elasticsearch6 - * - * @return void - */ - public function testSearchByOptionValue(): void - { - parent::testSearchByOptionValue(); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/Controller/QuickSearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/Controller/QuickSearchTest.php index 637d3d1a9d252..eb6cf6971573c 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch6/Controller/QuickSearchTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch6/Controller/QuickSearchTest.php @@ -9,6 +9,7 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\TestCase\AbstractController; +use Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker; /** * Tests quick search on Storefront. @@ -26,6 +27,12 @@ class QuickSearchTest extends AbstractController protected function setUp() { parent::setUp(); + // phpstan:ignore "Class Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker not found." + $checker = $this->_objectManager->get(ElasticsearchVersionChecker::class); + + if ($checker->getVersion() !== 6) { + $this->markTestSkipped('The installed elasticsearch version isn\'t supported by test'); + } $this->storeManager = $this->_objectManager->get(StoreManagerInterface::class); } @@ -42,7 +49,7 @@ protected function setUp() * @magentoConfigFixture fixturestore_store catalog/search/elasticsearch6_index_prefix storefront_quick_search * @magentoDataFixture Magento/Catalog/_files/products_for_search.php * @magentoDataFixture Magento/Store/_files/core_fixturestore.php - * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php + * @magentoDataFixture Magento/Elasticsearch6/_files/full_reindex.php */ public function testQuickSearchWithImprovedPriceRangeCalculation() { diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch6/_files/full_reindex.php b/dev/tests/integration/testsuite/Magento/Elasticsearch6/_files/full_reindex.php new file mode 100644 index 0000000000000..66c556d25ae19 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch6/_files/full_reindex.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +$checker = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\TestModuleCatalogSearch\Model\ElasticsearchVersionChecker::class +); +if ($checker->getVersion() === 6) { + include __DIR__ . '/../../../Magento/CatalogSearch/_files/full_reindex.php'; +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromCreateProject/composer.lock b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromCreateProject/composer.lock index d9da3edf3d209..e5cb7bd3d57fe 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromCreateProject/composer.lock +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testFromCreateProject/composer.lock @@ -4991,7 +4991,7 @@ "shasum": "ed1da1137848560dde1a85f0f54dc2fac262359e" }, "require": { - "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1", + "elasticsearch/elasticsearch": "~7.6", "magento/framework": "102.0.*", "magento/module-advanced-search": "100.3.*", "magento/module-catalog": "103.0.*", @@ -5031,7 +5031,7 @@ "shasum": "a9da3243900390ad163efc7969b07116d2eb793f" }, "require": { - "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1", + "elasticsearch/elasticsearch": "~7.6", "magento/framework": "102.0.*", "magento/module-advanced-search": "100.3.*", "magento/module-catalog-search": "101.0.*", @@ -9664,7 +9664,7 @@ "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.6", "dotmailer/dotmailer-magento2-extension": "3.1.1", - "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1", + "elasticsearch/elasticsearch": "~7.6", "ext-bcmath": "*", "ext-ctype": "*", "ext-curl": "*", diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testSkeleton/composer.lock b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testSkeleton/composer.lock index d755bfa0479e6..e0a4d7f6929e8 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testSkeleton/composer.lock +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/_files/testSkeleton/composer.lock @@ -4991,7 +4991,7 @@ "shasum": "ed1da1137848560dde1a85f0f54dc2fac262359e" }, "require": { - "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1", + "elasticsearch/elasticsearch": "~7.6", "magento/framework": "102.0.*", "magento/module-advanced-search": "100.3.*", "magento/module-catalog": "103.0.*", @@ -5031,7 +5031,7 @@ "shasum": "a9da3243900390ad163efc7969b07116d2eb793f" }, "require": { - "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1", + "elasticsearch/elasticsearch": "~7.6", "magento/framework": "102.0.*", "magento/module-advanced-search": "100.3.*", "magento/module-catalog-search": "101.0.*", @@ -9664,7 +9664,7 @@ "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.6", "dotmailer/dotmailer-magento2-extension": "3.1.1", - "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1", + "elasticsearch/elasticsearch": "~7.6", "ext-bcmath": "*", "ext-ctype": "*", "ext-curl": "*", diff --git a/dev/tests/static/framework/Magento/PhpStan/Formatters/FilteredErrorFormatter.php b/dev/tests/static/framework/Magento/PhpStan/Formatters/FilteredErrorFormatter.php index b3a4bd9ae0791..3da08f324d761 100644 --- a/dev/tests/static/framework/Magento/PhpStan/Formatters/FilteredErrorFormatter.php +++ b/dev/tests/static/framework/Magento/PhpStan/Formatters/FilteredErrorFormatter.php @@ -61,6 +61,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in $clearedAnalysisResult = new AnalysisResult( $fileSpecificErrorsWithoutIgnoredErrors, $analysisResult->getNotFileSpecificErrors(), + $analysisResult->getWarnings(), $analysisResult->isDefaultLevelUsed(), $analysisResult->hasInferrablePropertyTypesFromConstructor(), $analysisResult->getProjectConfigFile() diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index af785c28db414..6954983d049a5 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -4243,4 +4243,11 @@ ['Zend_Mime', 'Magento\Framework\HTTP\Mime'], ['Magento\Framework\Encryption\Crypt', 'Magento\Framework\Encryption\EncryptionAdapterInterface'], ['Magento\Wishlist\Setup\Patch\Schema\AddProductIdConstraint'], + ['Magento\Elasticsearch\Block\Adminhtml\System\Config\TestConnection'], + ['Magento\Elasticsearch\Model\Adapter\BatchDataMapper\CategoryFieldsProvider'], + ['Magento\Elasticsearch\Model\Adapter\DataMapper\ProductDataMapper'], + ['Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper'], + ['Magento\Elasticsearch\Model\Client\Elasticsearch'], + ['Magento\Elasticsearch\SearchAdapter\Aggregation\Interval'], + ]; From af251eb2311a7d477f5c6516195b7032704c755a Mon Sep 17 00:00:00 2001 From: AleksLi <aleksliwork@gmail.com> Date: Fri, 6 Mar 2020 21:10:28 +0100 Subject: [PATCH 214/369] MC-26683: Some sort of example how I see the solution of this. --- .../Magento/GraphQl/Controller/GraphQl.php | 2 + .../Model/Cart/AddProductsToCart.php | 18 +++- .../Exception/GraphQlCartInputException.php | 97 +++++++++++++++++++ 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 lib/internal/Magento/Framework/GraphQl/Exception/GraphQlCartInputException.php diff --git a/app/code/Magento/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php index 2d72fde91b031..a5b8a14ae0793 100644 --- a/app/code/Magento/GraphQl/Controller/GraphQl.php +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -160,6 +160,8 @@ public function dispatch(RequestInterface $request) : ResponseInterface } catch (\Exception $error) { $result['errors'] = isset($result) && isset($result['errors']) ? $result['errors'] : []; $result['errors'][] = $this->graphQlError->create($error); + // here we should have data from GraphQlCartInputException + $result['data'] = $error->getData(); $statusCode = ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS; } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index cfe78389fffe4..b9722d276975a 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -8,11 +8,12 @@ namespace Magento\QuoteGraphQl\Model\Cart; use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\Message\MessageInterface; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Model\Quote; /** - * Adding products to cart using GraphQL + * Add products to cart */ class AddProductsToCart { @@ -43,16 +44,29 @@ public function __construct( * * @param Quote $cart * @param array $cartItems + * @return \Magento\Framework\GraphQl\Exception\GraphQlCartInputException * @throws GraphQlInputException * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException */ - public function execute(Quote $cart, array $cartItems): void + public function execute(Quote $cart, array $cartItems): \Magento\Framework\GraphQl\Exception\GraphQlCartInputException { foreach ($cartItems as $cartItemData) { $this->addProductToCart->execute($cart, $cartItemData); } + if ($cart->getData('has_error')) { + $e = new \Magento\Framework\GraphQl\Exception\GraphQlCartInputException(__('Shopping cart errors')); + $errors = $cart->getErrors(); + foreach ($errors as $error) { + /** @var MessageInterface $error */ + $e->addError(new GraphQlInputException(__($error->getText()))); + } + $e->addData($cartItems); + + throw $e; + } + $this->cartRepository->save($cart); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlCartInputException.php b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlCartInputException.php new file mode 100644 index 0000000000000..2dbbc1c20476e --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlCartInputException.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Exception; + +use Magento\Framework\Exception\AggregateExceptionInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; +use GraphQL\Error\ClientAware; + +class GraphQlCartInputException extends LocalizedException implements AggregateExceptionInterface, ClientAware +{ + const EXCEPTION_CATEGORY = 'graphql-input'; + + /** + * @var boolean + */ + private $isSafe; + + /** + * The array of errors that have been added via the addError() method + * + * @var \Magento\Framework\Exception\LocalizedException[] + */ + private $errors = []; + + /** + * @var array + */ + private $data = []; + + /** + * Initialize object + * + * @param Phrase $phrase + * @param \Exception $cause + * @param int $code + * @param boolean $isSafe + */ + public function __construct(Phrase $phrase, \Exception $cause = null, $code = 0, $isSafe = true) + { + $this->isSafe = $isSafe; + parent::__construct($phrase, $cause, $code); + } + + /** + * @inheritdoc + */ + public function isClientSafe() : bool + { + return $this->isSafe; + } + + /** + * @inheritdoc + */ + public function getCategory() : string + { + return self::EXCEPTION_CATEGORY; + } + + /** + * Add child error if used as aggregate exception + * + * @param LocalizedException $exception + * @return $this + */ + public function addError(LocalizedException $exception): self + { + $this->errors[] = $exception; + return $this; + } + + /** + * Get child errors if used as aggregate exception + * + * @return LocalizedException[] + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @param array $data + * @return GraphQlInputException + */ + public function addData(array $data): self + { + $this->data = $data; + return $this; + } +} From 428605d04e815c782cfbe20922f244970d153006 Mon Sep 17 00:00:00 2001 From: Eduard Chitoraga <e.chitoraga@atwix.com> Date: Fri, 6 Mar 2020 22:29:04 +0200 Subject: [PATCH 215/369] Static tests fixes --- .../Magento/Wishlist/Block/Share/Email/Items.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Wishlist/Block/Share/Email/Items.php b/app/code/Magento/Wishlist/Block/Share/Email/Items.php index c7ff49943b222..130c7cb136afb 100644 --- a/app/code/Magento/Wishlist/Block/Share/Email/Items.php +++ b/app/code/Magento/Wishlist/Block/Share/Email/Items.php @@ -4,11 +4,6 @@ * See COPYING.txt for license details. */ -/** - * Wishlist block customer items - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Wishlist\Block\Share\Email; use Magento\Catalog\Model\Product; @@ -19,6 +14,8 @@ use Magento\Wishlist\Model\Item; /** + * Wishlist share items + * * @api * @since 100.0.2 */ @@ -36,6 +33,7 @@ class Items extends \Magento\Wishlist\Block\AbstractBlock /** * Items constructor. + * * @param \Magento\Catalog\Block\Product\Context $context * @param \Magento\Framework\App\Http\Context $httpContext * @param array $data @@ -59,6 +57,7 @@ public function __construct( * Identify the product from which thumbnail should be taken. * * @param Item $item + * * @return Product */ public function getProductForThumbnail(Item $item): Product @@ -71,6 +70,7 @@ public function getProductForThumbnail(Item $item): Product * * @param \Magento\Catalog\Model\Product $product * @param array $additional + * * @return string */ public function getProductUrl($product, $additional = []) @@ -84,6 +84,7 @@ public function getProductUrl($product, $additional = []) * * @param \Magento\Catalog\Model\Product $product * @param array $additional + * * @return string */ public function getAddToCartUrl($product, $additional = []) @@ -97,6 +98,7 @@ public function getAddToCartUrl($product, $additional = []) * Check whether wishlist item has description * * @param \Magento\Wishlist\Model\Item $item + * * @return bool */ public function hasDescription($item) From b2d07939fa03becf51151ebc49597614a71d3397 Mon Sep 17 00:00:00 2001 From: Eduard Chitoraga <e.chitoraga@atwix.com> Date: Fri, 6 Mar 2020 22:32:03 +0200 Subject: [PATCH 216/369] Static tests fixes --- .../view/frontend/templates/email/items.phtml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml b/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml index 8c71a8de033dc..782b7d4892e62 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/email/items.phtml @@ -11,14 +11,15 @@ <table> <tr> <?php $i = 0; - foreach ($block->getWishlistItems() as $item) : $i++ ?> - <?php /* @var $item \Magento\Wishlist\Model\Item */ ?> - <?php /* @var $_product \Magento\Catalog\Model\Product */ ?> + foreach ($block->getWishlistItems() as $item): $i++ ?> + <?php /* @var \Magento\Wishlist\Model\Item $item */ ?> + <?php /* @var \Magento\Catalog\Model\Product $_product */ ?> <?php $_product = $item->getProduct(); ?> <td class="col product"> <p> <a href="<?= $block->escapeUrl($block->getProductUrl($item)) ?>"> - <?= /* @noEscape */ $block->getImage($block->getProductForThumbnail($item), 'product_small_image')->toHtml() ?> + <?php $productThumbnail = $block->getProductForThumbnail($item) ?> + <?= /* @noEscape */ $block->getImage($productThumbnail, 'product_small_image')->toHtml() ?> </a> </p> @@ -27,7 +28,7 @@ <strong><?= $block->escapeHtml($_product->getName()) ?></strong> </a> </p> - <?php if ($block->hasDescription($item)) : ?> + <?php if ($block->hasDescription($item)): ?> <p> <strong><?= $block->escapeHtml(__('Comment')) ?>:</strong> <br/><?= /* @noEscape */ $block->getEscapedDescription($item) ?> @@ -39,14 +40,14 @@ </a> </p> </td> - <?php if ($i % 3 != 0) : ?> + <?php if ($i % 3 != 0): ?> <td></td> - <?php else : ?> + <?php else: ?> </tr> <tr> <td colspan="5"> </td> </tr> - <?php if ($i < $l) : ?> + <?php if ($i < $l): ?> <tr> <?php endif ?> <?php endif ?> From e01eda97df7ac400636e52dd8f454affb4e652ea Mon Sep 17 00:00:00 2001 From: Raul E Watson <raul.watson@maginus.com> Date: Sat, 7 Mar 2020 01:59:32 +0000 Subject: [PATCH 217/369] address static build failures --- .../Magento/Framework/Component/ComponentRegistrar.php | 7 +++---- .../Magento/Framework/Module/ModuleList/Loader.php | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/internal/Magento/Framework/Component/ComponentRegistrar.php b/lib/internal/Magento/Framework/Component/ComponentRegistrar.php index 8dba61bc5945b..bd20582875ce0 100644 --- a/lib/internal/Magento/Framework/Component/ComponentRegistrar.php +++ b/lib/internal/Magento/Framework/Component/ComponentRegistrar.php @@ -48,13 +48,12 @@ public static function register($type, $componentName, $path) ucfirst($type) . ' \'' . $componentName . '\' from \'' . $path . '\' ' . 'has been already defined in \'' . self::$paths[$type][$componentName] . '\'.' ); - } else { - self::$paths[$type][$componentName] = str_replace('\\', '/', $path); } + self::$paths[$type][$componentName] = str_replace('\\', '/', $path); } /** - * {@inheritdoc} + * @inheritdoc */ public function getPaths($type) { @@ -63,7 +62,7 @@ public function getPaths($type) } /** - * {@inheritdoc} + * @inheritdoc */ public function getPath($type, $componentName) { diff --git a/lib/internal/Magento/Framework/Module/ModuleList/Loader.php b/lib/internal/Magento/Framework/Module/ModuleList/Loader.php index 704d259be6486..7e484407d7a54 100644 --- a/lib/internal/Magento/Framework/Module/ModuleList/Loader.php +++ b/lib/internal/Magento/Framework/Module/ModuleList/Loader.php @@ -80,7 +80,7 @@ public function load(array $exclude = []) $result = []; $excludeSet = array_flip($exclude); - foreach ($this->getModuleConfigs() as [$file, $contents]) { + foreach ($this->getModuleConfigs() as list($file, $contents)) { try { $this->parser->loadXML($contents); } catch (\Magento\Framework\Exception\LocalizedException $e) { From b91a20406aa353dd969dae294550ec0ff0f63371 Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Sat, 7 Mar 2020 20:06:57 +0200 Subject: [PATCH 218/369] Added MFTF tests to check presence/absence of the category filter item in the layered navigation --- .../Catalog/Test/Mftf/Data/ConfigData.xml | 19 ++++++ ...rontCategoryPageWithCategoryFilterTest.xml | 64 +++++++++++++++++++ ...tCategoryPageWithoutCategoryFilterTest.xml | 64 +++++++++++++++++++ 3 files changed, 147 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Data/ConfigData.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ConfigData.xml new file mode 100644 index 0000000000000..35c5c8ac3c866 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ConfigData.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EnableCategoryFilterOnCategoryPageConfigData"> + <data key="path">catalog/layered_navigation/display_category</data> + <data key="value">1</data> + </entity> + <entity name="DisableCategoryFilterOnCategoryPageConfigData"> + <data key="path">catalog/layered_navigation/display_category</data> + <data key="value">0</data> + </entity> +</entities> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml new file mode 100644 index 0000000000000..35a532b27ea10 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCategoryPageWithCategoryFilterTest"> + <annotations> + <title value="Category with Layered Navigation - verify presence of category filter"/> + <stories value="Category page: Layered Navigation with category filter"/> + <description value="Verify that the category filter is present in layered navigation on category page"/> + <features value="Catalog"/> + <severity value="MINOR"/> + <group value="Catalog"/> + </annotations> + + <before> + <!-- Create one category --> + <createData entity="_defaultCategory" stepKey="defaultCategory"> + <field key="name">TopCategory</field> + </createData> + <!-- Create second category, having as parent the 1st one --> + <createData entity="SubCategoryWithParent" stepKey="subCategory"> + <field key="name">SubCategory</field> + <field key="parent_id">$$defaultCategory.id$$</field> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <!-- Create a product assigned to the 1st category --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <!-- Create 2nd product assigned to the 2nd category --> + <!-- The "Category filter" item is not shown in layered navigation --> + <!-- if there are not subcategories with products to show --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="subCategory"/> + </createData> + + <!-- Set the category filter to be present on the category page layered navigation --> + <magentoCLI command="config:set {{EnableCategoryFilterOnCategoryPageConfigData.path}} {{EnableCategoryFilterOnCategoryPageConfigData.value}}" stepKey="setCategoryFilterVisibleOnStorefront"/> + + <!-- Flush cache --> + <magentoCLI command="cache:flush" stepKey="clearCache1"/> + </before> + + <after> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="subCategory" stepKey="deleteSubCategory"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategoryMainCategory"/> + </after> + + <amOnPage url="{{StorefrontCategoryPage.url($$defaultCategory.name$$)}}" stepKey="navigateToCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + + <!-- Verify category filter item is present --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml new file mode 100644 index 0000000000000..7604415a2d7ff --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCategoryPageWithoutCategoryFilterTest"> + <annotations> + <title value="Category with Layered Navigation - verify absence of category filter"/> + <stories value="Category page: Layered Navigation without category filter"/> + <description value="Verify that the category filter is NOT present in layered navigation on category page"/> + <features value="Catalog"/> + <severity value="MINOR"/> + <group value="Catalog"/> + </annotations> + + <before> + <!-- Create one category --> + <createData entity="_defaultCategory" stepKey="defaultCategory"> + <field key="name">TopCategory</field> + </createData> + <!-- Create second category, having as parent the 1st one --> + <createData entity="SubCategoryWithParent" stepKey="subCategory"> + <field key="name">SubCategory</field> + <field key="parent_id">$$defaultCategory.id$$</field> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <!-- Create a product assigned to the 1st category --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <!-- Create 2nd product assigned to the 2nd category --> + <!-- The "Category filter" item is not shown in layered navigation --> + <!-- if there are not subcategories with products to show --> + <createData entity="_defaultProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="subCategory"/> + </createData> + + <!-- Set the category filter to NOT be present on the category page layered navigation --> + <magentoCLI command="config:set {{DisableCategoryFilterOnCategoryPageConfigData.path}} {{DisableCategoryFilterOnCategoryPageConfigData.value}}" stepKey="hideCategoryFilterOnStorefront"/> + + <!-- Flush cache --> + <magentoCLI command="cache:flush" stepKey="clearCache"/> + </before> + + <after> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="subCategory" stepKey="deleteSubCategory"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategoryMainCategory"/> + </after> + + <amOnPage url="{{StorefrontCategoryPage.url($$defaultCategory.name$$)}}" stepKey="navigateToCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + + <!-- Verify category filter item is NOT present --> + <dontSee selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + </test> +</tests> From 05e2f32ef611437912360da44c1292da6d742614 Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Sat, 7 Mar 2020 21:49:19 +0200 Subject: [PATCH 219/369] Updated the MFTF tests previously created --- ...sibilityInLayeredNavigationActionGroup.xml | 20 +++++++++++++++++++ ...rontCategoryPageWithCategoryFilterTest.xml | 9 ++++----- ...tCategoryPageWithoutCategoryFilterTest.xml | 9 ++++----- 3 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontChangeCategoryFilterVisibilityInLayeredNavigationActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontChangeCategoryFilterVisibilityInLayeredNavigationActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontChangeCategoryFilterVisibilityInLayeredNavigationActionGroup.xml new file mode 100644 index 0000000000000..124e49a01c290 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontChangeCategoryFilterVisibilityInLayeredNavigationActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- On a category page with layered navigation, verify if the category filter item is present --> + <actionGroup name="StorefrontCheckCategoryFilterIsVisibleInLayeredNavigationActionGroup"> + <!-- Verify category filter item is present --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + </actionGroup> + + <!-- On a category page with layered navigation, verify if the category filter item is NOT present --> + <actionGroup name="StorefrontCheckCategoryFilterIsNotVisibleInLayeredNavigationActionGroup"> + <!-- Verify category filter item is NOT present --> + <dontSee selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml index 35a532b27ea10..4431588d07ded 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml @@ -44,7 +44,6 @@ <!-- Set the category filter to be present on the category page layered navigation --> <magentoCLI command="config:set {{EnableCategoryFilterOnCategoryPageConfigData.path}} {{EnableCategoryFilterOnCategoryPageConfigData.value}}" stepKey="setCategoryFilterVisibleOnStorefront"/> - <!-- Flush cache --> <magentoCLI command="cache:flush" stepKey="clearCache1"/> </before> @@ -55,10 +54,10 @@ <deleteData createDataKey="defaultCategory" stepKey="deleteCategoryMainCategory"/> </after> - <amOnPage url="{{StorefrontCategoryPage.url($$defaultCategory.name$$)}}" stepKey="navigateToCategoryPage"/> - <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="navigateToCategoryPage"> + <argument name="category" value="$$defaultCategory$$"/> + </actionGroup> - <!-- Verify category filter item is present --> - <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + <actionGroup ref="StorefrontCheckCategoryFilterIsVisibleInLayeredNavigationActionGroup" stepKey="checkCategoryFilterIsPresent" /> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml index 7604415a2d7ff..db76552dd4eb8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml @@ -44,7 +44,6 @@ <!-- Set the category filter to NOT be present on the category page layered navigation --> <magentoCLI command="config:set {{DisableCategoryFilterOnCategoryPageConfigData.path}} {{DisableCategoryFilterOnCategoryPageConfigData.value}}" stepKey="hideCategoryFilterOnStorefront"/> - <!-- Flush cache --> <magentoCLI command="cache:flush" stepKey="clearCache"/> </before> @@ -55,10 +54,10 @@ <deleteData createDataKey="defaultCategory" stepKey="deleteCategoryMainCategory"/> </after> - <amOnPage url="{{StorefrontCategoryPage.url($$defaultCategory.name$$)}}" stepKey="navigateToCategoryPage"/> - <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="navigateToCategoryPage"> + <argument name="category" value="$$defaultCategory$$"/> + </actionGroup> - <!-- Verify category filter item is NOT present --> - <dontSee selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + <actionGroup ref="StorefrontCheckCategoryFilterIsNotVisibleInLayeredNavigationActionGroup" stepKey="checkCategoryFilterIsPresent" /> </test> </tests> From 5b0bd95e28479bc5ff5f257b5056e1c6007f3afc Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Mon, 9 Mar 2020 08:37:45 +0200 Subject: [PATCH 220/369] Adjusted MFTF added tests action groups names --- ...igationCategoryFilterNotVisibleActionGroup.xml} | 8 +------- ...dNavigationCategoryFilterVisibleActionGroup.xml | 14 ++++++++++++++ ...torefrontCategoryPageWithCategoryFilterTest.xml | 2 +- ...efrontCategoryPageWithoutCategoryFilterTest.xml | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) rename app/code/Magento/Catalog/Test/Mftf/ActionGroup/{StorefrontChangeCategoryFilterVisibilityInLayeredNavigationActionGroup.xml => AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup.xml} (55%) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontChangeCategoryFilterVisibilityInLayeredNavigationActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup.xml similarity index 55% rename from app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontChangeCategoryFilterVisibilityInLayeredNavigationActionGroup.xml rename to app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup.xml index 124e49a01c290..c892594201f17 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontChangeCategoryFilterVisibilityInLayeredNavigationActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup.xml @@ -6,14 +6,8 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <!-- On a category page with layered navigation, verify if the category filter item is present --> - <actionGroup name="StorefrontCheckCategoryFilterIsVisibleInLayeredNavigationActionGroup"> - <!-- Verify category filter item is present --> - <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> - </actionGroup> - <!-- On a category page with layered navigation, verify if the category filter item is NOT present --> - <actionGroup name="StorefrontCheckCategoryFilterIsNotVisibleInLayeredNavigationActionGroup"> + <actionGroup name="AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup"> <!-- Verify category filter item is NOT present --> <dontSee selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup.xml new file mode 100644 index 0000000000000..8eebafd3cd8ab --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- On a category page with layered navigation, verify if the category filter item is present --> + <actionGroup name="AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup"> + <!-- Verify category filter item is present --> + <see selector="{{StorefrontCategorySidebarSection.layeredFilterBlock}}" userInput="Category" stepKey="seeCategoryFilterInLayeredNav"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml index 4431588d07ded..8955f43e1b335 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml @@ -58,6 +58,6 @@ <argument name="category" value="$$defaultCategory$$"/> </actionGroup> - <actionGroup ref="StorefrontCheckCategoryFilterIsVisibleInLayeredNavigationActionGroup" stepKey="checkCategoryFilterIsPresent" /> + <actionGroup ref="AssertStorefrontLayeredNavigationCategoryFilterVisibleActionGroup" stepKey="checkCategoryFilterIsPresent" /> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml index db76552dd4eb8..7900a712e0664 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml @@ -58,6 +58,6 @@ <argument name="category" value="$$defaultCategory$$"/> </actionGroup> - <actionGroup ref="StorefrontCheckCategoryFilterIsNotVisibleInLayeredNavigationActionGroup" stepKey="checkCategoryFilterIsPresent" /> + <actionGroup ref="AssertStorefrontLayeredNavigationCategoryFilterNotVisibleActionGroup" stepKey="checkCategoryFilterIsNotPresent" /> </test> </tests> From e2f2b7930373dfda19e36dcd70031ae7e5e5fb4f Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Mon, 9 Mar 2020 08:45:19 +0200 Subject: [PATCH 221/369] Fixed failed unit test after review adjustments --- .../Catalog/Test/Unit/Model/Layer/FilterListTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php index 2b579ecea6b83..84c433379b156 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php @@ -71,8 +71,8 @@ protected function setUp() $this->model = new FilterList( $this->objectManagerMock, $this->attributeListMock, - $filters, - $this->layerCategoryConfigMock + $this->layerCategoryConfigMock, + $filters ); } @@ -112,7 +112,7 @@ public function testGetFilters($method, $value, $expectedClass) ->will($this->returnValue([$this->attributeMock])); $this->layerCategoryConfigMock->expects($this->once()) - ->method('isCategoryVisibleInLayer') + ->method('isCategoryFilterVisibleInLayerNavigation') ->willReturn(true); $this->assertEquals(['filter', 'filter'], $this->model->getFilters($this->layerMock)); @@ -156,7 +156,7 @@ public function testGetFiltersWithoutCategoryFilter( ->will($this->returnValue([$this->attributeMock])); $this->layerCategoryConfigMock->expects($this->once()) - ->method('isCategoryVisibleInLayer') + ->method('isCategoryFilterVisibleInLayerNavigation') ->willReturn(false); $this->assertEquals($expectedResult, $this->model->getFilters($this->layerMock)); From 929902465697b9e0ea48d8126a7ad4f18767bf75 Mon Sep 17 00:00:00 2001 From: Piotr Markiewicz <piotr.markiewicz@vaimo.com> Date: Mon, 9 Mar 2020 08:44:15 +0100 Subject: [PATCH 222/369] Added translations and removed fully qualified global functions --- .../Controller/Adminhtml/Export/File/Delete.php | 6 +++--- .../Controller/Adminhtml/Export/File/Download.php | 6 +++--- app/code/Magento/ImportExport/i18n/en_US.csv | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php index 75d772922c70c..316607bb247fb 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php @@ -63,7 +63,7 @@ public function execute() $resultRedirect->setPath('adminhtml/export/index'); $fileName = $this->getRequest()->getParam('filename'); if (empty($fileName)) { - $this->messageManager->addErrorMessage(\__('Please provide valid export file name')); + $this->messageManager->addErrorMessage(__('Please provide valid export file name')); return $resultRedirect; } @@ -73,9 +73,9 @@ public function execute() if ($directory->isFile($path)) { $this->file->deleteFile($path); - $this->messageManager->addSuccessMessage(\__('File %1 deleted', $fileName)); + $this->messageManager->addSuccessMessage(__('File %1 deleted', $fileName)); } else { - $this->messageManager->addErrorMessage(\__('%1 is not a valid file', $fileName)); + $this->messageManager->addErrorMessage(__('%1 is not a valid file', $fileName)); } } catch (FileSystemException $exception) { $this->messageManager->addErrorMessage($exception->getMessage()); diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php index 48e8b8f1d9d07..4107e19860328 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php @@ -61,8 +61,8 @@ public function execute() $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('adminhtml/export/index'); $fileName = $this->getRequest()->getParam('filename'); - if (empty($fileName) || \preg_match('/\.\.(\\\|\/)/', $fileName) !== 0) { - $this->messageManager->addErrorMessage(\__('Please provide valid export file name')); + if (empty($fileName) || preg_match('/\.\.(\\\|\/)/', $fileName) !== 0) { + $this->messageManager->addErrorMessage(__('Please provide valid export file name')); return $resultRedirect; } @@ -76,7 +76,7 @@ public function execute() DirectoryList::VAR_DIR ); } - $this->messageManager->addErrorMessage(\__('%1 is not a valid file', $fileName)); + $this->messageManager->addErrorMessage(__('%1 is not a valid file', $fileName)); } catch (\Exception $exception) { $this->messageManager->addErrorMessage($exception->getMessage()); } diff --git a/app/code/Magento/ImportExport/i18n/en_US.csv b/app/code/Magento/ImportExport/i18n/en_US.csv index fae93c78baa09..a4943fe72826f 100644 --- a/app/code/Magento/ImportExport/i18n/en_US.csv +++ b/app/code/Magento/ImportExport/i18n/en_US.csv @@ -124,3 +124,6 @@ Summary,Summary "Message is added to queue, wait to get your file soon. Make sure your cron job is running to export the file","Message is added to queue, wait to get your file soon. Make sure your cron job is running to export the file" "Invalid data","Invalid data" "Invalid response","Invalid response" +"File %1 deleted","File %1 deleted" +"Please provide valid export file name","Please provide valid export file name" +"%1 is not a valid file","%1 is not a valid file" From bbee9822e5810faf730c5fd0572db7ed36a39b32 Mon Sep 17 00:00:00 2001 From: Slava Mankivski <mankivsk@adobe.com> Date: Mon, 9 Mar 2020 11:18:46 -0500 Subject: [PATCH 223/369] Fixed docblock --- app/code/Magento/Cms/Helper/Wysiwyg/Images.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/code/Magento/Cms/Helper/Wysiwyg/Images.php b/app/code/Magento/Cms/Helper/Wysiwyg/Images.php index c634910f4468f..e42bb0143f6bf 100644 --- a/app/code/Magento/Cms/Helper/Wysiwyg/Images.php +++ b/app/code/Magento/Cms/Helper/Wysiwyg/Images.php @@ -253,6 +253,12 @@ public function getCurrentPath() return $this->_currentPath; } + /** + * Create subdirectory if doesn't exist + * + * @param string $absPath Path of subdirectory to create + * @throws \Magento\Framework\Exception\LocalizedException + */ private function createSubDirIfNotExist(string $absPath) { $relPath = $this->_directory->getRelativePath($absPath); From 007a89114b9d67b1ecfe1646f85447c1ccb28506 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 9 Mar 2020 17:34:38 +0100 Subject: [PATCH 224/369] DEPRECATED: AbstractAccount for Magento_Customer controllers --- .../Customer/Controller/AbstractAccount.php | 4 ++-- .../Customer/Controller/Plugin/Account.php | 23 ++++++++++++------- app/code/Magento/Customer/etc/frontend/di.xml | 2 +- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Customer/Controller/AbstractAccount.php b/app/code/Magento/Customer/Controller/AbstractAccount.php index 21611329ed9bc..aa0eca1423c17 100644 --- a/app/code/Magento/Customer/Controller/AbstractAccount.php +++ b/app/code/Magento/Customer/Controller/AbstractAccount.php @@ -9,9 +9,9 @@ use Magento\Framework\App\Action\Action; /** - * Class AbstractAccount - * @package Magento\Customer\Controller * @SuppressWarnings(PHPMD.NumberOfChildren) + * @deprecated 2.4.0 + * @see \Magento\Customer\Controller\AccountInterface */ abstract class AbstractAccount extends Action implements AccountInterface { diff --git a/app/code/Magento/Customer/Controller/Plugin/Account.php b/app/code/Magento/Customer/Controller/Plugin/Account.php index 179da148e7f78..5fc65eb845563 100644 --- a/app/code/Magento/Customer/Controller/Plugin/Account.php +++ b/app/code/Magento/Customer/Controller/Plugin/Account.php @@ -3,13 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Controller\Plugin; +use Magento\Customer\Controller\AccountInterface; use Magento\Customer\Model\Session; use Magento\Framework\App\ActionInterface; use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; -use Magento\Framework\App\Action\AbstractAction; use Magento\Framework\Controller\ResultInterface; class Account @@ -23,29 +25,35 @@ class Account * @var array */ private $allowedActions = []; + /** + * @var RequestInterface + */ + private $request; /** + * @param RequestInterface $request * @param Session $customerSession * @param array $allowedActions List of actions that are allowed for not authorized users */ public function __construct( + RequestInterface $request, Session $customerSession, array $allowedActions = [] ) { $this->session = $customerSession; $this->allowedActions = $allowedActions; + $this->request = $request; } /** * Dispatch actions allowed for not authorized users * - * @param AbstractAction $subject - * @param RequestInterface $request + * @param AccountInterface $subject * @return void */ - public function beforeDispatch(AbstractAction $subject, RequestInterface $request) + public function beforeExecute(AccountInterface $subject) { - $action = strtolower($request->getActionName()); + $action = strtolower($this->request->getActionName()); $pattern = '/^(' . implode('|', $this->allowedActions) . ')$/i'; if (!preg_match($pattern, $action)) { @@ -60,13 +68,12 @@ public function beforeDispatch(AbstractAction $subject, RequestInterface $reques /** * Remove No-referer flag from customer session * - * @param AbstractAction $subject + * @param AccountInterface $subject * @param ResponseInterface|ResultInterface $result - * @param RequestInterface $request * @return ResponseInterface|ResultInterface * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterDispatch(AbstractAction $subject, $result, RequestInterface $request) + public function afterExecute(AccountInterface $subject, $result) { $this->session->unsNoReferer(false); return $result; diff --git a/app/code/Magento/Customer/etc/frontend/di.xml b/app/code/Magento/Customer/etc/frontend/di.xml index a479d0d2af328..8867042cc6dc9 100644 --- a/app/code/Magento/Customer/etc/frontend/di.xml +++ b/app/code/Magento/Customer/etc/frontend/di.xml @@ -51,7 +51,7 @@ </argument> </arguments> </type> - <type name="Magento\Customer\Controller\AbstractAccount"> + <type name="Magento\Customer\Controller\AccountInterface"> <plugin name="customer_account" type="Magento\Customer\Controller\Plugin\Account" /> </type> <type name="Magento\Checkout\Block\Cart\Sidebar"> From 26aa6bdeba46c180e05941f2592a457537ef83be Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 9 Mar 2020 18:10:20 +0100 Subject: [PATCH 225/369] Missing PHPDoc to modified files --- .../Magento/Customer/Controller/AbstractAccount.php | 2 ++ .../Magento/Customer/Controller/Plugin/Account.php | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Customer/Controller/AbstractAccount.php b/app/code/Magento/Customer/Controller/AbstractAccount.php index aa0eca1423c17..36a96521cc6d1 100644 --- a/app/code/Magento/Customer/Controller/AbstractAccount.php +++ b/app/code/Magento/Customer/Controller/AbstractAccount.php @@ -9,6 +9,8 @@ use Magento\Framework\App\Action\Action; /** + * AbstractAccount class is deprecated, in favour of Composition approach to build Controllers + * * @SuppressWarnings(PHPMD.NumberOfChildren) * @deprecated 2.4.0 * @see \Magento\Customer\Controller\AccountInterface diff --git a/app/code/Magento/Customer/Controller/Plugin/Account.php b/app/code/Magento/Customer/Controller/Plugin/Account.php index 5fc65eb845563..b7352873269e9 100644 --- a/app/code/Magento/Customer/Controller/Plugin/Account.php +++ b/app/code/Magento/Customer/Controller/Plugin/Account.php @@ -14,6 +14,9 @@ use Magento\Framework\App\ResponseInterface; use Magento\Framework\Controller\ResultInterface; +/** + * Plugin verifies permissions using Action Name against injected (`fontend/di.xml`) rules + */ class Account { /** @@ -21,15 +24,16 @@ class Account */ protected $session; - /** - * @var array - */ - private $allowedActions = []; /** * @var RequestInterface */ private $request; + /** + * @var array + */ + private $allowedActions = []; + /** * @param RequestInterface $request * @param Session $customerSession From 54ae63a63dd0fd7101299cf4b4390c98a33a3f29 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 9 Mar 2020 18:19:49 +0100 Subject: [PATCH 226/369] Adjust Unit Tests for the change --- .../Unit/Controller/Plugin/AccountTest.php | 90 +++++++++---------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php index 2c70a8bda28fe..7dd376d57bdb0 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php @@ -3,18 +3,22 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Test\Unit\Controller\Plugin; +use Magento\Customer\Controller\AccountInterface; use Magento\Customer\Controller\Plugin\Account; use Magento\Customer\Model\Session; use Magento\Framework\App\ActionFlag; use Magento\Framework\App\ActionInterface; -use Magento\Framework\App\Action\AbstractAction; use Magento\Framework\App\Request\Http; +use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\Controller\ResultInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class AccountTest extends \PHPUnit\Framework\TestCase +class AccountTest extends TestCase { /** * @var string @@ -27,59 +31,51 @@ class AccountTest extends \PHPUnit\Framework\TestCase protected $plugin; /** - * @var Session | \PHPUnit_Framework_MockObject_MockObject + * @var Session|MockObject */ - protected $session; + protected $sessionMock; /** - * @var AbstractAction | \PHPUnit_Framework_MockObject_MockObject + * @var AccountInterface|MockObject */ - protected $subject; + protected $actionMock; /** - * @var Http | \PHPUnit_Framework_MockObject_MockObject + * @var Http|MockObject */ - protected $request; + protected $requestMock; /** - * @var ActionFlag | \PHPUnit_Framework_MockObject_MockObject + * @var ActionFlag|MockObject */ - protected $actionFlag; + protected $actionFlagMock; /** - * @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterface|MockObject */ - private $resultInterface; + private $resultMock; protected function setUp() { - $this->session = $this->getMockBuilder(\Magento\Customer\Model\Session::class) + $this->sessionMock = $this->getMockBuilder(Session::class) ->disableOriginalConstructor() - ->setMethods([ - 'setNoReferer', - 'unsNoReferer', - 'authenticate', - ]) + ->setMethods(['setNoReferer', 'unsNoReferer', 'authenticate']) ->getMock(); - $this->subject = $this->getMockBuilder(AbstractAction::class) - ->setMethods([ - 'getActionFlag', - ]) + $this->actionMock = $this->getMockBuilder(AccountInterface::class) + ->setMethods(['getActionFlag']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + $this->requestMock = $this->getMockBuilder(HttpRequest::class) ->disableOriginalConstructor() - ->setMethods([ - 'getActionName', - ]) + ->setMethods(['getActionName']) ->getMock(); - $this->resultInterface = $this->getMockBuilder(ResultInterface::class) + $this->resultMock = $this->getMockBuilder(ResultInterface::class) ->getMockForAbstractClass(); - $this->actionFlag = $this->getMockBuilder(\Magento\Framework\App\ActionFlag::class) + $this->actionFlagMock = $this->getMockBuilder(ActionFlag::class) ->disableOriginalConstructor() ->getMock(); } @@ -90,47 +86,43 @@ protected function setUp() * @param boolean $isActionAllowed * @param boolean $isAuthenticated * - * @dataProvider beforeDispatchDataProvider + * @dataProvider beforeExecuteDataProvider */ - public function testBeforeDispatch( - $action, - $allowedActions, - $isActionAllowed, - $isAuthenticated - ) { - $this->request->expects($this->once()) + public function testBeforeExecute($action, $allowedActions, $isActionAllowed, $isAuthenticated) + { + $this->requestMock->expects($this->once()) ->method('getActionName') ->willReturn($action); if ($isActionAllowed) { - $this->session->expects($this->once()) + $this->sessionMock->expects($this->once()) ->method('setNoReferer') ->with(true) ->willReturnSelf(); } else { - $this->session->expects($this->once()) + $this->sessionMock->expects($this->once()) ->method('authenticate') ->willReturn($isAuthenticated); if (!$isAuthenticated) { - $this->subject->expects($this->once()) + $this->actionMock->expects($this->once()) ->method('getActionFlag') - ->willReturn($this->actionFlag); + ->willReturn($this->actionFlagMock); - $this->actionFlag->expects($this->once()) + $this->actionFlagMock->expects($this->once()) ->method('set') ->with('', ActionInterface::FLAG_NO_DISPATCH, true) ->willReturnSelf(); } } - $plugin = new Account($this->session, $allowedActions); - $plugin->beforeDispatch($this->subject, $this->request); + $plugin = new Account($this->requestMock, $this->sessionMock, $allowedActions); + $plugin->beforeExecute($this->actionMock); } /** * @return array */ - public function beforeDispatchDataProvider() + public function beforeExecuteDataProvider() { return [ [ @@ -166,9 +158,9 @@ public function beforeDispatchDataProvider() ]; } - public function testAfterDispatch() + public function testAfterExecute() { - $this->session->expects($this->once()) + $this->sessionMock->expects($this->once()) ->method('unsNoReferer') ->with(false) ->willReturnSelf(); @@ -176,13 +168,13 @@ public function testAfterDispatch() $plugin = (new ObjectManager($this))->getObject( Account::class, [ - 'session' => $this->session, + 'session' => $this->sessionMock, 'allowedActions' => ['testaction'] ] ); $this->assertSame( - $this->resultInterface, - $plugin->afterDispatch($this->subject, $this->resultInterface, $this->request) + $this->resultMock, + $plugin->afterExecute($this->actionMock, $this->resultMock, $this->requestMock) ); } } From 85a5e19531ef53fae2230cae45b3cdfc73734f52 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Mon, 9 Mar 2020 13:46:54 -0500 Subject: [PATCH 227/369] MC-32270: Cart.applied_gift_cards resolver exception message thrown when removing an item from cart need to be refactored - Added code fix and test changes for exception message --- .../QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php | 4 ++-- .../Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php | 4 ++-- .../Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php index bf9ccef8ae44a..d202fcfb7a81d 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php @@ -65,9 +65,9 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); + throw new GraphQlNoSuchEntityException(__('The Cart doesn\'t contain the item.')); } catch (LocalizedException $e) { - throw new GraphQlInputException(__($e->getMessage()), $e); + throw new GraphQlInputException(__($e->getMessage())); } return [ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php index c93db424834ef..931f01858fcae 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php @@ -84,7 +84,7 @@ public function testRemoveNonExistentItem() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $notExistentItemId = 999; - $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item."); + $this->expectExceptionMessage("Cart doesn't contain the item."); $query = $this->getQuery($maskedQuoteId, $notExistentItemId); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); @@ -106,7 +106,7 @@ public function testRemoveItemIfItemIsNotBelongToCart() 'virtual-product' ); - $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); + $this->expectExceptionMessage("Cart doesn't contain the item."); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php index 6f105259bf65c..fc24ded85ab82 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php @@ -74,7 +74,7 @@ public function testRemoveNonExistentItem() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $notExistentItemId = 999; - $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item."); + $this->expectExceptionMessage("Cart doesn't contain the item."); $query = $this->getQuery($maskedQuoteId, $notExistentItemId); $this->graphQlMutation($query); @@ -95,7 +95,7 @@ public function testRemoveItemIfItemIsNotBelongToCart() 'virtual-product' ); - $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item."); + $this->expectExceptionMessage("Cart doesn't contain the item."); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); $this->graphQlMutation($query); From b6839aa26544744ffc16b28d5fb16628100e3cdd Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Mon, 9 Mar 2020 14:35:08 -0500 Subject: [PATCH 228/369] MC-32270: Cart.applied_gift_cards resolver exception message thrown when removing an item from cart need to be refactored - Added code fix changes --- .../Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php index d202fcfb7a81d..82dc71d6a082c 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php @@ -67,7 +67,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } catch (NoSuchEntityException $e) { throw new GraphQlNoSuchEntityException(__('The Cart doesn\'t contain the item.')); } catch (LocalizedException $e) { - throw new GraphQlInputException(__($e->getMessage())); + throw new GraphQlInputException(__($e->getMessage()), $e); } return [ From bb52ab904990bc6642490d5b06ef7817018afa96 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Mon, 9 Mar 2020 14:56:42 -0500 Subject: [PATCH 229/369] MC-32270: Cart.applied_gift_cards resolver exception message thrown when removing an item from cart need to be refactored - Added new phrase changes in en_US file --- app/code/Magento/Quote/i18n/en_US.csv | 1 + .../Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Quote/i18n/en_US.csv b/app/code/Magento/Quote/i18n/en_US.csv index b24179297493a..c899c432c70d4 100644 --- a/app/code/Magento/Quote/i18n/en_US.csv +++ b/app/code/Magento/Quote/i18n/en_US.csv @@ -31,6 +31,7 @@ Subtotal,Subtotal "Cart %1 does not contain item %2","Cart %1 does not contain item %2" "Could not save quote","Could not save quote" "Cart %1 doesn't contain item %2","Cart %1 doesn't contain item %2" +"The cart doesn't contain the item","The cart doesn't contain the item" "Could not remove item from quote","Could not remove item from quote" "The qty value is required to update quote item.","The qty value is required to update quote item." "Minimum order amount is %1","Minimum order amount is %1" diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php index 82dc71d6a082c..c2045d4a0e8d5 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php @@ -65,7 +65,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value try { $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId); } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__('The Cart doesn\'t contain the item.')); + throw new GraphQlNoSuchEntityException(__('The cart doesn\'t contain the item')); } catch (LocalizedException $e) { throw new GraphQlInputException(__($e->getMessage()), $e); } From bf47a0083345933a32a5e273dfbfc9f0f7bcf424 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Mon, 9 Mar 2020 15:02:31 -0500 Subject: [PATCH 230/369] MC-32270: Cart.applied_gift_cards resolver exception message thrown when removing an item from cart need to be refactored - Added new changes to test --- .../Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php | 4 ++-- .../Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php index 931f01858fcae..eb34197595853 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php @@ -84,7 +84,7 @@ public function testRemoveNonExistentItem() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $notExistentItemId = 999; - $this->expectExceptionMessage("Cart doesn't contain the item."); + $this->expectExceptionMessage("The cart doesn't contain the item"); $query = $this->getQuery($maskedQuoteId, $notExistentItemId); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); @@ -106,7 +106,7 @@ public function testRemoveItemIfItemIsNotBelongToCart() 'virtual-product' ); - $this->expectExceptionMessage("Cart doesn't contain the item."); + $this->expectExceptionMessage("The cart doesn't contain the item"); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php index fc24ded85ab82..4714e2b03182e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/RemoveItemFromCartTest.php @@ -74,7 +74,7 @@ public function testRemoveNonExistentItem() $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $notExistentItemId = 999; - $this->expectExceptionMessage("Cart doesn't contain the item."); + $this->expectExceptionMessage("The cart doesn't contain the item"); $query = $this->getQuery($maskedQuoteId, $notExistentItemId); $this->graphQlMutation($query); @@ -95,7 +95,7 @@ public function testRemoveItemIfItemIsNotBelongToCart() 'virtual-product' ); - $this->expectExceptionMessage("Cart doesn't contain the item."); + $this->expectExceptionMessage("The cart doesn't contain the item"); $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId); $this->graphQlMutation($query); From c47a6a26aae710f2fe64e92d25e03e456c90ae79 Mon Sep 17 00:00:00 2001 From: AleksLi <aleksliwork@gmail.com> Date: Mon, 9 Mar 2020 22:17:08 +0100 Subject: [PATCH 231/369] MC-26683: Added errors to the return model --- .../Magento/GraphQl/Controller/GraphQl.php | 2 - .../Model/Cart/AddProductsToCart.php | 17 +--- .../QuoteGraphQl/Model/Resolver/CartItems.php | 7 ++ .../Exception/GraphQlCartInputException.php | 97 ------------------- 4 files changed, 9 insertions(+), 114 deletions(-) delete mode 100644 lib/internal/Magento/Framework/GraphQl/Exception/GraphQlCartInputException.php diff --git a/app/code/Magento/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php index a5b8a14ae0793..2d72fde91b031 100644 --- a/app/code/Magento/GraphQl/Controller/GraphQl.php +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -160,8 +160,6 @@ public function dispatch(RequestInterface $request) : ResponseInterface } catch (\Exception $error) { $result['errors'] = isset($result) && isset($result['errors']) ? $result['errors'] : []; $result['errors'][] = $this->graphQlError->create($error); - // here we should have data from GraphQlCartInputException - $result['data'] = $error->getData(); $statusCode = ExceptionFormatter::HTTP_GRAPH_QL_SCHEMA_ERROR_STATUS; } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php index b9722d276975a..0360d9ccf5476 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php @@ -13,7 +13,7 @@ use Magento\Quote\Model\Quote; /** - * Add products to cart + * Adding products to cart using GraphQL */ class AddProductsToCart { @@ -44,29 +44,16 @@ public function __construct( * * @param Quote $cart * @param array $cartItems - * @return \Magento\Framework\GraphQl\Exception\GraphQlCartInputException * @throws GraphQlInputException * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException */ - public function execute(Quote $cart, array $cartItems): \Magento\Framework\GraphQl\Exception\GraphQlCartInputException + public function execute(Quote $cart, array $cartItems): void { foreach ($cartItems as $cartItemData) { $this->addProductToCart->execute($cart, $cartItemData); } - if ($cart->getData('has_error')) { - $e = new \Magento\Framework\GraphQl\Exception\GraphQlCartInputException(__('Shopping cart errors')); - $errors = $cart->getErrors(); - foreach ($errors as $error) { - /** @var MessageInterface $error */ - $e->addError(new GraphQlInputException(__($error->getText()))); - } - $e->addData($cartItems); - - throw $e; - } - $this->cartRepository->save($cart); } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php index 2674b3728619a..8017a91b5cfd2 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php @@ -9,6 +9,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Model\Quote\Item as QuoteItem; @@ -29,6 +30,12 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $cart = $value['model']; $itemsData = []; + if ($cart->getData('has_error')) { + $errors = $cart->getErrors(); + foreach ($errors as $error) { + $itemsData[] = new GraphQlInputException(__($error->getText())); + } + } foreach ($cart->getAllVisibleItems() as $cartItem) { /** * @var QuoteItem $cartItem diff --git a/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlCartInputException.php b/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlCartInputException.php deleted file mode 100644 index 2dbbc1c20476e..0000000000000 --- a/lib/internal/Magento/Framework/GraphQl/Exception/GraphQlCartInputException.php +++ /dev/null @@ -1,97 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\GraphQl\Exception; - -use Magento\Framework\Exception\AggregateExceptionInterface; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Phrase; -use GraphQL\Error\ClientAware; - -class GraphQlCartInputException extends LocalizedException implements AggregateExceptionInterface, ClientAware -{ - const EXCEPTION_CATEGORY = 'graphql-input'; - - /** - * @var boolean - */ - private $isSafe; - - /** - * The array of errors that have been added via the addError() method - * - * @var \Magento\Framework\Exception\LocalizedException[] - */ - private $errors = []; - - /** - * @var array - */ - private $data = []; - - /** - * Initialize object - * - * @param Phrase $phrase - * @param \Exception $cause - * @param int $code - * @param boolean $isSafe - */ - public function __construct(Phrase $phrase, \Exception $cause = null, $code = 0, $isSafe = true) - { - $this->isSafe = $isSafe; - parent::__construct($phrase, $cause, $code); - } - - /** - * @inheritdoc - */ - public function isClientSafe() : bool - { - return $this->isSafe; - } - - /** - * @inheritdoc - */ - public function getCategory() : string - { - return self::EXCEPTION_CATEGORY; - } - - /** - * Add child error if used as aggregate exception - * - * @param LocalizedException $exception - * @return $this - */ - public function addError(LocalizedException $exception): self - { - $this->errors[] = $exception; - return $this; - } - - /** - * Get child errors if used as aggregate exception - * - * @return LocalizedException[] - */ - public function getErrors(): array - { - return $this->errors; - } - - /** - * @param array $data - * @return GraphQlInputException - */ - public function addData(array $data): self - { - $this->data = $data; - return $this; - } -} From 3f0bde0b712427079f92ed6d96531775b5c2f1fd Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Mon, 9 Mar 2020 17:01:37 -0500 Subject: [PATCH 232/369] MC-13825: [2.4.x] Migrate ZF2 components to Laminas -- fix merge conflict --- composer.json | 61 +- composer.lock | 5461 +++++++++-------- .../Customer/Controller/AccountTest.php | 4 +- .../HeaderProvider/AbstractHeaderTestCase.php | 5 +- lib/internal/Magento/Framework/composer.json | 26 +- 5 files changed, 2884 insertions(+), 2673 deletions(-) diff --git a/composer.json b/composer.json index ac005f9da6a1e..882fb5f93b0ef 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,35 @@ "composer/composer": "^1.6", "elasticsearch/elasticsearch": "~2.0||~5.1||~6.1", "guzzlehttp/guzzle": "^6.3.3", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-config": "^2.6.0", + "laminas/laminas-console": "^2.6.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-dependency-plugin": "^1.0", + "laminas/laminas-di": "^2.6.1", + "laminas/laminas-eventmanager": "^3.0.0", + "laminas/laminas-feed": "^2.9.0", + "laminas/laminas-form": "^2.10.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-i18n": "^2.7.3", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.1", + "laminas/laminas-mail": "^2.9.0", + "laminas/laminas-mime": "^2.5.0", + "laminas/laminas-modulemanager": "^2.7", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-serializer": "^2.7.2", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.8", + "laminas/laminas-session": "^2.7.3", + "laminas/laminas-soap": "^2.7.0", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-text": "^2.6.0", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0", + "laminas/laminas-view": "~2.11.2", "league/flysystem": "^1.0", "league/flysystem-aws-s3-v3": "^1.0", "league/flysystem-azure-blob-storage": "^0.1.6", @@ -55,35 +84,7 @@ "tedivm/jshrink": "~1.3.0", "tubalmartin/cssmin": "4.1.1", "webonyx/graphql-php": "^0.13.8", - "wikimedia/less.php": "~1.8.0", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-config": "^2.6.0", - "zendframework/zend-console": "^2.6.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-di": "^2.6.1", - "zendframework/zend-eventmanager": "^3.0.0", - "zendframework/zend-feed": "^2.9.0", - "zendframework/zend-form": "^2.10.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-i18n": "^2.7.3", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.1", - "zendframework/zend-mail": "^2.9.0", - "zendframework/zend-mime": "^2.5.0", - "zendframework/zend-modulemanager": "^2.7", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-serializer": "^2.7.2", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.8", - "zendframework/zend-session": "^2.7.3", - "zendframework/zend-soap": "^2.7.0", - "zendframework/zend-stdlib": "^3.2.1", - "zendframework/zend-text": "^2.6.0", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-view": "~2.11.2" + "wikimedia/less.php": "~1.8.0" }, "require-dev": { "allure-framework/allure-phpunit": "~1.2.0", @@ -322,7 +323,7 @@ "Magento\\Framework\\": "lib/internal/Magento/Framework/", "Magento\\Setup\\": "setup/src/Magento/Setup/", "Magento\\": "app/code/Magento/", - "Zend\\Mvc\\Controller\\": "setup/src/Zend/Mvc/Controller/" + "Laminas\\Mvc\\Controller\\": "setup/src/Zend/Mvc/Controller/" }, "psr-0": { "": [ diff --git a/composer.lock b/composer.lock index 1236d2b0ae82f..39e6fe4845513 100644 --- a/composer.lock +++ b/composer.lock @@ -1,23 +1,23 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "522d676db5baf5864a824409c54948fc", + "content-hash": "92b62c12f24800168ad73c8c41bb31e1", "packages": [ { "name": "aws/aws-sdk-php", - "version": "3.133.24", + "version": "3.133.32", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "726426e1514be5220d55ecf02eb1f938a3b4a105" + "reference": "c4bd227436446f02d2b9963f3ea4ae6dc380420b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/726426e1514be5220d55ecf02eb1f938a3b4a105", - "reference": "726426e1514be5220d55ecf02eb1f938a3b4a105", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c4bd227436446f02d2b9963f3ea4ae6dc380420b", + "reference": "c4bd227436446f02d2b9963f3ea4ae6dc380420b", "shasum": "" }, "require": { @@ -88,7 +88,7 @@ "s3", "sdk" ], - "time": "2020-02-27T19:13:45+00:00" + "time": "2020-03-09T18:10:46+00:00" }, { "name": "braintree/braintree_php", @@ -542,16 +542,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "cbe23383749496fe0f373345208b79568e4bc248" + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/cbe23383749496fe0f373345208b79568e4bc248", - "reference": "cbe23383749496fe0f373345208b79568e4bc248", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1ab9842d69e64fb3a01be6b656501032d1b78cb7", + "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7", "shasum": "" }, "require": { @@ -582,7 +582,7 @@ "Xdebug", "performance" ], - "time": "2019-11-06T16:40:04+00:00" + "time": "2020-03-01T12:26:26+00:00" }, { "name": "container-interop/container-interop", @@ -1035,4414 +1035,4626 @@ "time": "2019-09-25T14:49:45+00:00" }, { - "name": "league/flysystem", - "version": "1.0.64", + "name": "laminas/laminas-captcha", + "version": "2.9.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "d13c43dbd4b791f815215959105a008515d1a2e0" + "url": "https://github.com/laminas/laminas-captcha.git", + "reference": "b88f650f3adf2d902ef56f6377cceb5cd87b9876" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/d13c43dbd4b791f815215959105a008515d1a2e0", - "reference": "d13c43dbd4b791f815215959105a008515d1a2e0", + "url": "https://api.github.com/repos/laminas/laminas-captcha/zipball/b88f650f3adf2d902ef56f6377cceb5cd87b9876", + "reference": "b88f650f3adf2d902ef56f6377cceb5cd87b9876", "shasum": "" }, "require": { - "ext-fileinfo": "*", - "php": ">=5.5.9" + "laminas/laminas-math": "^2.7 || ^3.0", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" + "replace": { + "zendframework/zend-captcha": "self.version" }, "require-dev": { - "phpspec/phpspec": "^3.4", - "phpunit/phpunit": "^5.7.26" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-recaptcha": "^3.0", + "laminas/laminas-session": "^2.8", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.10.1", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" }, "suggest": { - "ext-fileinfo": "Required for MimeType", - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + "laminas/laminas-i18n-resources": "Translations of captcha messages", + "laminas/laminas-recaptcha": "Laminas\\ReCaptcha component", + "laminas/laminas-session": "Laminas\\Session component", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-validator": "Laminas\\Validator component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" } }, "autoload": { "psr-4": { - "League\\Flysystem\\": "src/" + "Laminas\\Captcha\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } + "BSD-3-Clause" ], - "description": "Filesystem abstraction: Many filesystems, one API.", + "description": "Generate and validate CAPTCHAs using Figlets, images, ReCaptcha, and more", + "homepage": "https://laminas.dev", "keywords": [ - "Cloud Files", - "WebDAV", - "abstraction", - "aws", - "cloud", - "copy.com", - "dropbox", - "file systems", - "files", - "filesystem", - "filesystems", - "ftp", - "rackspace", - "remote", - "s3", - "sftp", - "storage" + "captcha", + "laminas" ], - "time": "2020-02-05T18:14:17+00:00" + "time": "2019-12-31T16:24:14+00:00" }, { - "name": "league/flysystem-aws-s3-v3", - "version": "1.0.24", + "name": "laminas/laminas-code", + "version": "3.3.2", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570" + "url": "https://github.com/laminas/laminas-code.git", + "reference": "128784abc7a0d9e1fcc30c446533aa6f1db1f999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4382036bde5dc926f9b8b337e5bdb15e5ec7b570", - "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570", + "url": "https://api.github.com/repos/laminas/laminas-code/zipball/128784abc7a0d9e1fcc30c446533aa6f1db1f999", + "reference": "128784abc7a0d9e1fcc30c446533aa6f1db1f999", "shasum": "" }, "require": { - "aws/aws-sdk-php": "^3.0.0", - "league/flysystem": "^1.0.40", - "php": ">=5.5.0" + "laminas/laminas-eventmanager": "^2.6 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.1" + }, + "replace": { + "zendframework/zend-code": "self.version" }, "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "^2.0.0" + "doctrine/annotations": "^1.0", + "ext-phar": "*", + "laminas/laminas-coding-standard": "^1.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "phpunit/phpunit": "^7.5.15" + }, + "suggest": { + "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", + "laminas/laminas-stdlib": "Laminas\\Stdlib component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "3.3.x-dev", + "dev-develop": "3.4.x-dev" } }, "autoload": { "psr-4": { - "League\\Flysystem\\AwsS3v3\\": "src/" + "Laminas\\Code\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } + "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", + "homepage": "https://laminas.dev", + "keywords": [ + "code", + "laminas" ], - "description": "Flysystem adapter for the AWS S3 SDK v3.x", - "time": "2020-02-23T13:31:58+00:00" + "time": "2019-12-31T16:28:14+00:00" }, { - "name": "league/flysystem-azure-blob-storage", - "version": "0.1.6", + "name": "laminas/laminas-config", + "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/thephpleague/flysystem-azure-blob-storage.git", - "reference": "97215345f3c42679299ba556a4d16d4847ee7f6d" + "url": "https://github.com/laminas/laminas-config.git", + "reference": "71ba6d5dd703196ce66b25abc4d772edb094dae1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-azure-blob-storage/zipball/97215345f3c42679299ba556a4d16d4847ee7f6d", - "reference": "97215345f3c42679299ba556a4d16d4847ee7f6d", + "url": "https://api.github.com/repos/laminas/laminas-config/zipball/71ba6d5dd703196ce66b25abc4d772edb094dae1", + "reference": "71ba6d5dd703196ce66b25abc4d772edb094dae1", "shasum": "" }, "require": { - "guzzlehttp/psr7": "^1.5", - "league/flysystem": "^1.0", - "microsoft/azure-storage-blob": "^1.1", - "php": ">=5.6" + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-config": "self.version" }, "require-dev": { - "phpunit/phpunit": "^5.7" + "fabpot/php-cs-fixer": "1.7.*", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.5", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-json": "Laminas\\Json to use the Json reader or writer classes", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } + }, "autoload": { "psr-4": { - "League\\Flysystem\\AzureBlobStorage\\": "src/" + "Laminas\\Config\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } + "description": "provides a nested object property based user interface for accessing this configuration data within application code", + "homepage": "https://laminas.dev", + "keywords": [ + "config", + "laminas" ], - "time": "2019-06-07T20:42:16+00:00" + "time": "2019-12-31T16:30:04+00:00" }, { - "name": "magento/composer", - "version": "1.6.x-dev", + "name": "laminas/laminas-console", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/magento/composer.git", - "reference": "fe738ac9155f550b669b260b3cfa6422eacb53fa" + "url": "https://github.com/laminas/laminas-console.git", + "reference": "478a6ceac3e31fb38d6314088abda8b239ee23a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/composer/zipball/fe738ac9155f550b669b260b3cfa6422eacb53fa", - "reference": "fe738ac9155f550b669b260b3cfa6422eacb53fa", + "url": "https://api.github.com/repos/laminas/laminas-console/zipball/478a6ceac3e31fb38d6314088abda8b239ee23a5", + "reference": "478a6ceac3e31fb38d6314088abda8b239ee23a5", "shasum": "" }, "require": { - "composer/composer": "^1.6", - "php": "~7.1.3||~7.2.0||~7.3.0", - "symfony/console": "~4.4.0" + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-console": "self.version" }, "require-dev": { - "phpunit/phpunit": "~7.0.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-filter": "^2.7.2", + "laminas/laminas-json": "^2.6 || ^3.0", + "laminas/laminas-validator": "^2.10.1", + "phpunit/phpunit": "^5.7.23 || ^6.4.3" + }, + "suggest": { + "laminas/laminas-filter": "To support DefaultRouteMatcher usage", + "laminas/laminas-validator": "To support DefaultRouteMatcher usage" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8.x-dev", + "dev-develop": "2.9.x-dev" + } + }, "autoload": { "psr-4": { - "Magento\\Composer\\": "src" + "Laminas\\Console\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "OSL-3.0", - "AFL-3.0" + "BSD-3-Clause" ], - "description": "Magento composer library helps to instantiate Composer application and run composer commands.", - "time": "2020-01-17T16:43:51+00:00" + "description": "Build console applications using getopt syntax or routing, complete with prompts", + "homepage": "https://laminas.dev", + "keywords": [ + "console", + "laminas" + ], + "time": "2019-12-31T16:31:45+00:00" }, { - "name": "magento/magento-composer-installer", - "version": "0.1.13", + "name": "laminas/laminas-crypt", + "version": "2.6.0", "source": { "type": "git", - "url": "https://github.com/magento/magento-composer-installer.git", - "reference": "8b6c32f53b4944a5d6656e86344cd0f9784709a1" + "url": "https://github.com/laminas/laminas-crypt.git", + "reference": "6f291fe90c84c74d737c9dc9b8f0ad2b55dc0567" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/8b6c32f53b4944a5d6656e86344cd0f9784709a1", - "reference": "8b6c32f53b4944a5d6656e86344cd0f9784709a1", + "url": "https://api.github.com/repos/laminas/laminas-crypt/zipball/6f291fe90c84c74d737c9dc9b8f0ad2b55dc0567", + "reference": "6f291fe90c84c74d737c9dc9b8f0ad2b55dc0567", "shasum": "" }, "require": { - "composer-plugin-api": "^1.0" + "container-interop/container-interop": "~1.0", + "laminas/laminas-math": "^2.6", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" }, "replace": { - "magento-hackathon/magento-composer-installer": "*" + "zendframework/zend-crypt": "self.version" }, "require-dev": { - "composer/composer": "*@dev", - "firegento/phpcs": "dev-patch-1", - "mikey179/vfsstream": "*", - "phpunit/phpunit": "*", - "phpunit/phpunit-mock-objects": "dev-master", - "squizlabs/php_codesniffer": "1.4.7", - "symfony/process": "*" + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" }, - "type": "composer-plugin", + "suggest": { + "ext-mcrypt": "Required for most features of Laminas\\Crypt" + }, + "type": "library", "extra": { - "composer-command-registry": [ - "MagentoHackathon\\Composer\\Magento\\Command\\DeployCommand" - ], - "class": "MagentoHackathon\\Composer\\Magento\\Plugin" + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } }, "autoload": { - "psr-0": { - "MagentoHackathon\\Composer\\Magento": "src/" + "psr-4": { + "Laminas\\Crypt\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "OSL-3.0" - ], - "authors": [ - { - "name": "Vinai Kopp", - "email": "vinai@netzarbeiter.com" - }, - { - "name": "Daniel Fahlke aka Flyingmana", - "email": "flyingmana@googlemail.com" - }, - { - "name": "Jörg Weller", - "email": "weller@flagbit.de" - }, - { - "name": "Karl Spies", - "email": "karl.spies@gmx.net" - }, - { - "name": "Tobias Vogt", - "email": "tobi@webguys.de" - }, - { - "name": "David Fuhr", - "email": "fuhr@flagbit.de" - } + "BSD-3-Clause" ], - "description": "Composer installer for Magento modules", - "homepage": "https://github.com/magento/magento-composer-installer", + "homepage": "https://laminas.dev", "keywords": [ - "composer-installer", - "magento" + "crypt", + "laminas" ], - "time": "2017-12-29T16:45:24+00:00" + "time": "2019-12-31T16:33:11+00:00" }, { - "name": "magento/zendframework1", - "version": "1.14.3", + "name": "laminas/laminas-db", + "version": "2.11.2", "source": { "type": "git", - "url": "https://github.com/magento/zf1.git", - "reference": "726855dfb080089dc7bc7b016624129f8e7bc4e5" + "url": "https://github.com/laminas/laminas-db.git", + "reference": "76f9527da996c2fef32ef1f3a939e18ca5e9d962" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/zf1/zipball/726855dfb080089dc7bc7b016624129f8e7bc4e5", - "reference": "726855dfb080089dc7bc7b016624129f8e7bc4e5", + "url": "https://api.github.com/repos/laminas/laminas-db/zipball/76f9527da996c2fef32ef1f3a939e18ca5e9d962", + "reference": "76f9527da996c2fef32ef1f3a939e18ca5e9d962", "shasum": "" }, "require": { - "php": ">=5.2.11" + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-db": "self.version" }, "require-dev": { - "phpunit/dbunit": "1.3.*", - "phpunit/phpunit": "3.7.*" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.14" + }, + "suggest": { + "laminas/laminas-eventmanager": "Laminas\\EventManager component", + "laminas/laminas-hydrator": "Laminas\\Hydrator component for using HydratingResultSets", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12.x-dev" + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" + }, + "laminas": { + "component": "Laminas\\Db", + "config-provider": "Laminas\\Db\\ConfigProvider" } }, "autoload": { - "psr-0": { - "Zend_": "library/" + "psr-4": { + "Laminas\\Db\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", - "include-path": [ - "library/" - ], "license": [ "BSD-3-Clause" ], - "description": "Magento Zend Framework 1", - "homepage": "http://framework.zend.com/", + "description": "Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations", + "homepage": "https://laminas.dev", "keywords": [ - "ZF1", - "framework" + "db", + "laminas" ], - "time": "2019-11-26T15:09:40+00:00" + "time": "2020-01-14T13:07:26+00:00" }, { - "name": "microsoft/azure-storage-blob", - "version": "1.5.0", + "name": "laminas/laminas-dependency-plugin", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/Azure/azure-storage-blob-php.git", - "reference": "6a333cd28a3742c3e99e79042dc6510f9f917919" + "url": "https://github.com/laminas/laminas-dependency-plugin.git", + "reference": "f269716dc584cd7b69e7f6e8ac1092d645ab56d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Azure/azure-storage-blob-php/zipball/6a333cd28a3742c3e99e79042dc6510f9f917919", - "reference": "6a333cd28a3742c3e99e79042dc6510f9f917919", + "url": "https://api.github.com/repos/laminas/laminas-dependency-plugin/zipball/f269716dc584cd7b69e7f6e8ac1092d645ab56d5", + "reference": "f269716dc584cd7b69e7f6e8ac1092d645ab56d5", "shasum": "" }, "require": { - "microsoft/azure-storage-common": "~1.4", - "php": ">=5.6.0" + "composer-plugin-api": "^1.1", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "composer/composer": "^1.9", + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "phpcompatibility/php-compatibility": "^9.3", + "phpunit/phpunit": "^8.4", + "roave/security-advisories": "dev-master", + "webimpress/coding-standard": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev", + "dev-develop": "1.1.x-dev" + }, + "class": "Laminas\\DependencyPlugin\\DependencyRewriterPlugin" }, - "type": "library", "autoload": { "psr-4": { - "MicrosoftAzure\\Storage\\Blob\\": "src/Blob" + "Laminas\\DependencyPlugin\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Azure Storage PHP Client Library", - "email": "dmsh@microsoft.com" - } - ], - "description": "This project provides a set of PHP client libraries that make it easy to access Microsoft Azure Storage Blob APIs.", - "keywords": [ - "azure", - "blob", - "php", - "sdk", - "storage" + "BSD-3-Clause" ], - "time": "2020-01-02T07:18:59+00:00" + "description": "Replace zendframework and zfcampus packages with their Laminas Project equivalents.", + "time": "2020-01-14T19:36:52+00:00" }, { - "name": "microsoft/azure-storage-common", - "version": "1.4.1", + "name": "laminas/laminas-di", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/Azure/azure-storage-common-php.git", - "reference": "be4df800761d0d0fa91a9460c7f42517197d57a0" + "url": "https://github.com/laminas/laminas-di.git", + "reference": "239b22408a1f8eacda6fc2b838b5065c4cf1d88e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Azure/azure-storage-common-php/zipball/be4df800761d0d0fa91a9460c7f42517197d57a0", - "reference": "be4df800761d0d0fa91a9460c7f42517197d57a0", + "url": "https://api.github.com/repos/laminas/laminas-di/zipball/239b22408a1f8eacda6fc2b838b5065c4cf1d88e", + "reference": "239b22408a1f8eacda6fc2b838b5065c4cf1d88e", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "~6.0", - "php": ">=5.6.0" + "container-interop/container-interop": "^1.1", + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^0.4.5 || ^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-di": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "phpunit/phpunit": "~4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" + } + }, "autoload": { "psr-4": { - "MicrosoftAzure\\Storage\\Common\\": "src/Common" + "Laminas\\Di\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Azure Storage PHP Client Library", - "email": "dmsh@microsoft.com" - } + "BSD-3-Clause" ], - "description": "This project provides a set of common code shared by Azure Storage Blob, Table, Queue and File PHP client libraries.", + "homepage": "https://laminas.dev", "keywords": [ - "azure", - "common", - "php", - "sdk", - "storage" + "di", + "laminas" ], - "time": "2020-01-02T07:15:54+00:00" + "time": "2019-12-31T15:17:33+00:00" }, { - "name": "monolog/monolog", - "version": "1.25.3", + "name": "laminas/laminas-diactoros", + "version": "1.8.7p1", "source": { "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1" + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "56a9aca1f89231763d24d2ae13531b97fa5f4029" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1", - "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/56a9aca1f89231763d24d2ae13531b97fa5f4029", + "reference": "56a9aca1f89231763d24d2ae13531b97fa5f4029", "shasum": "" }, "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "psr/http-message": "^1.0" }, "provide": { - "psr/log-implementation": "1.0.0" + "psr/http-message-implementation": "1.0" }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" + "replace": { + "zendframework/zend-diactoros": "self.version" }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" + "require-dev": { + "ext-dom": "*", + "ext-libxml": "*", + "laminas/laminas-coding-standard": "~1.0", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0.x-dev" + "dev-release-1.8": "1.8.x-dev" } }, "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], "psr-4": { - "Monolog\\": "src/Monolog" + "Laminas\\Diactoros\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } + "BSD-3-Clause" ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", "keywords": [ - "log", - "logging", - "psr-3" + "http", + "laminas", + "psr", + "psr-7" ], - "time": "2019-12-20T14:15:16+00:00" + "time": "2020-01-07T19:25:17+00:00" }, { - "name": "mtdowling/jmespath.php", - "version": "2.5.0", + "name": "laminas/laminas-escaper", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "52168cb9472de06979613d365c7f1ab8798be895" + "url": "https://github.com/laminas/laminas-escaper.git", + "reference": "25f2a053eadfa92ddacb609dcbbc39362610da70" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", - "reference": "52168cb9472de06979613d365c7f1ab8798be895", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/25f2a053eadfa92ddacb609dcbbc39362610da70", + "reference": "25f2a053eadfa92ddacb609dcbbc39362610da70", "shasum": "" }, "require": { - "php": ">=5.4.0", - "symfony/polyfill-mbstring": "^1.4" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-escaper": "self.version" }, "require-dev": { - "composer/xdebug-handler": "^1.2", - "phpunit/phpunit": "^4.8.36|^7.5.15" + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" }, - "bin": [ - "bin/jp.php" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" } }, "autoload": { "psr-4": { - "JmesPath\\": "src/" - }, - "files": [ - "src/JmesPath.php" - ] + "Laminas\\Escaper\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } + "BSD-3-Clause" ], - "description": "Declaratively specify how to extract elements from a JSON document", + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "homepage": "https://laminas.dev", "keywords": [ - "json", - "jsonpath" + "escaper", + "laminas" ], - "time": "2019-12-30T18:03:34+00:00" + "time": "2019-12-31T16:43:30+00:00" }, { - "name": "paragonie/random_compat", - "version": "v9.99.99", + "name": "laminas/laminas-eventmanager", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" + "url": "https://github.com/laminas/laminas-eventmanager.git", + "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", - "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "url": "https://api.github.com/repos/laminas/laminas-eventmanager/zipball/ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", + "reference": "ce4dc0bdf3b14b7f9815775af9dfee80a63b4748", "shasum": "" }, "require": { - "php": "^7" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-eventmanager": "self.version" }, "require-dev": { - "phpunit/phpunit": "4.*|5.*", - "vimeo/psalm": "^1" + "athletic/athletic": "^0.1", + "container-interop/container-interop": "^1.1.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-stdlib": "^2.7.3 || ^3.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" }, "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", + "laminas/laminas-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev", + "dev-develop": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Laminas\\EventManager\\": "src/" + } + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } + "BSD-3-Clause" ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "description": "Trigger and listen to events within a PHP application", + "homepage": "https://laminas.dev", "keywords": [ - "csprng", - "polyfill", - "pseudorandom", - "random" + "event", + "eventmanager", + "events", + "laminas" ], - "time": "2018-07-02T15:55:56+00:00" + "time": "2019-12-31T16:44:52+00:00" }, { - "name": "paragonie/sodium_compat", - "version": "v1.12.2", + "name": "laminas/laminas-feed", + "version": "2.12.0", "source": { "type": "git", - "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "3b953109fdfc821c1979bc829c8b7421721fef82" + "url": "https://github.com/laminas/laminas-feed.git", + "reference": "64d25e18a6ea3db90c27fe2d6b95630daa1bf602" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/3b953109fdfc821c1979bc829c8b7421721fef82", - "reference": "3b953109fdfc821c1979bc829c8b7421721fef82", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/64d25e18a6ea3db90c27fe2d6b95630daa1bf602", + "reference": "64d25e18a6ea3db90c27fe2d6b95630daa1bf602", "shasum": "" }, "require": { - "paragonie/random_compat": ">=1", - "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + "ext-dom": "*", + "ext-libxml": "*", + "laminas/laminas-escaper": "^2.5.2", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "require-dev": { - "phpunit/phpunit": "^3|^4|^5|^6|^7" + "replace": { + "zendframework/zend-feed": "self.version" + }, + "require-dev": { + "laminas/laminas-cache": "^2.7.2", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.8.2", + "laminas/laminas-http": "^2.7", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-validator": "^2.10.1", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-message": "^1.0.1" }, "suggest": { - "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", - "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + "laminas/laminas-cache": "Laminas\\Cache component, for optionally caching feeds between requests", + "laminas/laminas-db": "Laminas\\Db component, for use with PubSubHubbub", + "laminas/laminas-http": "Laminas\\Http for PubSubHubbub, and optionally for use with Laminas\\Feed\\Reader", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for easily extending ExtensionManager implementations", + "laminas/laminas-validator": "Laminas\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent", + "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Laminas\\Feed\\Reader\\Http\\Psr7ResponseDecorator" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" + } + }, "autoload": { - "files": [ - "autoload.php" - ] + "psr-4": { + "Laminas\\Feed\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "ISC" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com" - }, - { - "name": "Frank Denis", - "email": "jedisct1@pureftpd.org" - } + "BSD-3-Clause" ], - "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "description": "provides functionality for consuming RSS and Atom feeds", + "homepage": "https://laminas.dev", "keywords": [ - "Authentication", - "BLAKE2b", - "ChaCha20", - "ChaCha20-Poly1305", - "Chapoly", - "Curve25519", - "Ed25519", - "EdDSA", - "Edwards-curve Digital Signature Algorithm", - "Elliptic Curve Diffie-Hellman", - "Poly1305", - "Pure-PHP cryptography", - "RFC 7748", - "RFC 8032", - "Salpoly", - "Salsa20", - "X25519", - "XChaCha20-Poly1305", - "XSalsa20-Poly1305", - "Xchacha20", - "Xsalsa20", - "aead", - "cryptography", - "ecdh", - "elliptic curve", - "elliptic curve cryptography", - "encryption", - "libsodium", - "php", - "public-key cryptography", - "secret-key cryptography", - "side-channel resistant" + "feed", + "laminas" ], - "time": "2019-12-30T03:11:08+00:00" + "time": "2019-12-31T16:46:54+00:00" }, { - "name": "pelago/emogrifier", - "version": "v2.2.0", + "name": "laminas/laminas-filter", + "version": "2.9.3", "source": { "type": "git", - "url": "https://github.com/MyIntervals/emogrifier.git", - "reference": "2472bc1c3a2dee8915ecc2256139c6100024332f" + "url": "https://github.com/laminas/laminas-filter.git", + "reference": "52b5cdbef8902280996e687e7352a648a8e22f31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/2472bc1c3a2dee8915ecc2256139c6100024332f", - "reference": "2472bc1c3a2dee8915ecc2256139c6100024332f", + "url": "https://api.github.com/repos/laminas/laminas-filter/zipball/52b5cdbef8902280996e687e7352a648a8e22f31", + "reference": "52b5cdbef8902280996e687e7352a648a8e22f31", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0", - "symfony/css-selector": "^3.4.0 || ^4.0.0" + "laminas/laminas-stdlib": "^2.7.7 || ^3.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "conflict": { + "laminas/laminas-validator": "<2.10.1" + }, + "replace": { + "zendframework/zend-filter": "self.version" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.2.0", - "phpmd/phpmd": "^2.6.0", - "phpunit/phpunit": "^4.8.0", - "squizlabs/php_codesniffer": "^3.3.2" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-crypt": "^3.2.1", + "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", + "laminas/laminas-uri": "^2.6", + "pear/archive_tar": "^1.4.3", + "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-factory": "^1.0" + }, + "suggest": { + "laminas/laminas-crypt": "Laminas\\Crypt component, for encryption filters", + "laminas/laminas-i18n": "Laminas\\I18n component for filters depending on i18n functionality", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component, for using the filter chain functionality", + "laminas/laminas-uri": "Laminas\\Uri component, for the UriNormalize filter", + "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" + }, + "laminas": { + "component": "Laminas\\Filter", + "config-provider": "Laminas\\Filter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Pelago\\": "src/" + "Laminas\\Filter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Oliver Klee", - "email": "github@oliverklee.de" - }, - { - "name": "Zoli Szabó", - "email": "zoli.szabo+github@gmail.com" - }, - { - "name": "John Reeve", - "email": "jreeve@pelagodesign.com" - }, - { - "name": "Jake Hotson", - "email": "jake@qzdesign.co.uk" - }, - { - "name": "Cameron Brooks" - }, - { - "name": "Jaime Prado" - } + "BSD-3-Clause" ], - "description": "Converts CSS styles into inline style attributes in your HTML code", - "homepage": "https://www.myintervals.com/emogrifier.php", + "description": "Programmatically filter and normalize data and files", + "homepage": "https://laminas.dev", "keywords": [ - "css", - "email", - "pre-processing" + "filter", + "laminas" ], - "time": "2019-09-04T16:07:59+00:00" + "time": "2020-01-07T20:43:53+00:00" }, { - "name": "php-amqplib/php-amqplib", - "version": "v2.10.1", + "name": "laminas/laminas-form", + "version": "2.14.3", "source": { "type": "git", - "url": "https://github.com/php-amqplib/php-amqplib.git", - "reference": "6e2b2501e021e994fb64429e5a78118f83b5c200" + "url": "https://github.com/laminas/laminas-form.git", + "reference": "012aae01366cb8c8fb64e39a887363ef82f388dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/6e2b2501e021e994fb64429e5a78118f83b5c200", - "reference": "6e2b2501e021e994fb64429e5a78118f83b5c200", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/012aae01366cb8c8fb64e39a887363ef82f388dd", + "reference": "012aae01366cb8c8fb64e39a887363ef82f388dd", "shasum": "" }, "require": { - "ext-bcmath": "*", - "ext-sockets": "*", - "php": ">=5.6" + "laminas/laminas-hydrator": "^1.1 || ^2.1 || ^3.0", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, "replace": { - "videlalvaro/php-amqplib": "self.version" + "zendframework/zend-form": "self.version" }, "require-dev": { - "ext-curl": "*", - "nategood/httpful": "^0.2.20", - "phpunit/phpunit": "^5.7|^6.5|^7.0", - "squizlabs/php_codesniffer": "^2.5" + "doctrine/annotations": "~1.0", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-captcha": "^2.7.1", + "laminas/laminas-code": "^2.6 || ^3.0", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-recaptcha": "^3.0.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.6", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.2", + "phpunit/phpunit": "^5.7.23 || ^6.5.3" + }, + "suggest": { + "laminas/laminas-captcha": "^2.7.1, required for using CAPTCHA form elements", + "laminas/laminas-code": "^2.6 || ^3.0, required to use laminas-form annotations support", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, reuired for laminas-form annotations support", + "laminas/laminas-i18n": "^2.6, required when using laminas-form view helpers", + "laminas/laminas-recaptcha": "in order to use the ReCaptcha form element", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", + "laminas/laminas-view": "^2.6.2, required for using the laminas-form view helpers" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10-dev" + "dev-master": "2.14.x-dev", + "dev-develop": "2.15.x-dev" + }, + "laminas": { + "component": "Laminas\\Form", + "config-provider": "Laminas\\Form\\ConfigProvider" } }, "autoload": { "psr-4": { - "PhpAmqpLib\\": "PhpAmqpLib/" - } + "Laminas\\Form\\": "src/" + }, + "files": [ + "autoload/formElementManagerPolyfill.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-2.1-or-later" - ], - "authors": [ - { - "name": "Alvaro Videla", - "role": "Original Maintainer" - }, - { - "name": "John Kelly", - "email": "johnmkelly86@gmail.com", - "role": "Maintainer" - }, - { - "name": "Raúl Araya", - "email": "nubeiro@gmail.com", - "role": "Maintainer" - }, - { - "name": "Luke Bakken", - "email": "luke@bakken.io", - "role": "Maintainer" - } + "BSD-3-Clause" ], - "description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.", - "homepage": "https://github.com/php-amqplib/php-amqplib/", + "description": "Validate and display simple and complex forms, casting forms to business objects and vice versa", + "homepage": "https://laminas.dev", "keywords": [ - "message", - "queue", - "rabbitmq" + "form", + "laminas" ], - "time": "2019-10-10T13:23:40+00:00" + "time": "2019-12-31T16:56:34+00:00" }, { - "name": "phpseclib/mcrypt_compat", - "version": "1.0.8", + "name": "laminas/laminas-http", + "version": "2.11.2", "source": { "type": "git", - "url": "https://github.com/phpseclib/mcrypt_compat.git", - "reference": "f74c7b1897b62f08f268184b8bb98d9d9ab723b0" + "url": "https://github.com/laminas/laminas-http.git", + "reference": "8c66963b933c80da59433da56a44dfa979f3ec88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/mcrypt_compat/zipball/f74c7b1897b62f08f268184b8bb98d9d9ab723b0", - "reference": "f74c7b1897b62f08f268184b8bb98d9d9ab723b0", + "url": "https://api.github.com/repos/laminas/laminas-http/zipball/8c66963b933c80da59433da56a44dfa979f3ec88", + "reference": "8c66963b933c80da59433da56a44dfa979f3ec88", "shasum": "" }, "require": { - "php": ">=5.3.3", - "phpseclib/phpseclib": ">=2.0.11 <3.0.0" + "laminas/laminas-loader": "^2.5.1", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-uri": "^2.5.2", + "laminas/laminas-validator": "^2.10.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-http": "self.version" }, "require-dev": { - "phpunit/phpunit": "^4.8.35|^5.7|^6.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^3.1 || ^2.6", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3" }, "suggest": { - "ext-openssl": "Will enable faster cryptographic operations" + "paragonie/certainty": "For automated management of cacert.pem" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" + } + }, "autoload": { - "files": [ - "lib/mcrypt.php" - ] + "psr-4": { + "Laminas\\Http\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "homepage": "http://phpseclib.sourceforge.net" - } + "BSD-3-Clause" ], - "description": "PHP 7.1 polyfill for the mcrypt extension from PHP <= 7.0", + "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "homepage": "https://laminas.dev", "keywords": [ - "cryptograpy", - "encryption", - "mcrypt" + "http", + "http client", + "laminas" ], - "time": "2018-08-22T03:11:43+00:00" + "time": "2019-12-31T17:02:36+00:00" }, { - "name": "phpseclib/phpseclib", - "version": "2.0.25", + "name": "laminas/laminas-hydrator", + "version": "2.4.2", "source": { "type": "git", - "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "c18159618ed7cd7ff721ac1a8fec7860a475d2f0" + "url": "https://github.com/laminas/laminas-hydrator.git", + "reference": "4a0e81cf05f32edcace817f1f48cb4055f689d85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c18159618ed7cd7ff721ac1a8fec7860a475d2f0", - "reference": "c18159618ed7cd7ff721ac1a8fec7860a475d2f0", + "url": "https://api.github.com/repos/laminas/laminas-hydrator/zipball/4a0e81cf05f32edcace817f1f48cb4055f689d85", + "reference": "4a0e81cf05f32edcace817f1f48cb4055f689d85", "shasum": "" }, "require": { - "php": ">=5.3.3" + "laminas/laminas-stdlib": "^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-hydrator": "self.version" }, "require-dev": { - "phing/phing": "~2.7", - "phpunit/phpunit": "^4.8.35|^5.7|^6.0", - "sami/sami": "~2.0", - "squizlabs/php_codesniffer": "~2.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-inputfilter": "^2.6", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" }, "suggest": { - "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", - "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", - "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", - "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", + "laminas/laminas-filter": "^2.6, to support naming strategy hydrator usage", + "laminas/laminas-serializer": "^2.6.1, to use the SerializableStrategy", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" }, "type": "library", + "extra": { + "branch-alias": { + "dev-release-2.4": "2.4.x-dev" + }, + "laminas": { + "component": "Laminas\\Hydrator", + "config-provider": "Laminas\\Hydrator\\ConfigProvider" + } + }, "autoload": { - "files": [ - "phpseclib/bootstrap.php" - ], "psr-4": { - "phpseclib\\": "phpseclib/" + "Laminas\\Hydrator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" - }, - { - "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" - }, - { - "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" - }, - { - "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" - }, - { - "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" - } + "BSD-3-Clause" ], - "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", - "homepage": "http://phpseclib.sourceforge.net", + "description": "Serialize objects to arrays, and vice versa", + "homepage": "https://laminas.dev", "keywords": [ - "BigInteger", - "aes", - "asn.1", - "asn1", - "blowfish", - "crypto", - "cryptography", - "encryption", - "rsa", - "security", - "sftp", - "signature", - "signing", - "ssh", - "twofish", - "x.509", - "x509" + "hydrator", + "laminas" ], - "time": "2020-02-25T04:16:50+00:00" + "time": "2019-12-31T17:06:38+00:00" }, { - "name": "psr/container", - "version": "1.0.0", + "name": "laminas/laminas-i18n", + "version": "2.10.1", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + "url": "https://github.com/laminas/laminas-i18n.git", + "reference": "815be447f1c77f70a86bf24d00087fcb975b39ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", - "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/815be447f1c77f70a86bf24d00087fcb975b39ff", + "reference": "815be447f1c77f70a86bf24d00087fcb975b39ff", "shasum": "" }, "require": { - "php": ">=5.3.0" + "ext-intl": "*", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0" + }, + "replace": { + "zendframework/zend-i18n": "self.version" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6", + "laminas/laminas-view": "^2.6.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16" + }, + "suggest": { + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-eventmanager": "You should install this package to use the events in the translator", + "laminas/laminas-filter": "You should install this package to use the provided filters", + "laminas/laminas-i18n-resources": "Translation resources", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "You should install this package to use the provided validators", + "laminas/laminas-view": "You should install this package to use the provided view helpers" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" + }, + "laminas": { + "component": "Laminas\\I18n", + "config-provider": "Laminas\\I18n\\ConfigProvider" } }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "Laminas\\I18n\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } + "BSD-3-Clause" ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Provide translations for your application, and filter and validate internationalized values", + "homepage": "https://laminas.dev", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "i18n", + "laminas" ], - "time": "2017-02-14T16:28:37+00:00" + "time": "2019-12-31T17:07:17+00:00" }, { - "name": "psr/http-message", - "version": "1.0.1", + "name": "laminas/laminas-inputfilter", + "version": "2.10.1", "source": { "type": "git", - "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "url": "git@github.com:laminas/laminas-inputfilter.git", + "reference": "b29ce8f512c966468eee37ea4873ae5fb545d00a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/laminas/laminas-inputfilter/zipball/b29ce8f512c966468eee37ea4873ae5fb545d00a", + "reference": "b29ce8f512c966468eee37ea4873ae5fb545d00a", "shasum": "" }, "require": { - "php": ">=5.3.0" + "laminas/laminas-filter": "^2.9.1", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.11", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-inputfilter": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.15", + "psr/http-message": "^1.0" + }, + "suggest": { + "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" + }, + "laminas": { + "component": "Laminas\\InputFilter", + "config-provider": "Laminas\\InputFilter\\ConfigProvider" } }, "autoload": { "psr-4": { - "Psr\\Http\\Message\\": "src/" + "Laminas\\InputFilter\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } + "BSD-3-Clause" ], - "description": "Common interface for HTTP messages", - "homepage": "https://github.com/php-fig/http-message", + "description": "Normalize and validate input sets from the web, APIs, the CLI, and more, including files", + "homepage": "https://laminas.dev", "keywords": [ - "http", - "http-message", - "psr", - "psr-7", - "request", - "response" + "inputfilter", + "laminas" ], - "time": "2016-08-06T14:39:51+00:00" + "time": "2019-12-31T17:11:54+00:00" }, { - "name": "psr/log", - "version": "1.1.2", + "name": "laminas/laminas-json", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" + "url": "https://github.com/laminas/laminas-json.git", + "reference": "db58425b7f0eba44a7539450cc926af80915951a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", - "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", + "url": "https://api.github.com/repos/laminas/laminas-json/zipball/db58425b7f0eba44a7539450cc926af80915951a", + "reference": "db58425b7f0eba44a7539450cc926af80915951a", "shasum": "" }, "require": { - "php": ">=5.3.0" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-json": "self.version" + }, + "require-dev": { + "fabpot/php-cs-fixer": "1.7.*", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.5 || ^3.0", + "laminas/laminas-xml": "^1.0.2", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "laminas/laminas-http": "Laminas\\Http component, required to use Laminas\\Json\\Server", + "laminas/laminas-server": "Laminas\\Server component, required to use Laminas\\Json\\Server", + "laminas/laminas-stdlib": "Laminas\\Stdlib component, for use with caching Laminas\\Json\\Server responses", + "laminas/laminas-xml": "To support Laminas\\Json\\Json::fromXml() usage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "2.6-dev", + "dev-develop": "2.7-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Laminas\\Json\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } + "BSD-3-Clause" ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", + "homepage": "https://laminas.dev", "keywords": [ - "log", - "psr", - "psr-3" + "json", + "laminas" ], - "time": "2019-11-01T11:05:21+00:00" + "time": "2019-12-31T17:15:00+00:00" }, { - "name": "ralouphie/getallheaders", - "version": "3.0.3", + "name": "laminas/laminas-loader", + "version": "2.6.1", "source": { "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" + "url": "https://github.com/laminas/laminas-loader.git", + "reference": "5d01c2c237ae9e68bec262f339947e2ea18979bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", + "url": "https://api.github.com/repos/laminas/laminas-loader/zipball/5d01c2c237ae9e68bec262f339947e2ea18979bc", + "reference": "5d01c2c237ae9e68bec262f339947e2ea18979bc", "shasum": "" }, "require": { - "php": ">=5.6" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-loader": "self.version" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6.x-dev", + "dev-develop": "2.7.x-dev" + } + }, "autoload": { - "files": [ - "src/getallheaders.php" - ] + "psr-4": { + "Laminas\\Loader\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } + "description": "Autoloading and plugin loading strategies", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "loader" ], - "description": "A polyfill for getallheaders.", - "time": "2019-03-08T08:55:37+00:00" + "time": "2019-12-31T17:18:27+00:00" }, { - "name": "ramsey/uuid", - "version": "3.8.0", + "name": "laminas/laminas-log", + "version": "2.12.0", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" + "url": "https://github.com/laminas/laminas-log.git", + "reference": "4e92d841b48868714a070b10866e94be80fc92ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", - "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "url": "https://api.github.com/repos/laminas/laminas-log/zipball/4e92d841b48868714a070b10866e94be80fc92ff", + "reference": "4e92d841b48868714a070b10866e94be80fc92ff", "shasum": "" }, "require": { - "paragonie/random_compat": "^1.0|^2.0|9.99.99", - "php": "^5.4 || ^7.0", - "symfony/polyfill-ctype": "^1.8" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "psr/log": "^1.1.2" + }, + "provide": { + "psr/log-implementation": "1.0.0" }, "replace": { - "rhumsaa/uuid": "self.version" + "zendframework/zend-log": "self.version" }, "require-dev": { - "codeception/aspect-mock": "^1.0 | ~2.0.0", - "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", - "ircmaxell/random-lib": "^1.1", - "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.9", - "moontoast/math": "^1.1", - "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|^5.0|^6.5", - "squizlabs/php_codesniffer": "^2.3" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-filter": "^2.5", + "laminas/laminas-mail": "^2.6.1", + "laminas/laminas-validator": "^2.10.1", + "mikey179/vfsstream": "^1.6.7", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.15" }, "suggest": { - "ext-ctype": "Provides support for PHP Ctype functions", - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "ext-mongo": "mongo extension to use Mongo writer", + "ext-mongodb": "mongodb extension to use MongoDB writer", + "laminas/laminas-db": "Laminas\\Db component to use the database log writer", + "laminas/laminas-escaper": "Laminas\\Escaper component, for use in the XML log formatter", + "laminas/laminas-mail": "Laminas\\Mail component to use the email log writer", + "laminas/laminas-validator": "Laminas\\Validator component to block invalid log messages" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "2.12.x-dev", + "dev-develop": "2.13.x-dev" + }, + "laminas": { + "component": "Laminas\\Log", + "config-provider": "Laminas\\Log\\ConfigProvider" } }, "autoload": { "psr-4": { - "Ramsey\\Uuid\\": "src/" + "Laminas\\Log\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - }, - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - } - ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", - "homepage": "https://github.com/ramsey/uuid", + "description": "Robust, composite logger with filtering, formatting, and PSR-3 support", + "homepage": "https://laminas.dev", "keywords": [ - "guid", - "identifier", - "uuid" + "laminas", + "log", + "logging" ], - "time": "2018-07-19T23:38:55+00:00" + "time": "2019-12-31T17:18:59+00:00" }, { - "name": "react/promise", - "version": "v2.7.1", + "name": "laminas/laminas-mail", + "version": "2.10.0", "source": { "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d" + "url": "https://github.com/laminas/laminas-mail.git", + "reference": "019fb670c1dff6be7fc91d3b88942bd0a5f68792" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d", - "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "url": "https://api.github.com/repos/laminas/laminas-mail/zipball/019fb670c1dff6be7fc91d3b88942bd0a5f68792", + "reference": "019fb670c1dff6be7fc91d3b88942bd0a5f68792", "shasum": "" }, "require": { - "php": ">=5.4.0" + "ext-iconv": "*", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mime": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-validator": "^2.10.2", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0", + "true/punycode": "^2.1" + }, + "replace": { + "zendframework/zend-mail": "self.version" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-crypt": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1", + "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4" + }, + "suggest": { + "laminas/laminas-crypt": "Crammd5 support in SMTP Auth", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" + }, + "laminas": { + "component": "Laminas\\Mail", + "config-provider": "Laminas\\Mail\\ConfigProvider" + } + }, "autoload": { "psr-4": { - "React\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] + "Laminas\\Mail\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com" - } + "BSD-3-Clause" ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "homepage": "https://laminas.dev", "keywords": [ - "promise", - "promises" + "laminas", + "mail" ], - "time": "2019-01-07T21:25:54+00:00" + "time": "2019-12-31T17:21:22+00:00" }, { - "name": "seld/jsonlint", - "version": "1.7.2", + "name": "laminas/laminas-math", + "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" + "url": "https://github.com/laminas/laminas-math.git", + "reference": "8027b37e00accc43f28605c7d8fd081baed1f475" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", - "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "url": "https://api.github.com/repos/laminas/laminas-math/zipball/8027b37e00accc43f28605c7d8fd081baed1f475", + "reference": "8027b37e00accc43f28605c7d8fd081baed1f475", "shasum": "" }, "require": { - "php": "^5.3 || ^7.0" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-math": "self.version" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "fabpot/php-cs-fixer": "1.7.*", + "ircmaxell/random-lib": "~1.1", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "ext-bcmath": "If using the bcmath functionality", + "ext-gmp": "If using the gmp functionality", + "ircmaxell/random-lib": "Fallback random byte generator for Laminas\\Math\\Rand if Mcrypt extensions is unavailable" }, - "bin": [ - "bin/jsonlint" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev", + "dev-develop": "2.8-dev" + } + }, "autoload": { "psr-4": { - "Seld\\JsonLint\\": "src/Seld/JsonLint/" + "Laminas\\Math\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } + "BSD-3-Clause" ], - "description": "JSON Linter", + "homepage": "https://laminas.dev", "keywords": [ - "json", - "linter", - "parser", - "validator" + "laminas", + "math" ], - "time": "2019-10-24T14:27:39+00:00" + "time": "2019-12-31T17:24:15+00:00" }, { - "name": "seld/phar-utils", - "version": "1.1.0", + "name": "laminas/laminas-mime", + "version": "2.7.3", "source": { "type": "git", - "url": "https://github.com/Seldaek/phar-utils.git", - "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0" + "url": "https://github.com/laminas/laminas-mime.git", + "reference": "e844abb02e868fae154207929190292ad25057cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8800503d56b9867d43d9c303b9cbcc26016e82f0", - "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0", + "url": "https://api.github.com/repos/laminas/laminas-mime/zipball/e844abb02e868fae154207929190292ad25057cc", + "reference": "e844abb02e868fae154207929190292ad25057cc", "shasum": "" }, "require": { - "php": ">=5.3" + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-mime": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-mail": "^2.6", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20" + }, + "suggest": { + "laminas/laminas-mail": "Laminas\\Mail component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" } }, "autoload": { "psr-4": { - "Seld\\PharUtils\\": "src/" + "Laminas\\Mime\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be" - } + "BSD-3-Clause" ], - "description": "PHAR file format utilities, for when PHP phars you up", + "description": "Create and parse MIME messages and parts", + "homepage": "https://laminas.dev", "keywords": [ - "phar" + "laminas", + "mime" ], - "time": "2020-02-14T15:25:33+00:00" + "time": "2020-03-06T08:38:03+00:00" }, { - "name": "symfony/console", - "version": "v4.4.4", + "name": "laminas/laminas-modulemanager", + "version": "2.8.4", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "f512001679f37e6a042b51897ed24a2f05eba656" + "url": "https://github.com/laminas/laminas-modulemanager.git", + "reference": "92b1cde1aab5aef687b863face6dd5d9c6751c78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f512001679f37e6a042b51897ed24a2f05eba656", - "reference": "f512001679f37e6a042b51897ed24a2f05eba656", + "url": "https://api.github.com/repos/laminas/laminas-modulemanager/zipball/92b1cde1aab5aef687b863face6dd5d9c6751c78", + "reference": "92b1cde1aab5aef687b863face6dd5d9c6751c78", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.8", - "symfony/service-contracts": "^1.1|^2" - }, - "conflict": { - "symfony/dependency-injection": "<3.4", - "symfony/event-dispatcher": "<4.3|>=5", - "symfony/lock": "<4.4", - "symfony/process": "<3.3" + "laminas/laminas-config": "^3.1 || ^2.6", + "laminas/laminas-eventmanager": "^3.2 || ^2.6.3", + "laminas/laminas-stdlib": "^3.1 || ^2.7", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "provide": { - "psr/log-implementation": "1.0" + "replace": { + "zendframework/zend-modulemanager": "self.version" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/event-dispatcher": "^4.3", - "symfony/lock": "^4.4|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", - "symfony/var-dumper": "^4.3|^5.0" + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-console": "^2.6", + "laminas/laminas-di": "^2.6", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-mvc": "^3.0 || ^2.7", + "laminas/laminas-servicemanager": "^3.0.3 || ^2.7.5", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16" }, "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "laminas/laminas-console": "Laminas\\Console component", + "laminas/laminas-loader": "Laminas\\Loader component if you are not using Composer autoloading for your modules", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "2.8.x-dev", + "dev-develop": "2.9.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\ModuleManager\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "Modular application system for laminas-mvc applications", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "modulemanager" ], - "description": "Symfony Console Component", - "homepage": "https://symfony.com", - "time": "2020-01-25T12:44:29+00:00" + "time": "2019-12-31T17:26:56+00:00" }, { - "name": "symfony/css-selector", - "version": "v4.4.4", + "name": "laminas/laminas-mvc", + "version": "2.7.15", "source": { "type": "git", - "url": "https://github.com/symfony/css-selector.git", - "reference": "a167b1860995b926d279f9bb538f873e3bfa3465" + "url": "https://github.com/laminas/laminas-mvc.git", + "reference": "7e7198b03556a57fb5fd3ed919d9e1cf71500642" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/a167b1860995b926d279f9bb538f873e3bfa3465", - "reference": "a167b1860995b926d279f9bb538f873e3bfa3465", + "url": "https://api.github.com/repos/laminas/laminas-mvc/zipball/7e7198b03556a57fb5fd3ed919d9e1cf71500642", + "reference": "7e7198b03556a57fb5fd3ed919d9e1cf71500642", "shasum": "" }, "require": { - "php": "^7.1.3" + "container-interop/container-interop": "^1.1", + "laminas/laminas-console": "^2.7", + "laminas/laminas-eventmanager": "^2.6.4 || ^3.0", + "laminas/laminas-form": "^2.11", + "laminas/laminas-hydrator": "^1.1 || ^2.4", + "laminas/laminas-psr7bridge": "^0.2", + "laminas/laminas-servicemanager": "^2.7.10 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7.5 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-mvc": "self.version" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "1.7.*", + "laminas/laminas-authentication": "^2.6", + "laminas/laminas-cache": "^2.8", + "laminas/laminas-di": "^2.6", + "laminas/laminas-filter": "^2.8", + "laminas/laminas-http": "^2.8", + "laminas/laminas-i18n": "^2.8", + "laminas/laminas-inputfilter": "^2.8", + "laminas/laminas-json": "^2.6.1", + "laminas/laminas-log": "^2.9.3", + "laminas/laminas-modulemanager": "^2.8", + "laminas/laminas-serializer": "^2.8", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-text": "^2.7", + "laminas/laminas-uri": "^2.6", + "laminas/laminas-validator": "^2.10", + "laminas/laminas-view": "^2.9", + "phpunit/phpunit": "^4.8.36", + "sebastian/comparator": "^1.2.4", + "sebastian/version": "^1.0.4" + }, + "suggest": { + "laminas/laminas-authentication": "Laminas\\Authentication component for Identity plugin", + "laminas/laminas-config": "Laminas\\Config component", + "laminas/laminas-di": "Laminas\\Di component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component for translatable segments", + "laminas/laminas-inputfilter": "Laminas\\Inputfilter component", + "laminas/laminas-json": "Laminas\\Json component", + "laminas/laminas-log": "Laminas\\Log component", + "laminas/laminas-modulemanager": "Laminas\\ModuleManager component", + "laminas/laminas-serializer": "Laminas\\Serializer component", + "laminas/laminas-servicemanager-di": "^1.0.1, if using laminas-servicemanager v3 and requiring the laminas-di integration", + "laminas/laminas-session": "Laminas\\Session component for FlashMessenger, PRG, and FPRG plugins", + "laminas/laminas-text": "Laminas\\Text component", + "laminas/laminas-uri": "Laminas\\Uri component", + "laminas/laminas-validator": "Laminas\\Validator component", + "laminas/laminas-view": "Laminas\\View component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "2.7-dev", + "dev-develop": "3.0-dev" } }, "autoload": { + "files": [ + "src/autoload.php" + ], "psr-4": { - "Symfony\\Component\\CssSelector\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Mvc\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "mvc" ], - "description": "Symfony CssSelector Component", - "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2019-12-31T17:32:15+00:00" }, { - "name": "symfony/event-dispatcher", - "version": "v4.4.4", + "name": "laminas/laminas-psr7bridge", + "version": "0.2.2", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9e3de195e5bc301704dd6915df55892f6dfc208b" + "url": "https://github.com/laminas/laminas-psr7bridge.git", + "reference": "14780ef1d40effd59d77ab29c6d439b2af42cdfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9e3de195e5bc301704dd6915df55892f6dfc208b", - "reference": "9e3de195e5bc301704dd6915df55892f6dfc208b", + "url": "https://api.github.com/repos/laminas/laminas-psr7bridge/zipball/14780ef1d40effd59d77ab29c6d439b2af42cdfa", + "reference": "14780ef1d40effd59d77ab29c6d439b2af42cdfa", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/event-dispatcher-contracts": "^1.1" - }, - "conflict": { - "symfony/dependency-injection": "<3.4" + "laminas/laminas-diactoros": "^1.1", + "laminas/laminas-http": "^2.5", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": ">=5.5", + "psr/http-message": "^1.0" }, - "provide": { - "psr/event-dispatcher-implementation": "1.0", - "symfony/event-dispatcher-implementation": "1.1" + "replace": { + "zendframework/zend-psr7bridge": "self.version" }, "require-dev": { - "psr/log": "~1.0", - "symfony/config": "^3.4|^4.0|^5.0", - "symfony/dependency-injection": "^3.4|^4.0|^5.0", - "symfony/expression-language": "^3.4|^4.0|^5.0", - "symfony/http-foundation": "^3.4|^4.0|^5.0", - "symfony/service-contracts": "^1.1|^2", - "symfony/stopwatch": "^3.4|^4.0|^5.0" - }, - "suggest": { - "symfony/dependency-injection": "", - "symfony/http-kernel": "" + "phpunit/phpunit": "^4.7", + "squizlabs/php_codesniffer": "^2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "1.0-dev", + "dev-develop": "1.1-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\EventDispatcher\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Psr7Bridge\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "PSR-7 <-> Laminas\\Http bridge", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-7" ], - "description": "Symfony EventDispatcher Component", - "homepage": "https://symfony.com", - "time": "2020-01-10T21:54:01+00:00" + "time": "2019-12-31T17:38:47+00:00" }, { - "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.7", + "name": "laminas/laminas-serializer", + "version": "2.9.1", "source": { "type": "git", - "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" + "url": "https://github.com/laminas/laminas-serializer.git", + "reference": "c1c9361f114271b0736db74e0083a919081af5e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", - "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "url": "https://api.github.com/repos/laminas/laminas-serializer/zipball/c1c9361f114271b0736db74e0083a919081af5e0", + "reference": "c1c9361f114271b0736db74e0083a919081af5e0", "shasum": "" }, "require": { - "php": "^7.1.3" + "laminas/laminas-json": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-serializer": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-math": "^2.6 || ^3.0", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16" }, "suggest": { - "psr/event-dispatcher": "", - "symfony/event-dispatcher-implementation": "" + "laminas/laminas-math": "(^2.6 || ^3.0) To support Python Pickle serialization", + "laminas/laminas-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" + }, + "laminas": { + "component": "Laminas\\Serializer", + "config-provider": "Laminas\\Serializer\\ConfigProvider" } }, "autoload": { "psr-4": { - "Symfony\\Contracts\\EventDispatcher\\": "" + "Laminas\\Serializer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "BSD-3-Clause" ], - "description": "Generic abstractions related to dispatching event", - "homepage": "https://symfony.com", + "description": "Serialize and deserialize PHP structures to a variety of representations", + "homepage": "https://laminas.dev", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "laminas", + "serializer" ], - "time": "2019-09-17T09:54:03+00:00" + "time": "2019-12-31T17:42:11+00:00" }, { - "name": "symfony/filesystem", - "version": "v4.4.4", + "name": "laminas/laminas-server", + "version": "2.8.1", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd" + "url": "https://github.com/laminas/laminas-server.git", + "reference": "4aaca9174c40a2fab2e2aa77999da99f71bdd88e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd", - "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd", + "url": "https://api.github.com/repos/laminas/laminas-server/zipball/4aaca9174c40a2fab2e2aa77999da99f71bdd88e", + "reference": "4aaca9174c40a2fab2e2aa77999da99f71bdd88e", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-ctype": "~1.8" + "laminas/laminas-code": "^2.5 || ^3.0", + "laminas/laminas-stdlib": "^2.5 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-server": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "2.8.x-dev", + "dev-develop": "2.9.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Server\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "Create Reflection-based RPC servers", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "server" ], - "description": "Symfony Filesystem Component", - "homepage": "https://symfony.com", - "time": "2020-01-21T08:20:44+00:00" + "time": "2019-12-31T17:43:03+00:00" }, { - "name": "symfony/finder", - "version": "v4.4.4", + "name": "laminas/laminas-servicemanager", + "version": "2.7.11", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "3a50be43515590faf812fbd7708200aabc327ec3" + "url": "https://github.com/laminas/laminas-servicemanager.git", + "reference": "841abb656c6018afebeec1f355be438426d6a3dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/3a50be43515590faf812fbd7708200aabc327ec3", - "reference": "3a50be43515590faf812fbd7708200aabc327ec3", + "url": "https://api.github.com/repos/laminas/laminas-servicemanager/zipball/841abb656c6018afebeec1f355be438426d6a3dd", + "reference": "841abb656c6018afebeec1f355be438426d6a3dd", "shasum": "" }, "require": { - "php": "^7.1.3" + "container-interop/container-interop": "~1.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.5 || ^7.0" + }, + "replace": { + "zendframework/zend-servicemanager": "self.version" + }, + "require-dev": { + "athletic/athletic": "dev-master", + "fabpot/php-cs-fixer": "1.7.*", + "laminas/laminas-di": "~2.5", + "laminas/laminas-mvc": "~2.5", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "laminas/laminas-di": "Laminas\\Di component", + "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "2.7-dev", + "dev-develop": "3.0-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\ServiceManager\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "servicemanager" ], - "description": "Symfony Finder Component", - "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2019-12-31T17:44:16+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.14.0", + "name": "laminas/laminas-session", + "version": "2.9.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" + "url": "https://github.com/laminas/laminas-session.git", + "reference": "fdba34c1b257235dba2fff6ed4df1844390f85f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", - "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", + "url": "https://api.github.com/repos/laminas/laminas-session/zipball/fdba34c1b257235dba2fff6ed4df1844390f85f6", + "reference": "fdba34c1b257235dba2fff6ed4df1844390f85f6", "shasum": "" }, "require": { - "php": ">=5.3.3" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-session": "self.version" + }, + "require-dev": { + "container-interop/container-interop": "^1.1", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-db": "^2.7", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-validator": "^2.6", + "mongodb/mongodb": "^1.0.1", + "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20" }, "suggest": { - "ext-ctype": "For best performance" + "laminas/laminas-cache": "Laminas\\Cache component", + "laminas/laminas-db": "Laminas\\Db component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-validator": "Laminas\\Validator component", + "mongodb/mongodb": "If you want to use the MongoDB session save handler" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" + }, + "laminas": { + "component": "Laminas\\Session", + "config-provider": "Laminas\\Session\\ConfigProvider" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Laminas\\Session\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "BSD-3-Clause" ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", + "description": "Object-oriented interface to PHP sessions and storage", + "homepage": "https://laminas.dev", "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "laminas", + "session" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2020-03-06T09:44:45+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.14.0", + "name": "laminas/laminas-soap", + "version": "2.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2" + "url": "https://github.com/laminas/laminas-soap.git", + "reference": "34f91d5c4c0a78bc5689cca2d1eaf829b27edd72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2", - "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2", + "url": "https://api.github.com/repos/laminas/laminas-soap/zipball/34f91d5c4c0a78bc5689cca2d1eaf829b27edd72", + "reference": "34f91d5c4c0a78bc5689cca2d1eaf829b27edd72", "shasum": "" }, "require": { - "php": ">=5.3.3" + "ext-soap": "*", + "laminas/laminas-server": "^2.6.1", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-uri": "^2.5.2", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-soap": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-http": "^2.5.4", + "phpunit/phpunit": "^5.7.21 || ^6.3" }, "suggest": { - "ext-mbstring": "For best performance" + "laminas/laminas-http": "Laminas\\Http component" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] + "Laminas\\Soap\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "BSD-3-Clause" ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", + "homepage": "https://laminas.dev", "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" + "laminas", + "soap" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2019-12-31T17:48:49+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.14.0", + "name": "laminas/laminas-stdlib", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675" + "url": "https://github.com/laminas/laminas-stdlib.git", + "reference": "2b18347625a2f06a1a485acfbc870f699dbe51c6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/5e66a0fa1070bf46bec4bea7962d285108edd675", - "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675", + "url": "https://api.github.com/repos/laminas/laminas-stdlib/zipball/2b18347625a2f06a1a485acfbc870f699dbe51c6", + "reference": "2b18347625a2f06a1a485acfbc870f699dbe51c6", "shasum": "" }, "require": { - "php": ">=5.3.3" + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-stdlib": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpbench/phpbench": "^0.13", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "3.2.x-dev", + "dev-develop": "3.3.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] + "Laminas\\Stdlib\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "BSD-3-Clause" ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", + "description": "SPL extensions, array utilities, error handlers, and more", + "homepage": "https://laminas.dev", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "laminas", + "stdlib" ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2019-12-31T17:51:15+00:00" }, { - "name": "symfony/process", - "version": "v4.4.4", + "name": "laminas/laminas-text", + "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "f5697ab4cb14a5deed7473819e63141bf5352c36" + "url": "https://github.com/laminas/laminas-text.git", + "reference": "3601b5eacb06ed0a12f658df860cc0f9613cf4db" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/f5697ab4cb14a5deed7473819e63141bf5352c36", - "reference": "f5697ab4cb14a5deed7473819e63141bf5352c36", + "url": "https://api.github.com/repos/laminas/laminas-text/zipball/3601b5eacb06ed0a12f658df860cc0f9613cf4db", + "reference": "3601b5eacb06ed0a12f658df860cc0f9613cf4db", "shasum": "" }, "require": { - "php": "^7.1.3" + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" + }, + "replace": { + "zendframework/zend-text": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.4-dev" + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Laminas\\Text\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "description": "Create FIGlets and text-based tables", + "homepage": "https://laminas.dev", + "keywords": [ + "laminas", + "text" ], - "description": "Symfony Process Component", - "homepage": "https://symfony.com", - "time": "2020-01-09T09:50:08+00:00" + "time": "2019-12-31T17:54:52+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.0.1", + "name": "laminas/laminas-uri", + "version": "2.7.1", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "144c5e51266b281231e947b51223ba14acf1a749" + "url": "https://github.com/laminas/laminas-uri.git", + "reference": "6be8ce19622f359b048ce4faebf1aa1bca73a7ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", - "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "url": "https://api.github.com/repos/laminas/laminas-uri/zipball/6be8ce19622f359b048ce4faebf1aa1bca73a7ff", + "reference": "6be8ce19622f359b048ce4faebf1aa1bca73a7ff", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/container": "^1.0" + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-validator": "^2.10", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "suggest": { - "symfony/service-implementation": "" + "replace": { + "zendframework/zend-uri": "self.version" + }, + "require-dev": { + "laminas/laminas-coding-standard": "~1.0.0", + "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.7.x-dev", + "dev-develop": "2.8.x-dev" } }, "autoload": { "psr-4": { - "Symfony\\Contracts\\Service\\": "" + "Laminas\\Uri\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } + "BSD-3-Clause" ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", + "description": "A component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "homepage": "https://laminas.dev", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "laminas", + "uri" ], - "time": "2019-11-18T17:27:11+00:00" + "time": "2019-12-31T17:56:00+00:00" }, { - "name": "tedivm/jshrink", - "version": "v1.3.3", + "name": "laminas/laminas-validator", + "version": "2.13.1", "source": { "type": "git", - "url": "https://github.com/tedious/JShrink.git", - "reference": "566e0c731ba4e372be2de429ef7d54f4faf4477a" + "url": "https://github.com/laminas/laminas-validator.git", + "reference": "36702f033486bf1953e254f5299aad205302e79d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tedious/JShrink/zipball/566e0c731ba4e372be2de429ef7d54f4faf4477a", - "reference": "566e0c731ba4e372be2de429ef7d54f4faf4477a", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/36702f033486bf1953e254f5299aad205302e79d", + "reference": "36702f033486bf1953e254f5299aad205302e79d", "shasum": "" }, "require": { - "php": "^5.6|^7.0" + "container-interop/container-interop": "^1.1", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^7.1" }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^2.8", - "php-coveralls/php-coveralls": "^1.1.0", - "phpunit/phpunit": "^6" + "replace": { + "zendframework/zend-validator": "self.version" + }, + "require-dev": { + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-db": "^2.7", + "laminas/laminas-filter": "^2.6", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-math": "^2.6", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8", + "laminas/laminas-uri": "^2.5", + "phpunit/phpunit": "^7.5.20 || ^8.5.2", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "suggest": { + "laminas/laminas-db": "Laminas\\Db component, required by the (No)RecordExists validator", + "laminas/laminas-filter": "Laminas\\Filter component, required by the Digits validator", + "laminas/laminas-i18n": "Laminas\\I18n component to allow translation of validation error messages", + "laminas/laminas-i18n-resources": "Translations of validator messages", + "laminas/laminas-math": "Laminas\\Math component, required by the Csrf validator", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", + "laminas/laminas-session": "Laminas\\Session component, ^2.8; required by the Csrf validator", + "laminas/laminas-uri": "Laminas\\Uri component, required by the Uri and Sitemap\\Loc validators", + "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.13.x-dev", + "dev-develop": "2.14.x-dev" + }, + "laminas": { + "component": "Laminas\\Validator", + "config-provider": "Laminas\\Validator\\ConfigProvider" + } + }, "autoload": { - "psr-0": { - "JShrink": "src/" + "psr-4": { + "Laminas\\Validator\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Robert Hafner", - "email": "tedivm@tedivm.com" - } - ], - "description": "Javascript Minifier built in PHP", - "homepage": "http://github.com/tedious/JShrink", + "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "homepage": "https://laminas.dev", "keywords": [ - "javascript", - "minifier" + "laminas", + "validator" ], - "time": "2019-06-28T18:11:46+00:00" + "time": "2020-01-15T09:59:30+00:00" }, { - "name": "true/punycode", - "version": "v2.1.1", + "name": "laminas/laminas-view", + "version": "2.11.4", "source": { "type": "git", - "url": "https://github.com/true/php-punycode.git", - "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" + "url": "https://github.com/laminas/laminas-view.git", + "reference": "3bbb2e94287383604c898284a18d2d06cf17301e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", - "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "url": "https://api.github.com/repos/laminas/laminas-view/zipball/3bbb2e94287383604c898284a18d2d06cf17301e", + "reference": "3bbb2e94287383604c898284a18d2d06cf17301e", "shasum": "" }, "require": { - "php": ">=5.3.0", - "symfony/polyfill-mbstring": "^1.3" + "laminas/laminas-eventmanager": "^2.6.2 || ^3.0", + "laminas/laminas-json": "^2.6.1 || ^3.0", + "laminas/laminas-loader": "^2.5", + "laminas/laminas-stdlib": "^2.7 || ^3.0", + "laminas/laminas-zendframework-bridge": "^1.0", + "php": "^5.6 || ^7.0" }, - "require-dev": { - "phpunit/phpunit": "~4.7", - "squizlabs/php_codesniffer": "~2.0" + "replace": { + "zendframework/zend-view": "self.version" + }, + "require-dev": { + "laminas/laminas-authentication": "^2.5", + "laminas/laminas-cache": "^2.6.1", + "laminas/laminas-coding-standard": "~1.0.0", + "laminas/laminas-config": "^2.6", + "laminas/laminas-console": "^2.6", + "laminas/laminas-escaper": "^2.5", + "laminas/laminas-feed": "^2.7", + "laminas/laminas-filter": "^2.6.1", + "laminas/laminas-http": "^2.5.4", + "laminas/laminas-i18n": "^2.6", + "laminas/laminas-log": "^2.7", + "laminas/laminas-modulemanager": "^2.7.1", + "laminas/laminas-mvc": "^2.7.14 || ^3.0", + "laminas/laminas-navigation": "^2.5", + "laminas/laminas-paginator": "^2.5", + "laminas/laminas-permissions-acl": "^2.6", + "laminas/laminas-router": "^3.0.1", + "laminas/laminas-serializer": "^2.6.1", + "laminas/laminas-servicemanager": "^2.7.5 || ^3.0.3", + "laminas/laminas-session": "^2.8.1", + "laminas/laminas-uri": "^2.5", + "phpunit/phpunit": "^5.7.15 || ^6.0.8" + }, + "suggest": { + "laminas/laminas-authentication": "Laminas\\Authentication component", + "laminas/laminas-escaper": "Laminas\\Escaper component", + "laminas/laminas-feed": "Laminas\\Feed component", + "laminas/laminas-filter": "Laminas\\Filter component", + "laminas/laminas-http": "Laminas\\Http component", + "laminas/laminas-i18n": "Laminas\\I18n component", + "laminas/laminas-mvc": "Laminas\\Mvc component", + "laminas/laminas-mvc-plugin-flashmessenger": "laminas-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with laminas-mvc versions 3 and up", + "laminas/laminas-navigation": "Laminas\\Navigation component", + "laminas/laminas-paginator": "Laminas\\Paginator component", + "laminas/laminas-permissions-acl": "Laminas\\Permissions\\Acl component", + "laminas/laminas-servicemanager": "Laminas\\ServiceManager component", + "laminas/laminas-uri": "Laminas\\Uri component" }, + "bin": [ + "bin/templatemap_generator.php" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.11.x-dev", + "dev-develop": "2.12.x-dev" + } + }, "autoload": { "psr-4": { - "TrueBV\\": "src/" + "Laminas\\View\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Renan Gonçalves", - "email": "renan.saddam@gmail.com" - } + "BSD-3-Clause" ], - "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", - "homepage": "https://github.com/true/php-punycode", + "description": "Flexible view layer supporting and providing multiple view layers, helpers, and more", + "homepage": "https://laminas.dev", "keywords": [ - "idna", - "punycode" + "laminas", + "view" ], - "time": "2016-11-16T10:37:54+00:00" + "time": "2019-12-31T18:03:30+00:00" }, { - "name": "tubalmartin/cssmin", - "version": "v4.1.1", + "name": "laminas/laminas-zendframework-bridge", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port.git", - "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf" + "url": "https://github.com/laminas/laminas-zendframework-bridge.git", + "reference": "0fb9675b84a1666ab45182b6c5b29956921e818d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/3cbf557f4079d83a06f9c3ff9b957c022d7805cf", - "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf", + "url": "https://api.github.com/repos/laminas/laminas-zendframework-bridge/zipball/0fb9675b84a1666ab45182b6c5b29956921e818d", + "reference": "0fb9675b84a1666ab45182b6c5b29956921e818d", "shasum": "" }, "require": { - "ext-pcre": "*", - "php": ">=5.3.2" + "php": "^5.6 || ^7.0" }, "require-dev": { - "cogpowered/finediff": "0.3.*", - "phpunit/phpunit": "4.8.*" + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.1", + "squizlabs/php_codesniffer": "^3.5" }, - "bin": [ - "cssmin" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev", + "dev-develop": "1.1.x-dev" + }, + "laminas": { + "module": "Laminas\\ZendFrameworkBridge" + } + }, "autoload": { + "files": [ + "src/autoload.php" + ], "psr-4": { - "tubalmartin\\CssMin\\": "src" + "Laminas\\ZendFrameworkBridge\\": "src//" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "authors": [ - { - "name": "Túbal Martín", - "homepage": "http://tubalmartin.me/" - } - ], - "description": "A PHP port of the YUI CSS compressor", - "homepage": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port", + "description": "Alias legacy ZF class names to Laminas Project equivalents.", "keywords": [ - "compress", - "compressor", - "css", - "cssmin", - "minify", - "yui" + "ZendFramework", + "autoloading", + "laminas", + "zf" ], - "time": "2018-01-15T15:26:51+00:00" + "time": "2020-01-07T22:58:31+00:00" }, { - "name": "webonyx/graphql-php", - "version": "v0.13.8", + "name": "league/flysystem", + "version": "1.0.65", "source": { "type": "git", - "url": "https://github.com/webonyx/graphql-php.git", - "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8" + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "8f17b3ba67097aafb8318cd5c553b1acf7c891c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/6829ae58f4c59121df1f86915fb9917a2ec595e8", - "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8f17b3ba67097aafb8318cd5c553b1acf7c891c8", + "reference": "8f17b3ba67097aafb8318cd5c553b1acf7c891c8", "shasum": "" }, "require": { - "ext-json": "*", - "ext-mbstring": "*", - "php": "^7.1||^8.0" + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" }, "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpbench/phpbench": "^0.14.0", - "phpstan/phpstan": "^0.11.4", - "phpstan/phpstan-phpunit": "^0.11.0", - "phpstan/phpstan-strict-rules": "^0.11.0", - "phpunit/phpcov": "^5.0", - "phpunit/phpunit": "^7.2", - "psr/http-message": "^1.0", - "react/promise": "2.*" + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.26" }, "suggest": { - "psr/http-message": "To use standard GraphQL server", - "react/promise": "To leverage async resolving on React PHP platform" - }, + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, "autoload": { "psr-4": { - "GraphQL\\": "src/" + "League\\Flysystem\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A PHP port of GraphQL reference implementation", - "homepage": "https://github.com/webonyx/graphql-php", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", "keywords": [ - "api", - "graphql" + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" ], - "time": "2019-08-25T10:32:47+00:00" + "time": "2020-03-08T18:53:20+00:00" }, { - "name": "wikimedia/less.php", - "version": "1.8.2", + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.24", "source": { "type": "git", - "url": "https://github.com/wikimedia/less.php.git", - "reference": "e238ad228d74b6ffd38209c799b34e9826909266" + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wikimedia/less.php/zipball/e238ad228d74b6ffd38209c799b34e9826909266", - "reference": "e238ad228d74b6ffd38209c799b34e9826909266", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4382036bde5dc926f9b8b337e5bdb15e5ec7b570", + "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570", "shasum": "" }, "require": { - "php": ">=7.2.9" + "aws/aws-sdk-php": "^3.0.0", + "league/flysystem": "^1.0.40", + "php": ">=5.5.0" }, "require-dev": { - "phpunit/phpunit": "7.5.14" + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" }, - "bin": [ - "bin/lessc" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, "autoload": { - "psr-0": { - "Less": "lib/" - }, - "classmap": [ - "lessc.inc.php" - ] + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Josh Schmidt", - "homepage": "https://github.com/oyejorge" - }, - { - "name": "Matt Agar", - "homepage": "https://github.com/agar" - }, - { - "name": "Martin Jantošovič", - "homepage": "https://github.com/Mordred" + "name": "Frank de Jonge", + "email": "info@frenky.net" } ], - "description": "PHP port of the Javascript version of LESS http://lesscss.org (Originally maintained by Josh Schmidt)", - "keywords": [ - "css", - "less", - "less.js", - "lesscss", - "php", - "stylesheet" - ], - "time": "2019-11-06T18:30:11+00:00" + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "time": "2020-02-23T13:31:58+00:00" }, { - "name": "zendframework/zend-captcha", - "version": "2.9.0", + "name": "league/flysystem-azure-blob-storage", + "version": "0.1.6", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-captcha.git", - "reference": "4272f3d0cde0a1fa9135d0cbc4a629fb655391d3" + "url": "https://github.com/thephpleague/flysystem-azure-blob-storage.git", + "reference": "97215345f3c42679299ba556a4d16d4847ee7f6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-captcha/zipball/4272f3d0cde0a1fa9135d0cbc4a629fb655391d3", - "reference": "4272f3d0cde0a1fa9135d0cbc4a629fb655391d3", + "url": "https://api.github.com/repos/thephpleague/flysystem-azure-blob-storage/zipball/97215345f3c42679299ba556a4d16d4847ee7f6d", + "reference": "97215345f3c42679299ba556a4d16d4847ee7f6d", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-math": "^2.7 || ^3.0", - "zendframework/zend-stdlib": "^3.2.1" + "guzzlehttp/psr7": "^1.5", + "league/flysystem": "^1.0", + "microsoft/azure-storage-blob": "^1.1", + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-session": "^2.8", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.10.1", - "zendframework/zendservice-recaptcha": "^3.0" - }, - "suggest": { - "zendframework/zend-i18n-resources": "Translations of captcha messages", - "zendframework/zend-session": "Zend\\Session component", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zendservice-recaptcha": "ZendService\\ReCaptcha component" + "phpunit/phpunit": "^5.7" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" - } - }, "autoload": { "psr-4": { - "Zend\\Captcha\\": "src/" + "League\\Flysystem\\AzureBlobStorage\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Generate and validate CAPTCHAs using Figlets, images, ReCaptcha, and more", - "keywords": [ - "ZendFramework", - "captcha", - "zf" + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } ], - "abandoned": "laminas/laminas-captcha", - "time": "2019-06-18T09:32:52+00:00" + "time": "2019-06-07T20:42:16+00:00" }, { - "name": "zendframework/zend-code", - "version": "3.3.2", + "name": "magento/composer", + "version": "1.6.x-dev", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-code.git", - "reference": "936fa7ad4d53897ea3e3eb41b5b760828246a20b" + "url": "https://github.com/magento/composer.git", + "reference": "fe738ac9155f550b669b260b3cfa6422eacb53fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-code/zipball/936fa7ad4d53897ea3e3eb41b5b760828246a20b", - "reference": "936fa7ad4d53897ea3e3eb41b5b760828246a20b", + "url": "https://api.github.com/repos/magento/composer/zipball/fe738ac9155f550b669b260b3cfa6422eacb53fa", + "reference": "fe738ac9155f550b669b260b3cfa6422eacb53fa", "shasum": "" }, "require": { - "php": "^7.1", - "zendframework/zend-eventmanager": "^2.6 || ^3.0" + "composer/composer": "^1.6", + "php": "~7.1.3||~7.2.0||~7.3.0", + "symfony/console": "~4.4.0" }, "require-dev": { - "doctrine/annotations": "^1.0", - "ext-phar": "*", - "phpunit/phpunit": "^7.5.15", - "zendframework/zend-coding-standard": "^1.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "zendframework/zend-stdlib": "Zend\\Stdlib component" + "phpunit/phpunit": "~7.0.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.3.x-dev", - "dev-develop": "3.4.x-dev" + "autoload": { + "psr-4": { + "Magento\\Composer\\": "src" } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "description": "Magento composer library helps to instantiate Composer application and run composer commands.", + "time": "2020-01-17T16:43:51+00:00" + }, + { + "name": "magento/magento-composer-installer", + "version": "0.1.13", + "source": { + "type": "git", + "url": "https://github.com/magento/magento-composer-installer.git", + "reference": "8b6c32f53b4944a5d6656e86344cd0f9784709a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/magento/magento-composer-installer/zipball/8b6c32f53b4944a5d6656e86344cd0f9784709a1", + "reference": "8b6c32f53b4944a5d6656e86344cd0f9784709a1", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "replace": { + "magento-hackathon/magento-composer-installer": "*" + }, + "require-dev": { + "composer/composer": "*@dev", + "firegento/phpcs": "dev-patch-1", + "mikey179/vfsstream": "*", + "phpunit/phpunit": "*", + "phpunit/phpunit-mock-objects": "dev-master", + "squizlabs/php_codesniffer": "1.4.7", + "symfony/process": "*" + }, + "type": "composer-plugin", + "extra": { + "composer-command-registry": [ + "MagentoHackathon\\Composer\\Magento\\Command\\DeployCommand" + ], + "class": "MagentoHackathon\\Composer\\Magento\\Plugin" + }, "autoload": { - "psr-4": { - "Zend\\Code\\": "src/" + "psr-0": { + "MagentoHackathon\\Composer\\Magento": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "OSL-3.0" ], - "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", + "authors": [ + { + "name": "Vinai Kopp", + "email": "vinai@netzarbeiter.com" + }, + { + "name": "Daniel Fahlke aka Flyingmana", + "email": "flyingmana@googlemail.com" + }, + { + "name": "Jörg Weller", + "email": "weller@flagbit.de" + }, + { + "name": "Karl Spies", + "email": "karl.spies@gmx.net" + }, + { + "name": "Tobias Vogt", + "email": "tobi@webguys.de" + }, + { + "name": "David Fuhr", + "email": "fuhr@flagbit.de" + } + ], + "description": "Composer installer for Magento modules", + "homepage": "https://github.com/magento/magento-composer-installer", "keywords": [ - "ZendFramework", - "code", - "zf" + "composer-installer", + "magento" ], - "abandoned": "laminas/laminas-code", - "time": "2019-08-31T14:14:34+00:00" + "time": "2017-12-29T16:45:24+00:00" }, { - "name": "zendframework/zend-config", - "version": "2.6.0", + "name": "magento/zendframework1", + "version": "1.14.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-config.git", - "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d" + "url": "https://github.com/magento/zf1.git", + "reference": "726855dfb080089dc7bc7b016624129f8e7bc4e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-config/zipball/2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", - "reference": "2920e877a9f6dca9fa8f6bd3b1ffc2e19bb1e30d", + "url": "https://api.github.com/repos/magento/zf1/zipball/726855dfb080089dc7bc7b016624129f8e7bc4e5", + "reference": "726855dfb080089dc7bc7b016624129f8e7bc4e5", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": ">=5.2.11" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.5", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" - }, - "suggest": { - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-json": "Zend\\Json to use the Json reader or writer classes", - "zendframework/zend-servicemanager": "Zend\\ServiceManager for use with the Config Factory to retrieve reader and writer instances" + "phpunit/dbunit": "1.3.*", + "phpunit/phpunit": "3.7.*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" + "dev-master": "1.12.x-dev" } }, "autoload": { - "psr-4": { - "Zend\\Config\\": "src/" + "psr-0": { + "Zend_": "library/" } }, "notification-url": "https://packagist.org/downloads/", + "include-path": [ + "library/" + ], "license": [ "BSD-3-Clause" ], - "description": "provides a nested object property based user interface for accessing this configuration data within application code", - "homepage": "https://github.com/zendframework/zend-config", + "description": "Magento Zend Framework 1", + "homepage": "http://framework.zend.com/", "keywords": [ - "config", - "zf2" + "ZF1", + "framework" ], - "abandoned": "laminas/laminas-config", - "time": "2016-02-04T23:01:10+00:00" + "time": "2019-11-26T15:09:40+00:00" }, { - "name": "zendframework/zend-console", - "version": "2.8.0", + "name": "microsoft/azure-storage-blob", + "version": "1.5.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-console.git", - "reference": "95817ae78f73c48026972e350a2ecc31c6d9f9ae" + "url": "https://github.com/Azure/azure-storage-blob-php.git", + "reference": "6a333cd28a3742c3e99e79042dc6510f9f917919" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-console/zipball/95817ae78f73c48026972e350a2ecc31c6d9f9ae", - "reference": "95817ae78f73c48026972e350a2ecc31c6d9f9ae", + "url": "https://api.github.com/repos/Azure/azure-storage-blob-php/zipball/6a333cd28a3742c3e99e79042dc6510f9f917919", + "reference": "6a333cd28a3742c3e99e79042dc6510f9f917919", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^3.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-filter": "^2.7.2", - "zendframework/zend-json": "^2.6 || ^3.0", - "zendframework/zend-validator": "^2.10.1" - }, - "suggest": { - "zendframework/zend-filter": "To support DefaultRouteMatcher usage", - "zendframework/zend-validator": "To support DefaultRouteMatcher usage" + "microsoft/azure-storage-common": "~1.4", + "php": ">=5.6.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "autoload": { + "psr-4": { + "MicrosoftAzure\\Storage\\Blob\\": "src/Blob" } }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Azure Storage PHP Client Library", + "email": "dmsh@microsoft.com" + } + ], + "description": "This project provides a set of PHP client libraries that make it easy to access Microsoft Azure Storage Blob APIs.", + "keywords": [ + "azure", + "blob", + "php", + "sdk", + "storage" + ], + "time": "2020-01-02T07:18:59+00:00" + }, + { + "name": "microsoft/azure-storage-common", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/Azure/azure-storage-common-php.git", + "reference": "be4df800761d0d0fa91a9460c7f42517197d57a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Azure/azure-storage-common-php/zipball/be4df800761d0d0fa91a9460c7f42517197d57a0", + "reference": "be4df800761d0d0fa91a9460c7f42517197d57a0", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "~6.0", + "php": ">=5.6.0" + }, + "type": "library", "autoload": { "psr-4": { - "Zend\\Console\\": "src/" + "MicrosoftAzure\\Storage\\Common\\": "src/Common" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Build console applications using getopt syntax or routing, complete with prompts", + "authors": [ + { + "name": "Azure Storage PHP Client Library", + "email": "dmsh@microsoft.com" + } + ], + "description": "This project provides a set of common code shared by Azure Storage Blob, Table, Queue and File PHP client libraries.", "keywords": [ - "ZendFramework", - "console", - "zf" + "azure", + "common", + "php", + "sdk", + "storage" ], - "abandoned": "laminas/laminas-console", - "time": "2019-02-04T19:48:22+00:00" + "time": "2020-01-02T07:15:54+00:00" }, { - "name": "zendframework/zend-crypt", - "version": "2.6.0", + "name": "monolog/monolog", + "version": "1.25.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-crypt.git", - "reference": "1b2f5600bf6262904167116fa67b58ab1457036d" + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-crypt/zipball/1b2f5600bf6262904167116fa67b58ab1457036d", - "reference": "1b2f5600bf6262904167116fa67b58ab1457036d", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1", + "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1", "shasum": "" }, "require": { - "container-interop/container-interop": "~1.0", - "php": "^5.5 || ^7.0", - "zendframework/zend-math": "^2.6", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0" + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { - "ext-mcrypt": "Required for most features of Zend\\Crypt" + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Crypt\\": "src/" + "Monolog\\": "src/Monolog" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } ], - "homepage": "https://github.com/zendframework/zend-crypt", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", "keywords": [ - "crypt", - "zf2" + "log", + "logging", + "psr-3" ], - "abandoned": "laminas/laminas-crypt", - "time": "2016-02-03T23:46:30+00:00" + "time": "2019-12-20T14:15:16+00:00" }, { - "name": "zendframework/zend-db", - "version": "2.11.0", + "name": "mtdowling/jmespath.php", + "version": "2.5.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-db.git", - "reference": "71626f95f6f9ee326e4be3c34228c1c466300a2c" + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "52168cb9472de06979613d365c7f1ab8798be895" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-db/zipball/71626f95f6f9ee326e4be3c34228c1c466300a2c", - "reference": "71626f95f6f9ee326e4be3c34228c1c466300a2c", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", + "reference": "52168cb9472de06979613d365c7f1ab8798be895", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "^1.4" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" - }, - "suggest": { - "zendframework/zend-eventmanager": "Zend\\EventManager component", - "zendframework/zend-hydrator": "Zend\\Hydrator component for using HydratingResultSets", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "composer/xdebug-handler": "^1.2", + "phpunit/phpunit": "^4.8.36|^7.5.15" }, + "bin": [ + "bin/jp.php" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.11.x-dev", - "dev-develop": "2.12.x-dev" - }, - "zf": { - "component": "Zend\\Db", - "config-provider": "Zend\\Db\\ConfigProvider" + "dev-master": "2.5-dev" } }, "autoload": { "psr-4": { - "Zend\\Db\\": "src/" - } + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Database abstraction layer, SQL abstraction, result set abstraction, and RowDataGateway and TableDataGateway implementations", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", "keywords": [ - "ZendFramework", - "db", - "zf" + "json", + "jsonpath" ], - "abandoned": "laminas/laminas-db", - "time": "2019-12-31T19:43:46+00:00" + "time": "2019-12-30T18:03:34+00:00" }, { - "name": "zendframework/zend-di", - "version": "2.6.1", + "name": "paragonie/random_compat", + "version": "v9.99.99", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-di.git", - "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37" + "url": "https://github.com/paragonie/random_compat.git", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-di/zipball/1fd1ba85660b5a2718741b38639dc7c4c3194b37", - "reference": "1fd1ba85660b5a2718741b38639dc7c4c3194b37", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", + "reference": "84b4dfb120c6f9b4ff7b3685f9b8f1aa365a0c95", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.1", - "php": "^5.5 || ^7.0", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": "^7" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" - } + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" }, - "autoload": { - "psr-4": { - "Zend\\Di\\": "src/" - } + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." }, + "type": "library", "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } ], - "homepage": "https://github.com/zendframework/zend-di", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", "keywords": [ - "di", - "zf2" + "csprng", + "polyfill", + "pseudorandom", + "random" ], - "abandoned": "laminas/laminas-di", - "time": "2016-04-25T20:58:11+00:00" + "time": "2018-07-02T15:55:56+00:00" }, { - "name": "zendframework/zend-diactoros", - "version": "1.8.7", + "name": "paragonie/sodium_compat", + "version": "v1.12.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b" + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "3b953109fdfc821c1979bc829c8b7421721fef82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/a85e67b86e9b8520d07e6415fcbcb8391b44a75b", - "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/3b953109fdfc821c1979bc829c8b7421721fef82", + "reference": "3b953109fdfc821c1979bc829c8b7421721fef82", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "psr/http-message": "^1.0" - }, - "provide": { - "psr/http-message-implementation": "1.0" + "paragonie/random_compat": ">=1", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" }, "require-dev": { - "ext-dom": "*", - "ext-libxml": "*", - "php-http/psr7-integration-tests": "dev-master", - "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", - "zendframework/zend-coding-standard": "~1.0" + "phpunit/phpunit": "^3|^4|^5|^6|^7" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-release-1.8": "1.8.x-dev" - } + "suggest": { + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." }, + "type": "library", "autoload": { "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php" - ], - "psr-4": { - "Zend\\Diactoros\\": "src/" - } + "autoload.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "ISC" ], - "description": "PSR HTTP Message implementations", - "homepage": "https://github.com/zendframework/zend-diactoros", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", "keywords": [ - "http", - "psr", - "psr-7" + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" ], - "abandoned": "laminas/laminas-diactoros", - "time": "2019-08-06T17:53:53+00:00" + "time": "2019-12-30T03:11:08+00:00" }, { - "name": "zendframework/zend-escaper", - "version": "2.6.1", + "name": "pelago/emogrifier", + "version": "v2.2.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-escaper.git", - "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f" + "url": "https://github.com/MyIntervals/emogrifier.git", + "reference": "2472bc1c3a2dee8915ecc2256139c6100024332f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-escaper/zipball/3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", - "reference": "3801caa21b0ca6aca57fa1c42b08d35c395ebd5f", + "url": "https://api.github.com/repos/MyIntervals/emogrifier/zipball/2472bc1c3a2dee8915ecc2256139c6100024332f", + "reference": "2472bc1c3a2dee8915ecc2256139c6100024332f", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "ext-dom": "*", + "ext-libxml": "*", + "php": "^5.5.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0", + "symfony/css-selector": "^3.4.0 || ^4.0.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0" + "friendsofphp/php-cs-fixer": "^2.2.0", + "phpmd/phpmd": "^2.6.0", + "phpunit/phpunit": "^4.8.0", + "squizlabs/php_codesniffer": "^3.3.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6.x-dev", - "dev-develop": "2.7.x-dev" + "dev-master": "3.0.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Escaper\\": "src/" + "Pelago\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "authors": [ + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Zoli Szabó", + "email": "zoli.szabo+github@gmail.com" + }, + { + "name": "John Reeve", + "email": "jreeve@pelagodesign.com" + }, + { + "name": "Jake Hotson", + "email": "jake@qzdesign.co.uk" + }, + { + "name": "Cameron Brooks" + }, + { + "name": "Jaime Prado" + } + ], + "description": "Converts CSS styles into inline style attributes in your HTML code", + "homepage": "https://www.myintervals.com/emogrifier.php", "keywords": [ - "ZendFramework", - "escaper", - "zf" + "css", + "email", + "pre-processing" ], - "abandoned": "laminas/laminas-escaper", - "time": "2019-09-05T20:03:20+00:00" + "time": "2019-09-04T16:07:59+00:00" }, { - "name": "zendframework/zend-eventmanager", - "version": "3.2.1", + "name": "php-amqplib/php-amqplib", + "version": "v2.10.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-eventmanager.git", - "reference": "a5e2583a211f73604691586b8406ff7296a946dd" + "url": "https://github.com/php-amqplib/php-amqplib.git", + "reference": "6e2b2501e021e994fb64429e5a78118f83b5c200" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-eventmanager/zipball/a5e2583a211f73604691586b8406ff7296a946dd", - "reference": "a5e2583a211f73604691586b8406ff7296a946dd", + "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/6e2b2501e021e994fb64429e5a78118f83b5c200", + "reference": "6e2b2501e021e994fb64429e5a78118f83b5c200", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "ext-bcmath": "*", + "ext-sockets": "*", + "php": ">=5.6" }, - "require-dev": { - "athletic/athletic": "^0.1", - "container-interop/container-interop": "^1.1.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-stdlib": "^2.7.3 || ^3.0" + "replace": { + "videlalvaro/php-amqplib": "self.version" }, - "suggest": { - "container-interop/container-interop": "^1.1.0, to use the lazy listeners feature", - "zendframework/zend-stdlib": "^2.7.3 || ^3.0, to use the FilterChain feature" + "require-dev": { + "ext-curl": "*", + "nategood/httpful": "^0.2.20", + "phpunit/phpunit": "^5.7|^6.5|^7.0", + "squizlabs/php_codesniffer": "^2.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev", - "dev-develop": "3.3-dev" + "dev-master": "2.10-dev" } }, "autoload": { "psr-4": { - "Zend\\EventManager\\": "src/" + "PhpAmqpLib\\": "PhpAmqpLib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "LGPL-2.1-or-later" ], - "description": "Trigger and listen to events within a PHP application", - "homepage": "https://github.com/zendframework/zend-eventmanager", + "authors": [ + { + "name": "Alvaro Videla", + "role": "Original Maintainer" + }, + { + "name": "John Kelly", + "email": "johnmkelly86@gmail.com", + "role": "Maintainer" + }, + { + "name": "Raúl Araya", + "email": "nubeiro@gmail.com", + "role": "Maintainer" + }, + { + "name": "Luke Bakken", + "email": "luke@bakken.io", + "role": "Maintainer" + } + ], + "description": "Formerly videlalvaro/php-amqplib. This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.", + "homepage": "https://github.com/php-amqplib/php-amqplib/", "keywords": [ - "event", - "eventmanager", - "events", - "zf2" + "message", + "queue", + "rabbitmq" ], - "abandoned": "laminas/laminas-eventmanager", - "time": "2018-04-25T15:33:34+00:00" + "time": "2019-10-10T13:23:40+00:00" }, { - "name": "zendframework/zend-feed", - "version": "2.12.0", + "name": "phpseclib/mcrypt_compat", + "version": "1.0.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-feed.git", - "reference": "d926c5af34b93a0121d5e2641af34ddb1533d733" + "url": "https://github.com/phpseclib/mcrypt_compat.git", + "reference": "f74c7b1897b62f08f268184b8bb98d9d9ab723b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-feed/zipball/d926c5af34b93a0121d5e2641af34ddb1533d733", - "reference": "d926c5af34b93a0121d5e2641af34ddb1533d733", + "url": "https://api.github.com/repos/phpseclib/mcrypt_compat/zipball/f74c7b1897b62f08f268184b8bb98d9d9ab723b0", + "reference": "f74c7b1897b62f08f268184b8bb98d9d9ab723b0", "shasum": "" }, "require": { - "ext-dom": "*", - "ext-libxml": "*", - "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5.2", - "zendframework/zend-stdlib": "^3.2.1" + "php": ">=5.3.3", + "phpseclib/phpseclib": ">=2.0.11 <3.0.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "psr/http-message": "^1.0.1", - "zendframework/zend-cache": "^2.7.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.8.2", - "zendframework/zend-http": "^2.7", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-validator": "^2.10.1" + "phpunit/phpunit": "^4.8.35|^5.7|^6.0" }, "suggest": { - "psr/http-message": "PSR-7 ^1.0.1, if you wish to use Zend\\Feed\\Reader\\Http\\Psr7ResponseDecorator", - "zendframework/zend-cache": "Zend\\Cache component, for optionally caching feeds between requests", - "zendframework/zend-db": "Zend\\Db component, for use with PubSubHubbub", - "zendframework/zend-http": "Zend\\Http for PubSubHubbub, and optionally for use with Zend\\Feed\\Reader", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for easily extending ExtensionManager implementations", - "zendframework/zend-validator": "Zend\\Validator component, for validating email addresses used in Atom feeds and entries when using the Writer subcomponent" + "ext-openssl": "Will enable faster cryptographic operations" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.12.x-dev", - "dev-develop": "2.13.x-dev" - } - }, "autoload": { - "psr-4": { - "Zend\\Feed\\": "src/" - } + "files": [ + "lib/mcrypt.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "provides functionality for consuming RSS and Atom feeds", + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "homepage": "http://phpseclib.sourceforge.net" + } + ], + "description": "PHP 7.1 polyfill for the mcrypt extension from PHP <= 7.0", "keywords": [ - "ZendFramework", - "feed", - "zf" + "cryptograpy", + "encryption", + "mcrypt" ], - "abandoned": "laminas/laminas-feed", - "time": "2019-03-05T20:08:49+00:00" + "time": "2018-08-22T03:11:43+00:00" }, { - "name": "zendframework/zend-filter", - "version": "2.9.2", + "name": "phpseclib/phpseclib", + "version": "2.0.25", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-filter.git", - "reference": "d78f2cdde1c31975e18b2a0753381ed7b61118ef" + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "c18159618ed7cd7ff721ac1a8fec7860a475d2f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/d78f2cdde1c31975e18b2a0753381ed7b61118ef", - "reference": "d78f2cdde1c31975e18b2a0753381ed7b61118ef", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c18159618ed7cd7ff721ac1a8fec7860a475d2f0", + "reference": "c18159618ed7cd7ff721ac1a8fec7860a475d2f0", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7.7 || ^3.1" - }, - "conflict": { - "zendframework/zend-validator": "<2.10.1" + "php": ">=5.3.3" }, "require-dev": { - "pear/archive_tar": "^1.4.3", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", - "psr/http-factory": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-crypt": "^3.2.1", - "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", - "zendframework/zend-uri": "^2.6" + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "sami/sami": "~2.0", + "squizlabs/php_codesniffer": "~2.0" }, "suggest": { - "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", - "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", - "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", - "zendframework/zend-uri": "Zend\\Uri component, for the UriNormalize filter" + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" - }, - "zf": { - "component": "Zend\\Filter", - "config-provider": "Zend\\Filter\\ConfigProvider" - } - }, "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], "psr-4": { - "Zend\\Filter\\": "src/" + "phpseclib\\": "phpseclib/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Programmatically filter and normalize data and files", + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", "keywords": [ - "ZendFramework", - "filter", - "zf" + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" ], - "abandoned": "laminas/laminas-filter", - "time": "2019-08-19T07:08:04+00:00" + "time": "2020-02-25T04:16:50+00:00" }, { - "name": "zendframework/zend-form", - "version": "2.14.3", + "name": "psr/container", + "version": "1.0.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-form.git", - "reference": "0b1616c59b1f3df194284e26f98c81ad0c377871" + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/0b1616c59b1f3df194284e26f98c81ad0c377871", - "reference": "0b1616c59b1f3df194284e26f98c81ad0c377871", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-stdlib": "^3.2.1" - }, - "require-dev": { - "doctrine/annotations": "~1.0", - "phpunit/phpunit": "^5.7.23 || ^6.5.3", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-captcha": "^2.7.1", - "zendframework/zend-code": "^2.6 || ^3.0", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.6", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.2", - "zendframework/zendservice-recaptcha": "^3.0.0" - }, - "suggest": { - "zendframework/zend-captcha": "^2.7.1, required for using CAPTCHA form elements", - "zendframework/zend-code": "^2.6 || ^3.0, required to use zend-form annotations support", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, reuired for zend-form annotations support", - "zendframework/zend-i18n": "^2.6, required when using zend-form view helpers", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, required to use the form factories or provide services", - "zendframework/zend-view": "^2.6.2, required for using the zend-form view helpers", - "zendframework/zendservice-recaptcha": "in order to use the ReCaptcha form element" + "php": ">=5.3.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.14.x-dev", - "dev-develop": "2.15.x-dev" - }, - "zf": { - "component": "Zend\\Form", - "config-provider": "Zend\\Form\\ConfigProvider" + "branch-alias": { + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Form\\": "src/" - }, - "files": [ - "autoload/formElementManagerPolyfill.php" - ] + "Psr\\Container\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Validate and display simple and complex forms, casting forms to business objects and vice versa", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "ZendFramework", - "form", - "zf" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], - "abandoned": "laminas/laminas-form", - "time": "2019-10-04T10:46:36+00:00" + "time": "2017-02-14T16:28:37+00:00" }, { - "name": "zendframework/zend-http", - "version": "2.11.2", + "name": "psr/http-message", + "version": "1.0.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-http.git", - "reference": "e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a" + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a", - "reference": "e15e0ce45a2a4f642cd0b7b4f4d4d0366b729a1a", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-loader": "^2.5.1", - "zendframework/zend-stdlib": "^3.2.1", - "zendframework/zend-uri": "^2.5.2", - "zendframework/zend-validator": "^2.10.1" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^3.1 || ^2.6" - }, - "suggest": { - "paragonie/certainty": "For automated management of cacert.pem" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.11.x-dev", - "dev-develop": "2.12.x-dev" + "dev-master": "1.0.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Http\\": "src/" + "Psr\\Http\\Message\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Provides an easy interface for performing Hyper-Text Transfer Protocol (HTTP) requests", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", "keywords": [ - "ZendFramework", "http", - "http client", - "zend", - "zf" + "http-message", + "psr", + "psr-7", + "request", + "response" ], - "abandoned": "laminas/laminas-http", - "time": "2019-12-30T20:47:33+00:00" + "time": "2016-08-06T14:39:51+00:00" }, { - "name": "zendframework/zend-hydrator", - "version": "2.4.2", + "name": "psr/log", + "version": "1.1.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-hydrator.git", - "reference": "2bfc6845019e7b6d38b0ab5e55190244dc510285" + "url": "https://github.com/php-fig/log.git", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-hydrator/zipball/2bfc6845019e7b6d38b0ab5e55190244dc510285", - "reference": "2bfc6845019e7b6d38b0ab5e55190244dc510285", + "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", + "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-inputfilter": "^2.6", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" - }, - "suggest": { - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0, to support aggregate hydrator usage", - "zendframework/zend-filter": "^2.6, to support naming strategy hydrator usage", - "zendframework/zend-serializer": "^2.6.1, to use the SerializableStrategy", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3, to support hydrator plugin manager usage" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-release-2.4": "2.4.x-dev" - }, - "zf": { - "component": "Zend\\Hydrator", - "config-provider": "Zend\\Hydrator\\ConfigProvider" + "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Hydrator\\": "src/" + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Serialize objects to arrays, and vice versa", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", "keywords": [ - "ZendFramework", - "hydrator", - "zf" + "log", + "psr", + "psr-3" ], - "abandoned": "laminas/laminas-hydrator", - "time": "2019-10-04T11:17:36+00:00" + "time": "2019-11-01T11:05:21+00:00" }, { - "name": "zendframework/zend-i18n", - "version": "2.10.1", + "name": "ralouphie/getallheaders", + "version": "3.0.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-i18n.git", - "reference": "84038e6a1838b611dcc491b1c40321fa4c3a123c" + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/84038e6a1838b611dcc491b1c40321fa4c3a123c", - "reference": "84038e6a1838b611dcc491b1c40321fa4c3a123c", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "ext-intl": "*", - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6", - "zendframework/zend-view": "^2.6.3" - }, - "suggest": { - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-eventmanager": "You should install this package to use the events in the translator", - "zendframework/zend-filter": "You should install this package to use the provided filters", - "zendframework/zend-i18n-resources": "Translation resources", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "You should install this package to use the provided validators", - "zendframework/zend-view": "You should install this package to use the provided view helpers" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" - }, - "zf": { - "component": "Zend\\I18n", - "config-provider": "Zend\\I18n\\ConfigProvider" - } - }, "autoload": { - "psr-4": { - "Zend\\I18n\\": "src/" - } + "files": [ + "src/getallheaders.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Provide translations for your application, and filter and validate internationalized values", - "keywords": [ - "ZendFramework", - "i18n", - "zf" + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } ], - "abandoned": "laminas/laminas-i18n", - "time": "2019-12-12T14:08:22+00:00" + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" }, { - "name": "zendframework/zend-inputfilter", - "version": "2.10.1", + "name": "ramsey/uuid", + "version": "3.8.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-inputfilter.git", - "reference": "1f44a2e9bc394a71638b43bc7024b572fa65410e" + "url": "https://github.com/ramsey/uuid.git", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/1f44a2e9bc394a71638b43bc7024b572fa65410e", - "reference": "1f44a2e9bc394a71638b43bc7024b572fa65410e", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/d09ea80159c1929d75b3f9c60504d613aeb4a1e3", + "reference": "d09ea80159c1929d75b3f9c60504d613aeb4a1e3", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-filter": "^2.9.1", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.11" + "paragonie/random_compat": "^1.0|^2.0|9.99.99", + "php": "^5.4 || ^7.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.15", - "psr/http-message": "^1.0", - "zendframework/zend-coding-standard": "~1.0.0" + "codeception/aspect-mock": "^1.0 | ~2.0.0", + "doctrine/annotations": "~1.2.0", + "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ~2.1.0", + "ircmaxell/random-lib": "^1.1", + "jakub-onderka/php-parallel-lint": "^0.9.0", + "mockery/mockery": "^0.9.9", + "moontoast/math": "^1.1", + "php-mock/php-mock-phpunit": "^0.3|^1.1", + "phpunit/phpunit": "^4.7|^5.0|^6.5", + "squizlabs/php_codesniffer": "^2.3" }, "suggest": { - "psr/http-message-implementation": "PSR-7 is required if you wish to validate PSR-7 UploadedFileInterface payloads" + "ext-ctype": "Provides support for PHP Ctype functions", + "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", + "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", + "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", + "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" - }, - "zf": { - "component": "Zend\\InputFilter", - "config-provider": "Zend\\InputFilter\\ConfigProvider" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Zend\\InputFilter\\": "src/" + "Ramsey\\Uuid\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Normalize and validate input sets from the web, APIs, the CLI, and more, including files", + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + }, + { + "name": "Marijn Huizendveld", + "email": "marijn.huizendveld@gmail.com" + }, + { + "name": "Thibaud Fabre", + "email": "thibaud@aztech.io" + } + ], + "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", + "homepage": "https://github.com/ramsey/uuid", "keywords": [ - "ZendFramework", - "inputfilter", - "zf" + "guid", + "identifier", + "uuid" ], - "abandoned": "laminas/laminas-inputfilter", - "time": "2019-08-28T19:45:32+00:00" + "time": "2018-07-19T23:38:55+00:00" }, { - "name": "zendframework/zend-json", - "version": "2.6.1", + "name": "react/promise", + "version": "v2.7.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-json.git", - "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28" + "url": "https://github.com/reactphp/promise.git", + "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-json/zipball/4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", - "reference": "4c8705dbe4ad7d7e51b2876c5b9eea0ef916ba28", + "url": "https://api.github.com/repos/reactphp/promise/zipball/31ffa96f8d2ed0341a57848cbb84d88b89dd664d", + "reference": "31ffa96f8d2ed0341a57848cbb84d88b89dd664d", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0" + "php": ">=5.4.0" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.5 || ^3.0", - "zendframework/zendxml": "^1.0.2" - }, - "suggest": { - "zendframework/zend-http": "Zend\\Http component, required to use Zend\\Json\\Server", - "zendframework/zend-server": "Zend\\Server component, required to use Zend\\Json\\Server", - "zendframework/zend-stdlib": "Zend\\Stdlib component, for use with caching Zend\\Json\\Server responses", - "zendframework/zendxml": "To support Zend\\Json\\Json::fromXml() usage" + "phpunit/phpunit": "~4.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6-dev", - "dev-develop": "2.7-dev" - } - }, "autoload": { "psr-4": { - "Zend\\Json\\": "src/" - } + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "provides convenience methods for serializing native PHP to JSON and decoding JSON to native PHP", - "homepage": "https://github.com/zendframework/zend-json", + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", "keywords": [ - "json", - "zf2" + "promise", + "promises" ], - "abandoned": "laminas/laminas-json", - "time": "2016-02-04T21:20:26+00:00" + "time": "2019-01-07T21:25:54+00:00" }, { - "name": "zendframework/zend-loader", - "version": "2.6.1", + "name": "seld/jsonlint", + "version": "1.7.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-loader.git", - "reference": "91da574d29b58547385b2298c020b257310898c6" + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-loader/zipball/91da574d29b58547385b2298c020b257310898c6", - "reference": "91da574d29b58547385b2298c020b257310898c6", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/e2e5d290e4d2a4f0eb449f510071392e00e10d19", + "reference": "e2e5d290e4d2a4f0eb449f510071392e00e10d19", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^5.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, + "bin": [ + "bin/jsonlint" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.6.x-dev", - "dev-develop": "2.7.x-dev" - } - }, "autoload": { "psr-4": { - "Zend\\Loader\\": "src/" + "Seld\\JsonLint\\": "src/Seld/JsonLint/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Autoloading and plugin loading strategies", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "JSON Linter", "keywords": [ - "ZendFramework", - "loader", - "zf" + "json", + "linter", + "parser", + "validator" ], - "abandoned": "laminas/laminas-loader", - "time": "2019-09-04T19:38:14+00:00" + "time": "2019-10-24T14:27:39+00:00" }, { - "name": "zendframework/zend-log", - "version": "2.12.0", + "name": "seld/phar-utils", + "version": "1.1.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-log.git", - "reference": "e5ec088dc8a7b4d96a3a6627761f720a738a36b8" + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/e5ec088dc8a7b4d96a3a6627761f720a738a36b8", - "reference": "e5ec088dc8a7b4d96a3a6627761f720a738a36b8", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/8800503d56b9867d43d9c303b9cbcc26016e82f0", + "reference": "8800503d56b9867d43d9c303b9cbcc26016e82f0", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "psr/log": "^1.1.2", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" - }, - "require-dev": { - "mikey179/vfsstream": "^1.6.7", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.15", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-filter": "^2.5", - "zendframework/zend-mail": "^2.6.1", - "zendframework/zend-validator": "^2.10.1" - }, - "suggest": { - "ext-mongo": "mongo extension to use Mongo writer", - "ext-mongodb": "mongodb extension to use MongoDB writer", - "zendframework/zend-db": "Zend\\Db component to use the database log writer", - "zendframework/zend-escaper": "Zend\\Escaper component, for use in the XML log formatter", - "zendframework/zend-mail": "Zend\\Mail component to use the email log writer", - "zendframework/zend-validator": "Zend\\Validator component to block invalid log messages" + "php": ">=5.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.12.x-dev", - "dev-develop": "2.13.x-dev" - }, - "zf": { - "component": "Zend\\Log", - "config-provider": "Zend\\Log\\ConfigProvider" + "dev-master": "1.x-dev" } }, "autoload": { "psr-4": { - "Zend\\Log\\": "src/" + "Seld\\PharUtils\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Robust, composite logger with filtering, formatting, and PSR-3 support", + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", "keywords": [ - "ZendFramework", - "log", - "logging", - "zf" + "phar" ], - "abandoned": "laminas/laminas-log", - "time": "2019-12-27T16:18:31+00:00" + "time": "2020-02-14T15:25:33+00:00" }, { - "name": "zendframework/zend-mail", - "version": "2.10.0", + "name": "symfony/console", + "version": "v4.4.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mail.git", - "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8" + "url": "https://github.com/symfony/console.git", + "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", - "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8", + "url": "https://api.github.com/repos/symfony/console/zipball/4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", + "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", "shasum": "" }, "require": { - "ext-iconv": "*", - "php": "^5.6 || ^7.0", - "true/punycode": "^2.1", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mime": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-validator": "^2.10.2" + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1|^2" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3|>=5", + "symfony/lock": "<4.4", + "symfony/process": "<3.3" + }, + "provide": { + "psr/log-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-crypt": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1" + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/event-dispatcher": "^4.3", + "symfony/lock": "^4.4|^5.0", + "symfony/process": "^3.4|^4.0|^5.0", + "symfony/var-dumper": "^4.3|^5.0" }, "suggest": { - "zendframework/zend-crypt": "Crammd5 support in SMTP Auth", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.10.x-dev", - "dev-develop": "2.11.x-dev" - }, - "zf": { - "component": "Zend\\Mail", - "config-provider": "Zend\\Mail\\ConfigProvider" + "dev-master": "4.4-dev" } }, "autoload": { "psr-4": { - "Zend\\Mail\\": "src/" - } + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", - "keywords": [ - "ZendFramework", - "mail", - "zf" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "abandoned": "laminas/laminas-mail", - "time": "2018-06-07T13:37:07+00:00" + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2020-02-24T13:10:00+00:00" }, { - "name": "zendframework/zend-math", - "version": "2.7.1", + "name": "symfony/css-selector", + "version": "v4.4.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-math.git", - "reference": "1abce074004dacac1a32cd54de94ad47ef960d38" + "url": "https://github.com/symfony/css-selector.git", + "reference": "d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-math/zipball/1abce074004dacac1a32cd54de94ad47ef960d38", - "reference": "1abce074004dacac1a32cd54de94ad47ef960d38", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22", + "reference": "d0a6dd288fa8848dcc3d1f58b94de6a7cc5d2d22", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0" - }, - "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "ircmaxell/random-lib": "~1.1", - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-bcmath": "If using the bcmath functionality", - "ext-gmp": "If using the gmp functionality", - "ircmaxell/random-lib": "Fallback random byte generator for Zend\\Math\\Rand if Mcrypt extensions is unavailable" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "2.8-dev" + "dev-master": "4.4-dev" } }, "autoload": { "psr-4": { - "Zend\\Math\\": "src/" - } + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "homepage": "https://github.com/zendframework/zend-math", - "keywords": [ - "math", - "zf2" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "abandoned": "laminas/laminas-math", - "time": "2018-12-04T15:34:17+00:00" + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2020-02-04T09:01:01+00:00" }, { - "name": "zendframework/zend-mime", - "version": "2.7.2", + "name": "symfony/event-dispatcher", + "version": "v4.4.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mime.git", - "reference": "c91e0350be53cc9d29be15563445eec3b269d7c1" + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mime/zipball/c91e0350be53cc9d29be15563445eec3b269d7c1", - "reference": "c91e0350be53cc9d29be15563445eec3b269d7c1", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/4ad8e149799d3128621a3a1f70e92b9897a8930d", + "reference": "4ad8e149799d3128621a3a1f70e92b9897a8930d", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": "^7.1.3", + "symfony/event-dispatcher-contracts": "^1.1" + }, + "conflict": { + "symfony/dependency-injection": "<3.4" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" }, "require-dev": { - "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-mail": "^2.6" + "psr/log": "~1.0", + "symfony/config": "^3.4|^4.0|^5.0", + "symfony/dependency-injection": "^3.4|^4.0|^5.0", + "symfony/expression-language": "^3.4|^4.0|^5.0", + "symfony/http-foundation": "^3.4|^4.0|^5.0", + "symfony/service-contracts": "^1.1|^2", + "symfony/stopwatch": "^3.4|^4.0|^5.0" }, "suggest": { - "zendframework/zend-mail": "Zend\\Mail component" + "symfony/dependency-injection": "", + "symfony/http-kernel": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7.x-dev", - "dev-develop": "2.8.x-dev" + "dev-master": "4.4-dev" } }, "autoload": { "psr-4": { - "Zend\\Mime\\": "src/" - } + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Create and parse MIME messages and parts", - "keywords": [ - "ZendFramework", - "mime", - "zf" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "abandoned": "laminas/laminas-mime", - "time": "2019-10-16T19:30:37+00:00" + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2020-02-04T09:32:40+00:00" }, { - "name": "zendframework/zend-modulemanager", - "version": "2.8.4", + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.7", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-modulemanager.git", - "reference": "b2596d24b9a4e36a3cd114d35d3ad0918db9a243" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-modulemanager/zipball/b2596d24b9a4e36a3cd114d35d3ad0918db9a243", - "reference": "b2596d24b9a4e36a3cd114d35d3ad0918db9a243", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c43ab685673fb6c8d84220c77897b1d6cdbe1d18", + "reference": "c43ab685673fb6c8d84220c77897b1d6cdbe1d18", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-config": "^3.1 || ^2.6", - "zendframework/zend-eventmanager": "^3.2 || ^2.6.3", - "zendframework/zend-stdlib": "^3.1 || ^2.7" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-console": "^2.6", - "zendframework/zend-di": "^2.6", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-mvc": "^3.0 || ^2.7", - "zendframework/zend-servicemanager": "^3.0.3 || ^2.7.5" + "php": "^7.1.3" }, "suggest": { - "zendframework/zend-console": "Zend\\Console component", - "zendframework/zend-loader": "Zend\\Loader component if you are not using Composer autoloading for your modules", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component" + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "1.1-dev" } }, "autoload": { "psr-4": { - "Zend\\ModuleManager\\": "src/" + "Symfony\\Contracts\\EventDispatcher\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "description": "Modular application system for zend-mvc applications", + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", "keywords": [ - "ZendFramework", - "modulemanager", - "zf" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], - "abandoned": "laminas/laminas-modulemanager", - "time": "2019-10-28T13:29:38+00:00" + "time": "2019-09-17T09:54:03+00:00" }, { - "name": "zendframework/zend-mvc", - "version": "2.7.15", + "name": "symfony/filesystem", + "version": "v4.4.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-mvc.git", - "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089" + "url": "https://github.com/symfony/filesystem.git", + "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-mvc/zipball/a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", - "reference": "a8d45689d37a9e4ff4b75ea0b7478fa3d4f9c089", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/266c9540b475f26122b61ef8b23dd9198f5d1cfd", + "reference": "266c9540b475f26122b61ef8b23dd9198f5d1cfd", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.1", - "php": "^5.5 || ^7.0", - "zendframework/zend-console": "^2.7", - "zendframework/zend-eventmanager": "^2.6.4 || ^3.0", - "zendframework/zend-form": "^2.11", - "zendframework/zend-hydrator": "^1.1 || ^2.4", - "zendframework/zend-psr7bridge": "^0.2", - "zendframework/zend-servicemanager": "^2.7.10 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7.5 || ^3.0" - }, - "replace": { - "zendframework/zend-router": "^2.0" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "^4.8.36", - "sebastian/comparator": "^1.2.4", - "sebastian/version": "^1.0.4", - "zendframework/zend-authentication": "^2.6", - "zendframework/zend-cache": "^2.8", - "zendframework/zend-di": "^2.6", - "zendframework/zend-filter": "^2.8", - "zendframework/zend-http": "^2.8", - "zendframework/zend-i18n": "^2.8", - "zendframework/zend-inputfilter": "^2.8", - "zendframework/zend-json": "^2.6.1", - "zendframework/zend-log": "^2.9.3", - "zendframework/zend-modulemanager": "^2.8", - "zendframework/zend-serializer": "^2.8", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-text": "^2.7", - "zendframework/zend-uri": "^2.6", - "zendframework/zend-validator": "^2.10", - "zendframework/zend-view": "^2.9" - }, - "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component for Identity plugin", - "zendframework/zend-config": "Zend\\Config component", - "zendframework/zend-di": "Zend\\Di component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component for translatable segments", - "zendframework/zend-inputfilter": "Zend\\Inputfilter component", - "zendframework/zend-json": "Zend\\Json component", - "zendframework/zend-log": "Zend\\Log component", - "zendframework/zend-modulemanager": "Zend\\ModuleManager component", - "zendframework/zend-serializer": "Zend\\Serializer component", - "zendframework/zend-servicemanager-di": "^1.0.1, if using zend-servicemanager v3 and requiring the zend-di integration", - "zendframework/zend-session": "Zend\\Session component for FlashMessenger, PRG, and FPRG plugins", - "zendframework/zend-text": "Zend\\Text component", - "zendframework/zend-uri": "Zend\\Uri component", - "zendframework/zend-validator": "Zend\\Validator component", - "zendframework/zend-view": "Zend\\View component" + "php": "^7.1.3", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "3.0-dev" + "dev-master": "4.4-dev" } }, "autoload": { - "files": [ - "src/autoload.php" - ], "psr-4": { - "Zend\\Mvc\\": "src/" - } + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "homepage": "https://github.com/zendframework/zend-mvc", - "keywords": [ - "mvc", - "zf2" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "abandoned": "laminas/laminas-mvc", - "time": "2018-05-03T13:13:41+00:00" + "description": "Symfony Filesystem Component", + "homepage": "https://symfony.com", + "time": "2020-01-21T08:20:44+00:00" }, { - "name": "zendframework/zend-psr7bridge", - "version": "0.2.2", + "name": "symfony/finder", + "version": "v4.4.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-psr7bridge.git", - "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605" + "url": "https://github.com/symfony/finder.git", + "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-psr7bridge/zipball/86c0b53b0c6381391c4add4a93a56e51d5c74605", - "reference": "86c0b53b0c6381391c4add4a93a56e51d5c74605", + "url": "https://api.github.com/repos/symfony/finder/zipball/ea69c129aed9fdeca781d4b77eb20b62cf5d5357", + "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357", "shasum": "" }, "require": { - "php": ">=5.5", - "psr/http-message": "^1.0", - "zendframework/zend-diactoros": "^1.1", - "zendframework/zend-http": "^2.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.7", - "squizlabs/php_codesniffer": "^2.3" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev", - "dev-develop": "1.1-dev" + "dev-master": "4.4-dev" } }, "autoload": { "psr-4": { - "Zend\\Psr7Bridge\\": "src/" - } + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "PSR-7 <-> Zend\\Http bridge", - "homepage": "https://github.com/zendframework/zend-psr7bridge", - "keywords": [ - "http", - "psr", - "psr-7" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "abandoned": "laminas/laminas-psr7bridge", - "time": "2016-05-10T21:44:39+00:00" + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2020-02-14T07:42:58+00:00" }, { - "name": "zendframework/zend-serializer", - "version": "2.9.1", + "name": "symfony/polyfill-ctype", + "version": "v1.14.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-serializer.git", - "reference": "6fb7ae016cfdf0cfcdfa2b989e6a65f351170e21" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-serializer/zipball/6fb7ae016cfdf0cfcdfa2b989e6a65f351170e21", - "reference": "6fb7ae016cfdf0cfcdfa2b989e6a65f351170e21", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", + "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-json": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-math": "^2.6 || ^3.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" + "php": ">=5.3.3" }, "suggest": { - "zendframework/zend-math": "(^2.6 || ^3.0) To support Python Pickle serialization", - "zendframework/zend-servicemanager": "(^2.7.5 || ^3.0.3) To support plugin manager support" + "ext-ctype": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" - }, - "zf": { - "component": "Zend\\Serializer", - "config-provider": "Zend\\Serializer\\ConfigProvider" + "dev-master": "1.14-dev" } }, "autoload": { "psr-4": { - "Zend\\Serializer\\": "src/" - } + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Serialize and deserialize PHP structures to a variety of representations", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", "keywords": [ - "ZendFramework", - "serializer", - "zf" + "compatibility", + "ctype", + "polyfill", + "portable" ], - "abandoned": "laminas/laminas-serializer", - "time": "2019-10-19T08:06:30+00:00" + "time": "2020-01-13T11:15:53+00:00" }, { - "name": "zendframework/zend-server", - "version": "2.8.1", + "name": "symfony/polyfill-mbstring", + "version": "v1.14.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-server.git", - "reference": "d80c44700ebb92191dd9a3005316a6ab6637c0d1" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-server/zipball/d80c44700ebb92191dd9a3005316a6ab6637c0d1", - "reference": "d80c44700ebb92191dd9a3005316a6ab6637c0d1", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2", + "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-code": "^2.5 || ^3.0", - "zendframework/zend-stdlib": "^2.5 || ^3.0" + "php": ">=5.3.3" }, - "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "1.14-dev" } }, "autoload": { "psr-4": { - "Zend\\Server\\": "src/" - } + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Create Reflection-based RPC servers", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", "keywords": [ - "ZendFramework", - "server", - "zf" + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" ], - "abandoned": "laminas/laminas-server", - "time": "2019-10-16T18:27:05+00:00" + "time": "2020-01-13T11:15:53+00:00" }, { - "name": "zendframework/zend-servicemanager", - "version": "2.7.11", + "name": "symfony/polyfill-php73", + "version": "v1.14.0", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-servicemanager.git", - "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", - "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/5e66a0fa1070bf46bec4bea7962d285108edd675", + "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675", "shasum": "" }, "require": { - "container-interop/container-interop": "~1.0", - "php": "^5.5 || ^7.0" - }, - "require-dev": { - "athletic/athletic": "dev-master", - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", - "zendframework/zend-di": "~2.5", - "zendframework/zend-mvc": "~2.5" - }, - "suggest": { - "ocramius/proxy-manager": "ProxyManager 0.5.* to handle lazy initialization of services", - "zendframework/zend-di": "Zend\\Di component" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7-dev", - "dev-develop": "3.0-dev" + "dev-master": "1.14-dev" } }, "autoload": { "psr-4": { - "Zend\\ServiceManager\\": "src/" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "homepage": "https://github.com/zendframework/zend-servicemanager", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", "keywords": [ - "servicemanager", - "zf2" + "compatibility", + "polyfill", + "portable", + "shim" ], - "abandoned": "laminas/laminas-servicemanager", - "time": "2018-06-22T14:49:54+00:00" + "time": "2020-01-13T11:15:53+00:00" }, { - "name": "zendframework/zend-session", - "version": "2.9.1", + "name": "symfony/process", + "version": "v4.4.5", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-session.git", - "reference": "c289c4d733ec23a389e25c7c451f4d062088511f" + "url": "https://github.com/symfony/process.git", + "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-session/zipball/c289c4d733ec23a389e25c7c451f4d062088511f", - "reference": "c289c4d733ec23a389e25c7c451f4d062088511f", + "url": "https://api.github.com/repos/symfony/process/zipball/bf9166bac906c9e69fb7a11d94875e7ced97bcd7", + "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-stdlib": "^3.2.1" - }, - "require-dev": { - "container-interop/container-interop": "^1.1", - "mongodb/mongodb": "^1.0.1", - "php-mock/php-mock-phpunit": "^1.1.2 || ^2.0", - "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.16", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-db": "^2.7", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-validator": "^2.6" - }, - "suggest": { - "mongodb/mongodb": "If you want to use the MongoDB session save handler", - "zendframework/zend-cache": "Zend\\Cache component", - "zendframework/zend-db": "Zend\\Db component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-validator": "Zend\\Validator component" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" - }, - "zf": { - "component": "Zend\\Session", - "config-provider": "Zend\\Session\\ConfigProvider" + "dev-master": "4.4-dev" } }, "autoload": { "psr-4": { - "Zend\\Session\\": "src/" - } + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Object-oriented interface to PHP sessions and storage", - "keywords": [ - "ZendFramework", - "session", - "zf" + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "abandoned": "laminas/laminas-session", - "time": "2019-10-28T19:40:43+00:00" + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2020-02-07T20:06:44+00:00" }, { - "name": "zendframework/zend-soap", - "version": "2.8.0", + "name": "symfony/service-contracts", + "version": "v2.0.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-soap.git", - "reference": "8762d79efa220d82529c43ce08d70554146be645" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "144c5e51266b281231e947b51223ba14acf1a749" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-soap/zipball/8762d79efa220d82529c43ce08d70554146be645", - "reference": "8762d79efa220d82529c43ce08d70554146be645", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", + "reference": "144c5e51266b281231e947b51223ba14acf1a749", "shasum": "" }, "require": { - "ext-soap": "*", - "php": "^5.6 || ^7.0", - "zendframework/zend-server": "^2.6.1", - "zendframework/zend-stdlib": "^2.7 || ^3.0", - "zendframework/zend-uri": "^2.5.2" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.21 || ^6.3", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-http": "^2.5.4" + "php": "^7.2.5", + "psr/container": "^1.0" }, "suggest": { - "zendframework/zend-http": "Zend\\Http component" + "symfony/service-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.7.x-dev", - "dev-develop": "2.8.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { "psr-4": { - "Zend\\Soap\\": "src/" + "Symfony\\Contracts\\Service\\": "" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } ], - "homepage": "https://github.com/zendframework/zend-soap", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", "keywords": [ - "soap", - "zf2" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], - "abandoned": "laminas/laminas-soap", - "time": "2019-04-30T16:45:35+00:00" + "time": "2019-11-18T17:27:11+00:00" }, { - "name": "zendframework/zend-stdlib", - "version": "3.2.1", + "name": "tedivm/jshrink", + "version": "v1.3.3", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-stdlib.git", - "reference": "66536006722aff9e62d1b331025089b7ec71c065" + "url": "https://github.com/tedious/JShrink.git", + "reference": "566e0c731ba4e372be2de429ef7d54f4faf4477a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-stdlib/zipball/66536006722aff9e62d1b331025089b7ec71c065", - "reference": "66536006722aff9e62d1b331025089b7ec71c065", + "url": "https://api.github.com/repos/tedious/JShrink/zipball/566e0c731ba4e372be2de429ef7d54f4faf4477a", + "reference": "566e0c731ba4e372be2de429ef7d54f4faf4477a", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^5.6|^7.0" }, "require-dev": { - "phpbench/phpbench": "^0.13", - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.2", - "zendframework/zend-coding-standard": "~1.0.0" + "friendsofphp/php-cs-fixer": "^2.8", + "php-coveralls/php-coveralls": "^1.1.0", + "phpunit/phpunit": "^6" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2.x-dev", - "dev-develop": "3.3.x-dev" - } - }, "autoload": { - "psr-4": { - "Zend\\Stdlib\\": "src/" + "psr-0": { + "JShrink": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "SPL extensions, array utilities, error handlers, and more", + "authors": [ + { + "name": "Robert Hafner", + "email": "tedivm@tedivm.com" + } + ], + "description": "Javascript Minifier built in PHP", + "homepage": "http://github.com/tedious/JShrink", "keywords": [ - "ZendFramework", - "stdlib", - "zf" + "javascript", + "minifier" ], - "abandoned": "laminas/laminas-stdlib", - "time": "2018-08-28T21:34:05+00:00" + "time": "2019-06-28T18:11:46+00:00" }, { - "name": "zendframework/zend-text", - "version": "2.7.1", + "name": "true/punycode", + "version": "v2.1.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-text.git", - "reference": "41e32dafa4015e160e2f95a7039554385c71624d" + "url": "https://github.com/true/php-punycode.git", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-text/zipball/41e32dafa4015e160e2f95a7039554385c71624d", - "reference": "41e32dafa4015e160e2f95a7039554385c71624d", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-stdlib": "^2.7 || ^3.0" + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6" + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7.x-dev", - "dev-develop": "2.8.x-dev" - } - }, "autoload": { "psr-4": { - "Zend\\Text\\": "src/" + "TrueBV\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Create FIGlets and text-based tables", + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "homepage": "https://github.com/true/php-punycode", "keywords": [ - "ZendFramework", - "text", - "zf" + "idna", + "punycode" ], - "abandoned": "laminas/laminas-text", - "time": "2019-10-16T20:36:27+00:00" + "time": "2016-11-16T10:37:54+00:00" }, { - "name": "zendframework/zend-uri", - "version": "2.7.1", + "name": "tubalmartin/cssmin", + "version": "v4.1.1", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-uri.git", - "reference": "bfc4a5b9a309711e968d7c72afae4ac50c650083" + "url": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port.git", + "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/bfc4a5b9a309711e968d7c72afae4ac50c650083", - "reference": "bfc4a5b9a309711e968d7c72afae4ac50c650083", + "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/3cbf557f4079d83a06f9c3ff9b957c022d7805cf", + "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-validator": "^2.10" + "ext-pcre": "*", + "php": ">=5.3.2" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6.5.8 || ^7.1.4", - "zendframework/zend-coding-standard": "~1.0.0" + "cogpowered/finediff": "0.3.*", + "phpunit/phpunit": "4.8.*" }, + "bin": [ + "cssmin" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.7.x-dev", - "dev-develop": "2.8.x-dev" - } - }, "autoload": { "psr-4": { - "Zend\\Uri\\": "src/" + "tubalmartin\\CssMin\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], - "description": "A component that aids in manipulating and validating » Uniform Resource Identifiers (URIs)", + "authors": [ + { + "name": "Túbal Martín", + "homepage": "http://tubalmartin.me/" + } + ], + "description": "A PHP port of the YUI CSS compressor", + "homepage": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port", "keywords": [ - "ZendFramework", - "uri", - "zf" + "compress", + "compressor", + "css", + "cssmin", + "minify", + "yui" ], - "abandoned": "laminas/laminas-uri", - "time": "2019-10-07T13:35:33+00:00" + "time": "2018-01-15T15:26:51+00:00" }, { - "name": "zendframework/zend-validator", - "version": "2.13.0", + "name": "webonyx/graphql-php", + "version": "v0.13.8", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-validator.git", - "reference": "b54acef1f407741c5347f2a97f899ab21f2229ef" + "url": "https://github.com/webonyx/graphql-php.git", + "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/b54acef1f407741c5347f2a97f899ab21f2229ef", - "reference": "b54acef1f407741c5347f2a97f899ab21f2229ef", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/6829ae58f4c59121df1f86915fb9917a2ec595e8", + "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8", "shasum": "" }, "require": { - "container-interop/container-interop": "^1.1", - "php": "^7.1", - "zendframework/zend-stdlib": "^3.2.1" + "ext-json": "*", + "ext-mbstring": "*", + "php": "^7.1||^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.0.8 || ^5.7.15", - "psr/http-client": "^1.0", - "psr/http-factory": "^1.0", + "doctrine/coding-standard": "^6.0", + "phpbench/phpbench": "^0.14.0", + "phpstan/phpstan": "^0.11.4", + "phpstan/phpstan-phpunit": "^0.11.0", + "phpstan/phpstan-strict-rules": "^0.11.0", + "phpunit/phpcov": "^5.0", + "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-db": "^2.7", - "zendframework/zend-filter": "^2.6", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-math": "^2.6", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8", - "zendframework/zend-uri": "^2.5" + "react/promise": "2.*" }, "suggest": { - "psr/http-message": "psr/http-message, required when validating PSR-7 UploadedFileInterface instances via the Upload and UploadFile validators", - "zendframework/zend-db": "Zend\\Db component, required by the (No)RecordExists validator", - "zendframework/zend-filter": "Zend\\Filter component, required by the Digits validator", - "zendframework/zend-i18n": "Zend\\I18n component to allow translation of validation error messages", - "zendframework/zend-i18n-resources": "Translations of validator messages", - "zendframework/zend-math": "Zend\\Math component, required by the Csrf validator", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component to allow using the ValidatorPluginManager and validator chains", - "zendframework/zend-session": "Zend\\Session component, ^2.8; required by the Csrf validator", - "zendframework/zend-uri": "Zend\\Uri component, required by the Uri and Sitemap\\Loc validators" + "psr/http-message": "To use standard GraphQL server", + "react/promise": "To leverage async resolving on React PHP platform" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.13.x-dev", - "dev-develop": "2.14.x-dev" - }, - "zf": { - "component": "Zend\\Validator", - "config-provider": "Zend\\Validator\\ConfigProvider" - } - }, "autoload": { "psr-4": { - "Zend\\Validator\\": "src/" + "GraphQL\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "description": "Validation classes for a wide range of domains, and the ability to chain validators to create complex validation criteria", + "description": "A PHP port of GraphQL reference implementation", + "homepage": "https://github.com/webonyx/graphql-php", "keywords": [ - "ZendFramework", - "validator", - "zf" + "api", + "graphql" ], - "abandoned": "laminas/laminas-validator", - "time": "2019-12-28T04:07:18+00:00" + "time": "2019-08-25T10:32:47+00:00" }, { - "name": "zendframework/zend-view", - "version": "2.11.4", + "name": "wikimedia/less.php", + "version": "1.8.2", "source": { "type": "git", - "url": "https://github.com/zendframework/zend-view.git", - "reference": "a8b1b2d9b52e191539be861a6529f8c8a0c06b9d" + "url": "https://github.com/wikimedia/less.php.git", + "reference": "e238ad228d74b6ffd38209c799b34e9826909266" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-view/zipball/a8b1b2d9b52e191539be861a6529f8c8a0c06b9d", - "reference": "a8b1b2d9b52e191539be861a6529f8c8a0c06b9d", + "url": "https://api.github.com/repos/wikimedia/less.php/zipball/e238ad228d74b6ffd38209c799b34e9826909266", + "reference": "e238ad228d74b6ffd38209c799b34e9826909266", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", - "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", - "zendframework/zend-json": "^2.6.1 || ^3.0", - "zendframework/zend-loader": "^2.5", - "zendframework/zend-stdlib": "^2.7 || ^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.15 || ^6.0.8", - "zendframework/zend-authentication": "^2.5", - "zendframework/zend-cache": "^2.6.1", - "zendframework/zend-coding-standard": "~1.0.0", - "zendframework/zend-config": "^2.6", - "zendframework/zend-console": "^2.6", - "zendframework/zend-escaper": "^2.5", - "zendframework/zend-feed": "^2.7", - "zendframework/zend-filter": "^2.6.1", - "zendframework/zend-http": "^2.5.4", - "zendframework/zend-i18n": "^2.6", - "zendframework/zend-log": "^2.7", - "zendframework/zend-modulemanager": "^2.7.1", - "zendframework/zend-mvc": "^2.7.14 || ^3.0", - "zendframework/zend-navigation": "^2.5", - "zendframework/zend-paginator": "^2.5", - "zendframework/zend-permissions-acl": "^2.6", - "zendframework/zend-router": "^3.0.1", - "zendframework/zend-serializer": "^2.6.1", - "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", - "zendframework/zend-session": "^2.8.1", - "zendframework/zend-uri": "^2.5" + "php": ">=7.2.9" }, - "suggest": { - "zendframework/zend-authentication": "Zend\\Authentication component", - "zendframework/zend-escaper": "Zend\\Escaper component", - "zendframework/zend-feed": "Zend\\Feed component", - "zendframework/zend-filter": "Zend\\Filter component", - "zendframework/zend-http": "Zend\\Http component", - "zendframework/zend-i18n": "Zend\\I18n component", - "zendframework/zend-mvc": "Zend\\Mvc component", - "zendframework/zend-mvc-plugin-flashmessenger": "zend-mvc-plugin-flashmessenger component, if you want to use the FlashMessenger view helper with zend-mvc versions 3 and up", - "zendframework/zend-navigation": "Zend\\Navigation component", - "zendframework/zend-paginator": "Zend\\Paginator component", - "zendframework/zend-permissions-acl": "Zend\\Permissions\\Acl component", - "zendframework/zend-servicemanager": "Zend\\ServiceManager component", - "zendframework/zend-uri": "Zend\\Uri component" + "require-dev": { + "phpunit/phpunit": "7.5.14" }, "bin": [ - "bin/templatemap_generator.php" + "bin/lessc" ], "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.11.x-dev", - "dev-develop": "2.12.x-dev" - } - }, "autoload": { - "psr-4": { - "Zend\\View\\": "src/" - } + "psr-0": { + "Less": "lib/" + }, + "classmap": [ + "lessc.inc.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], - "description": "Flexible view layer supporting and providing multiple view layers, helpers, and more", + "authors": [ + { + "name": "Josh Schmidt", + "homepage": "https://github.com/oyejorge" + }, + { + "name": "Matt Agar", + "homepage": "https://github.com/agar" + }, + { + "name": "Martin Jantošovič", + "homepage": "https://github.com/Mordred" + } + ], + "description": "PHP port of the Javascript version of LESS http://lesscss.org (Originally maintained by Josh Schmidt)", "keywords": [ - "ZendFramework", - "view", - "zf" + "css", + "less", + "less.js", + "lesscss", + "php", + "stylesheet" ], - "abandoned": "laminas/laminas-view", - "time": "2019-12-04T08:40:50+00:00" + "time": "2019-11-06T18:30:11+00:00" } ], "packages-dev": [ @@ -7912,16 +8124,16 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "262ea0d209c292e0330be1041424887bbbffef04" + "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/262ea0d209c292e0330be1041424887bbbffef04", - "reference": "262ea0d209c292e0330be1041424887bbbffef04", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", + "reference": "3308a70be084d6d7fd1ee5787b4c2e6eb4b70aab", "shasum": "" }, "require": { @@ -7973,7 +8185,7 @@ "selenium", "webdriver" ], - "time": "2020-02-17T08:14:38+00:00" + "time": "2020-03-04T14:40:12+00:00" }, { "name": "phpcollection/phpcollection", @@ -8188,26 +8400,25 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9" + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", - "reference": "2e32a6d48972b2c1976ed5d8967145b6cec4a4a9", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", + "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", "shasum": "" }, "require": { - "php": "^7.1", + "php": "^7.2", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { - "ext-tokenizer": "^7.1", - "mockery/mockery": "~1", - "phpunit/phpunit": "^7.0" + "ext-tokenizer": "^7.2", + "mockery/mockery": "~1" }, "type": "library", "extra": { @@ -8231,7 +8442,7 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "time": "2019-08-22T18:11:29+00:00" + "time": "2020-02-18T18:59:58+00:00" }, { "name": "phpmd/phpmd", @@ -8358,16 +8569,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.10.2", + "version": "v1.10.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9" + "reference": "451c3cd1418cf640de218914901e51b064abb093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9", - "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", "shasum": "" }, "require": { @@ -8417,20 +8628,20 @@ "spy", "stub" ], - "time": "2020-01-20T15:57:02+00:00" + "time": "2020-03-05T15:02:03+00:00" }, { "name": "phpstan/phpstan", - "version": "0.12.11", + "version": "0.12.14", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "ca5f2b7cf81c6d8fba74f9576970399c5817e03b" + "reference": "37bdd26a80235d0f9045b49f4151102b7831cbe2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ca5f2b7cf81c6d8fba74f9576970399c5817e03b", - "reference": "ca5f2b7cf81c6d8fba74f9576970399c5817e03b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37bdd26a80235d0f9045b49f4151102b7831cbe2", + "reference": "37bdd26a80235d0f9045b49f4151102b7831cbe2", "shasum": "" }, "require": { @@ -8456,7 +8667,7 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2020-02-16T14:00:29+00:00" + "time": "2020-03-02T22:29:43+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9650,16 +9861,16 @@ }, { "name": "symfony/browser-kit", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "45cae6dd8683d2de56df7ec23638e9429c70135f" + "reference": "090ce406505149d6852a7c03b0346dec3b8cf612" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/45cae6dd8683d2de56df7ec23638e9429c70135f", - "reference": "45cae6dd8683d2de56df7ec23638e9429c70135f", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/090ce406505149d6852a7c03b0346dec3b8cf612", + "reference": "090ce406505149d6852a7c03b0346dec3b8cf612", "shasum": "" }, "require": { @@ -9705,20 +9916,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-02-23T10:00:59+00:00" }, { "name": "symfony/config", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4d3979f54472637169080f802dc82197e21fdcce" + "reference": "cbfef5ae91ccd3b06621c18d58cd355c68c87ae9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4d3979f54472637169080f802dc82197e21fdcce", - "reference": "4d3979f54472637169080f802dc82197e21fdcce", + "url": "https://api.github.com/repos/symfony/config/zipball/cbfef5ae91ccd3b06621c18d58cd355c68c87ae9", + "reference": "cbfef5ae91ccd3b06621c18d58cd355c68c87ae9", "shasum": "" }, "require": { @@ -9769,20 +9980,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-02-04T09:32:40+00:00" }, { "name": "symfony/dependency-injection", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "ec60a7d12f5e8ab0f99456adce724717d9c1784a" + "reference": "ebb2e882e8c9e2eb990aa61ddcd389848466e342" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ec60a7d12f5e8ab0f99456adce724717d9c1784a", - "reference": "ec60a7d12f5e8ab0f99456adce724717d9c1784a", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ebb2e882e8c9e2eb990aa61ddcd389848466e342", + "reference": "ebb2e882e8c9e2eb990aa61ddcd389848466e342", "shasum": "" }, "require": { @@ -9842,20 +10053,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2020-01-31T09:49:27+00:00" + "time": "2020-02-29T09:50:10+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "b66fe8ccc850ea11c4cd31677706c1219768bea1" + "reference": "11dcf08f12f29981bf770f097a5d64d65bce5929" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/b66fe8ccc850ea11c4cd31677706c1219768bea1", - "reference": "b66fe8ccc850ea11c4cd31677706c1219768bea1", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/11dcf08f12f29981bf770f097a5d64d65bce5929", + "reference": "11dcf08f12f29981bf770f097a5d64d65bce5929", "shasum": "" }, "require": { @@ -9903,20 +10114,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2020-01-04T13:00:46+00:00" + "time": "2020-02-29T10:05:28+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "491a20dfa87e0b3990170593bc2de0bb34d828a5" + "reference": "7e41b4fcad4619535f45f8bfa7744c4f384e1648" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/491a20dfa87e0b3990170593bc2de0bb34d828a5", - "reference": "491a20dfa87e0b3990170593bc2de0bb34d828a5", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7e41b4fcad4619535f45f8bfa7744c4f384e1648", + "reference": "7e41b4fcad4619535f45f8bfa7744c4f384e1648", "shasum": "" }, "require": { @@ -9958,20 +10169,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2020-01-31T09:11:17+00:00" + "time": "2020-02-13T19:40:01+00:00" }, { "name": "symfony/mime", - "version": "v5.0.4", + "version": "v5.0.5", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "2a3c7fee1f1a0961fa9cf360d5da553d05095e59" + "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/2a3c7fee1f1a0961fa9cf360d5da553d05095e59", - "reference": "2a3c7fee1f1a0961fa9cf360d5da553d05095e59", + "url": "https://api.github.com/repos/symfony/mime/zipball/9b3e5b5e58c56bbd76628c952d2b78556d305f3c", + "reference": "9b3e5b5e58c56bbd76628c952d2b78556d305f3c", "shasum": "" }, "require": { @@ -10020,11 +10231,11 @@ "mime", "mime-type" ], - "time": "2020-01-04T14:08:26+00:00" + "time": "2020-02-04T09:41:09+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", @@ -10254,7 +10465,7 @@ }, { "name": "symfony/stopwatch", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -10304,16 +10515,16 @@ }, { "name": "symfony/yaml", - "version": "v4.4.4", + "version": "v4.4.5", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "cd014e425b3668220adb865f53bff64b3ad21767" + "reference": "94d005c176db2080e98825d98e01e8b311a97a88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/cd014e425b3668220adb865f53bff64b3ad21767", - "reference": "cd014e425b3668220adb865f53bff64b3ad21767", + "url": "https://api.github.com/repos/symfony/yaml/zipball/94d005c176db2080e98825d98e01e8b311a97a88", + "reference": "94d005c176db2080e98825d98e01e8b311a97a88", "shasum": "" }, "require": { @@ -10359,7 +10570,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2020-01-21T11:12:16+00:00" + "time": "2020-02-03T10:46:43+00:00" }, { "name": "theseer/fdomdocument", diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index 9b0b53e11615f..c5fdd050bb46b 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -691,14 +691,14 @@ public function testConfirmationEmailWithSpecialCharacters(): void $message = $this->transportBuilderMock->getSentMessage(); $rawMessage = $message->getRawMessage(); - /** @var \Zend\Mime\Part $messageBodyPart */ + /** @var \Laminas\Mime\Part $messageBodyPart */ $messageBodyParts = $message->getBody()->getParts(); $messageBodyPart = reset($messageBodyParts); $messageEncoding = $messageBodyPart->getCharset(); $name = 'John Smith'; if (strtoupper($messageEncoding) !== 'ASCII') { - $name = \Zend\Mail\Header\HeaderWrap::mimeEncodeValue($name, $messageEncoding); + $name = \Laminas\Mail\Header\HeaderWrap::mimeEncodeValue($name, $messageEncoding); } $nameEmail = sprintf('%s <%s>', $name, $email); diff --git a/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php b/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php index cb338d00cab16..1fc58ff136c92 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php +++ b/dev/tests/integration/testsuite/Magento/Framework/App/Response/HeaderProvider/AbstractHeaderTestCase.php @@ -5,8 +5,8 @@ */ namespace Magento\Framework\App\Response\HeaderProvider; +use Laminas\Http\Header\HeaderInterface; use Magento\Framework\App\Response\Http as HttpResponse; -use Zend\Http\Header\HeaderInterface; /** * Class AbstractHeaderTestCase @@ -26,8 +26,7 @@ public function setUp() parent::setUp(); $this->_objectManager->configure( [ - 'preferences' => - [ + 'preferences' => [ HttpResponse::class => 'Magento\Framework\App\Response\Http\Interceptor' ] ] diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index a5a04228b4d2a..35fc402d3805f 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -11,6 +11,7 @@ }, "require": { "php": "~7.1.3||~7.2.0||~7.3.0", + "ext-bcmath": "*", "ext-curl": "*", "ext-dom": "*", "ext-gd": "*", @@ -20,27 +21,26 @@ "ext-openssl": "*", "ext-simplexml": "*", "ext-xsl": "*", - "ext-bcmath": "*", "lib-libxml": "*", "colinmollenhour/php-redis-session-abstract": "~1.4.0", "composer/composer": "^1.6", + "guzzlehttp/guzzle": "^6.3.3", + "laminas/laminas-code": "~3.3.0", + "laminas/laminas-crypt": "^2.6.0", + "laminas/laminas-http": "^2.6.0", + "laminas/laminas-mail": "^2.9.0", + "laminas/laminas-mime": "^2.5.0", + "laminas/laminas-mvc": "~2.7.0", + "laminas/laminas-stdlib": "^3.2.1", + "laminas/laminas-uri": "^2.5.1", + "laminas/laminas-validator": "^2.6.0", "magento/zendframework1": "~1.14.2", "monolog/monolog": "^1.17", - "wikimedia/less.php": "~1.8.0", + "ramsey/uuid": "~3.8.0", "symfony/console": "~4.4.0", "symfony/process": "~4.4.0", "tedivm/jshrink": "~1.3.0", - "zendframework/zend-code": "~3.3.0", - "zendframework/zend-crypt": "^2.6.0", - "zendframework/zend-http": "^2.6.0", - "zendframework/zend-mvc": "~2.7.0", - "zendframework/zend-stdlib": "^3.2.1", - "zendframework/zend-uri": "^2.5.1", - "zendframework/zend-validator": "^2.6.0", - "zendframework/zend-mail": "^2.9.0", - "zendframework/zend-mime": "^2.5.0", - "guzzlehttp/guzzle": "^6.3.3", - "ramsey/uuid": "~3.8.0" + "wikimedia/less.php": "~1.8.0" }, "archive": { "exclude": [ From 5a836abef542dffa23bd131f74d9f9afc9860c17 Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Mon, 9 Mar 2020 17:02:32 -0500 Subject: [PATCH 233/369] MC-13825: [2.4.x] Migrate ZF2 components to Laminas --- .../CacheInvalidate/Model/PurgeCache.php | 2 +- .../CacheInvalidate/Model/SocketFactory.php | 7 + .../Captcha/Model/ResourceModel/Log.php | 2 +- .../Product/Frontend/Action/Synchronize.php | 8 +- .../Model/Cart/RequestQuantityProcessor.php | 3 + .../Patch/Data/AddDownloadableHostsConfig.php | 13 +- .../Test/Unit/Model/Oauth/ConsumerTest.php | 4 +- .../App/FrontController/BuiltinPlugin.php | 2 + .../Test/Unit/Model/Cache/ServerTest.php | 3 + .../Quote/Model/Quote/Item/Updater.php | 3 +- .../Controller/Adminhtml/Feed/IndexTest.php | 3 - .../Test/Unit/Controller/Feed/IndexTest.php | 4 +- .../Ui/Controller/Adminhtml/Index/Render.php | 8 +- .../Magento/Ui/Controller/Index/Render.php | 7 +- .../Webapi/Model/Config/ClassReflector.php | 30 ++-- app/code/Magento/Webapi/Model/Soap/Wsdl.php | 2 +- .../Model/Soap/Wsdl/ComplexTypeStrategy.php | 39 +++-- app/etc/di.xml | 1 + composer.json | 2 +- .../Authentication/OauthHelper.php | 18 ++- .../Magento/TestFramework/Request.php | 2 + .../Controller/Cards/DeleteActionTest.php | 7 +- .../SourceClassWithNamespace.php | 2 + .../GraphQl/Config/GraphQlReaderTest.php | 2 + .../Controller/Subscriber/NewActionTest.php | 2 +- .../Magento/Phpserver/PhpserverTest.php | 1 + .../Formatters/FilteredErrorFormatter.php | 1 + .../Integrity/Library/Injectable.php | 3 + .../Test/Integrity/Library/InjectableTest.php | 10 +- .../Api/ExtensibleInterfacesTest.php | 3 +- .../Magento/Test/Integrity/PublicCodeTest.php | 1 + lib/internal/Magento/Framework/App/Feed.php | 2 +- .../Magento/Framework/App/Request/Http.php | 3 +- .../Code/Generator/ClassGenerator.php | 14 +- .../Code/Generator/EntityAbstract.php | 22 ++- .../Generator/InterfaceMethodGenerator.php | 4 +- .../Framework/Code/Reader/ArgumentsReader.php | 3 + .../Framework/DB/Adapter/Pdo/Mysql.php | 2 + .../Data/Test/Unit/Form/FormKeyTest.php | 7 +- .../Magento/Framework/Filesystem/Glob.php | 7 +- .../Magento/Framework/Mail/EmailMessage.php | 4 +- .../Magento/Framework/Mail/Message.php | 2 +- .../Magento/Framework/Mail/MimeMessage.php | 2 +- .../Magento/Framework/Mail/Transport.php | 3 + .../Code/Generator/RemoteServiceGenerator.php | 10 +- .../Framework/Oauth/Helper/Request.php | 5 +- .../Code/Generator/Repository.php | 5 +- .../ExtensionAttributesProcessor.php | 6 + .../Framework/Reflection/MethodsMap.php | 7 + .../Framework/Reflection/NameFinder.php | 3 + .../Validator/CookieDomainValidator.php | 5 +- .../Magento/Framework/Stdlib/Parameters.php | 2 +- .../Magento/Framework/Url/Validator.php | 17 +-- .../Framework/Validator/AllowedProtocols.php | 10 +- setup/config/module.config.php | 2 +- setup/src/Magento/Setup/Application.php | 4 +- .../Magento/Setup/Controller/AddDatabase.php | 5 + .../Setup/Controller/BackupActionItems.php | 58 +++++--- .../Setup/Controller/CompleteBackup.php | 9 +- .../Setup/Controller/CreateAdminAccount.php | 5 + .../Magento/Setup/Controller/CreateBackup.php | 5 + .../Setup/Controller/CustomizeYourStore.php | 8 +- .../Setup/Controller/DatabaseCheck.php | 2 +- .../Setup/Controller/DependencyCheck.php | 15 +- .../Magento/Setup/Controller/Environment.php | 5 + setup/src/Magento/Setup/Controller/Home.php | 2 + setup/src/Magento/Setup/Controller/Index.php | 2 + .../src/Magento/Setup/Controller/Install.php | 20 +-- .../src/Magento/Setup/Controller/License.php | 7 +- .../Magento/Setup/Controller/Maintenance.php | 8 +- .../Magento/Setup/Controller/Marketplace.php | 13 +- .../Controller/MarketplaceCredentials.php | 7 +- .../src/Magento/Setup/Controller/Modules.php | 3 + .../Magento/Setup/Controller/Navigation.php | 17 ++- .../Controller/ReadinessCheckInstaller.php | 11 +- .../Controller/ReadinessCheckUpdater.php | 11 +- .../Setup/Controller/SelectVersion.php | 7 +- .../src/Magento/Setup/Controller/Session.php | 3 + .../src/Magento/Setup/Controller/Success.php | 6 + .../Magento/Setup/Controller/SystemConfig.php | 8 +- .../Setup/Controller/UpdaterSuccess.php | 10 +- .../src/Magento/Setup/Controller/UrlCheck.php | 4 + .../Setup/Controller/WebConfiguration.php | 5 + .../Setup/Model/AdminAccountFactory.php | 7 +- .../Model/ConfigOptionsListCollector.php | 4 +- .../Magento/Setup/Model/Cron/JobFactory.php | 14 +- .../Magento/Setup/Model/InstallerFactory.php | 9 +- setup/src/Magento/Setup/Model/Navigation.php | 9 ++ .../src/Magento/Setup/Model/PackagesAuth.php | 15 +- setup/src/Magento/Setup/Module.php | 21 ++- .../Setup/Module/ConnectionFactory.php | 3 +- .../Module/Di/Code/Reader/FileScanner.php | 6 +- .../Magento/Setup/Module/ResourceFactory.php | 20 ++- .../Mvc/View/Http/InjectTemplateListener.php | 5 + .../Unit/Controller/ExtensionGridTest.php | 3 +- .../Test/Unit/Controller/ModuleGridTest.php | 2 +- .../Test/Unit/Controller/StartUpdaterTest.php | 3 +- .../Controller/UpdateExtensionGridTest.php | 2 +- .../Unit/Model/ObjectManagerProviderTest.php | 12 +- .../Mvc/Bootstrap/InitParamListenerTest.php | 134 +++++++++++------- .../LazyControllerAbstractFactory.php | 18 ++- 101 files changed, 599 insertions(+), 290 deletions(-) diff --git a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php index a111414fd3074..aeef8d00f720a 100644 --- a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php +++ b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php @@ -8,7 +8,7 @@ use Magento\Framework\Cache\InvalidateLogger; /** - * Class PurgeCache + * PurgeCache model */ class PurgeCache { diff --git a/app/code/Magento/CacheInvalidate/Model/SocketFactory.php b/app/code/Magento/CacheInvalidate/Model/SocketFactory.php index 25b4228d9de5e..5a2d602308e92 100644 --- a/app/code/Magento/CacheInvalidate/Model/SocketFactory.php +++ b/app/code/Magento/CacheInvalidate/Model/SocketFactory.php @@ -3,11 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CacheInvalidate\Model; +/** + * Factory for the \Laminas\Http\Client\Adapter\Socket + */ class SocketFactory { /** + * Create object + * * @return \Laminas\Http\Client\Adapter\Socket */ public function create() diff --git a/app/code/Magento/Captcha/Model/ResourceModel/Log.php b/app/code/Magento/Captcha/Model/ResourceModel/Log.php index 30c20fdeb3956..83d055da7c26d 100644 --- a/app/code/Magento/Captcha/Model/ResourceModel/Log.php +++ b/app/code/Magento/Captcha/Model/ResourceModel/Log.php @@ -13,7 +13,7 @@ class Log extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { /** - * Type Remote Address + * Remote Address log type */ const TYPE_REMOTE_ADDRESS = 1; diff --git a/app/code/Magento/Catalog/Controller/Product/Frontend/Action/Synchronize.php b/app/code/Magento/Catalog/Controller/Product/Frontend/Action/Synchronize.php index f6896fe6a7a99..6e76d3510103a 100644 --- a/app/code/Magento/Catalog/Controller/Product/Frontend/Action/Synchronize.php +++ b/app/code/Magento/Catalog/Controller/Product/Frontend/Action/Synchronize.php @@ -7,12 +7,13 @@ use Magento\Catalog\Model\Product\ProductFrontendAction\Synchronizer; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\Result\JsonFactory; /** * Synchronizes Product Frontend Actions with database */ -class Synchronize extends \Magento\Framework\App\Action\Action +class Synchronize extends \Magento\Framework\App\Action\Action implements HttpPostActionInterface { /** * @var Context @@ -46,6 +47,8 @@ public function __construct( } /** + * @inheritDoc + * * This is handle for synchronizing between frontend and backend product actions: * - visit product page (recently_viewed) * - compare products (recently_compared) @@ -57,9 +60,6 @@ public function __construct( * 'added_at' => "JS_TIMESTAMP" * ] * ] - * - * - * @inheritdoc */ public function execute() { diff --git a/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php b/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php index d8597eb3640b3..27566ba6805af 100644 --- a/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php +++ b/app/code/Magento/Checkout/Model/Cart/RequestQuantityProcessor.php @@ -9,6 +9,9 @@ use Magento\Framework\Locale\ResolverInterface; +/** + * Cart request quantity processor + */ class RequestQuantityProcessor { /** diff --git a/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php b/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php index 642b1734310ea..b962e3af6e0aa 100644 --- a/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php +++ b/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php @@ -7,21 +7,22 @@ namespace Magento\Downloadable\Setup\Patch\Data; +use Laminas\Uri\Uri as UriHandler; +use Magento\Backend\App\Area\FrontNameResolver; use Magento\Config\Model\Config\Backend\Admin\Custom; +use Magento\Downloadable\Api\DomainManagerInterface as DomainManager; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\Url\ScopeResolverInterface; use Magento\Framework\UrlInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; -use Laminas\Uri\Uri as UriHandler; -use Magento\Framework\Url\ScopeResolverInterface; -use Magento\Downloadable\Api\DomainManagerInterface as DomainManager; -use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Backend\App\Area\FrontNameResolver; /** * Adding base url as allowed downloadable domain. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AddDownloadableHostsConfig implements DataPatchInterface { @@ -79,7 +80,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function apply() { diff --git a/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php b/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php index 13df65f05fcca..29b96f229b4b1 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Oauth/ConsumerTest.php @@ -111,8 +111,8 @@ protected function setUp() ); $this->validDataArray = [ - 'key' => md5(uniqid()), - 'secret' => md5(uniqid()), + 'key' => md5(uniqid()), // phpcs:ignore Magento2.Security.InsecureFunction + 'secret' => md5(uniqid()), // phpcs:ignore Magento2.Security.InsecureFunction 'callback_url' => 'http://example.com/callback', 'rejected_callback_url' => 'http://example.com/rejectedCallback' ]; diff --git a/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php b/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php index 8fcecea2e23d2..7018efa6238b6 100644 --- a/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php +++ b/app/code/Magento/PageCache/Model/App/FrontController/BuiltinPlugin.php @@ -53,6 +53,8 @@ public function __construct( } /** + * Add PageCache functionality to Dispatch method + * * @param \Magento\Framework\App\FrontControllerInterface $subject * @param callable $proceed * @param \Magento\Framework\App\RequestInterface $request diff --git a/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php b/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php index 57c4bb7107b13..553d86abd6546 100644 --- a/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php +++ b/app/code/Magento/PageCache/Test/Unit/Model/Cache/ServerTest.php @@ -23,6 +23,9 @@ class ServerTest extends \PHPUnit\Framework\TestCase /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Framework\UrlInterface */ protected $urlBuilderMock; + /** @var \PHPUnit_Framework_MockObject_MockObject| \Magento\Framework\Cache\InvalidateLogger */ + private $loggerMock; + protected function setUp() { $this->configMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class); diff --git a/app/code/Magento/Quote/Model/Quote/Item/Updater.php b/app/code/Magento/Quote/Model/Quote/Item/Updater.php index 410eb69e96ff5..270d9160161a8 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Updater.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Updater.php @@ -8,12 +8,11 @@ use Magento\Catalog\Model\ProductFactory; use Magento\Framework\Locale\FormatInterface; use Magento\Framework\DataObject\Factory as ObjectFactory; -use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Item; use Laminas\Code\Exception\InvalidArgumentException; /** - * Class Updater + * Quote item updater */ class Updater { diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php index 3964cec18da8d..92348d2b9cb58 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Adminhtml/Feed/IndexTest.php @@ -6,11 +6,8 @@ namespace Magento\Rss\Test\Unit\Controller\Adminhtml\Feed; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Laminas\Feed\Writer\Exception\InvalidArgumentException; /** - * Class IndexTest - * @package Magento\Rss\Controller\Feed * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class IndexTest extends \PHPUnit\Framework\TestCase diff --git a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php index 3afd9f7833bba..51d6b01b48bb5 100644 --- a/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php +++ b/app/code/Magento/Rss/Test/Unit/Controller/Feed/IndexTest.php @@ -6,11 +6,9 @@ namespace Magento\Rss\Test\Unit\Controller\Feed; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Laminas\Feed\Writer\Exception\InvalidArgumentException; /** - * Class IndexTest - * @package Magento\Rss\Controller\Feed + * Test for \Magento\Rss\Controller\Feed\Index */ class IndexTest extends \PHPUnit\Framework\TestCase { diff --git a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php index bbdbcc637a5a0..a1502b0650e2b 100644 --- a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php +++ b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php @@ -13,11 +13,15 @@ use Psr\Log\LoggerInterface; use Magento\Framework\Escaper; use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\ResultInterface; /** * Render a component. * * @SuppressWarnings(PHPMD.AllPurposeAction) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Render extends AbstractAction { @@ -68,7 +72,9 @@ public function __construct( } /** - * @inheritdoc + * Render a component + * + * @return ResponseInterface|Json|ResultInterface|void */ public function execute() { diff --git a/app/code/Magento/Ui/Controller/Index/Render.php b/app/code/Magento/Ui/Controller/Index/Render.php index f74123955ce23..42818686840aa 100644 --- a/app/code/Magento/Ui/Controller/Index/Render.php +++ b/app/code/Magento/Ui/Controller/Index/Render.php @@ -14,6 +14,9 @@ use Magento\Framework\Controller\Result\JsonFactory; use Psr\Log\LoggerInterface; use Magento\Framework\AuthorizationInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\Result\Json; +use Magento\Framework\Controller\ResultInterface; /** * Is responsible for providing ui components information on store front. @@ -87,7 +90,9 @@ public function __construct( } /** - * @inheritdoc + * Provides ui component + * + * @return ResponseInterface|Json|ResultInterface|void */ public function execute() { diff --git a/app/code/Magento/Webapi/Model/Config/ClassReflector.php b/app/code/Magento/Webapi/Model/Config/ClassReflector.php index af34a3852caab..be880a0767bba 100644 --- a/app/code/Magento/Webapi/Model/Config/ClassReflector.php +++ b/app/code/Magento/Webapi/Model/Config/ClassReflector.php @@ -5,24 +5,24 @@ */ namespace Magento\Webapi\Model\Config; +use Laminas\Code\Reflection\ClassReflection; use Laminas\Code\Reflection\MethodReflection; +use Magento\Framework\Reflection\TypeProcessor; /** - * Class reflector. + * Config class reflector */ class ClassReflector { /** - * @var \Magento\Framework\Reflection\TypeProcessor + * @var TypeProcessor */ protected $_typeProcessor; /** - * Construct reflector. - * - * @param \Magento\Framework\Reflection\TypeProcessor $typeProcessor + * @param TypeProcessor $typeProcessor */ - public function __construct(\Magento\Framework\Reflection\TypeProcessor $typeProcessor) + public function __construct(TypeProcessor $typeProcessor) { $this->_typeProcessor = $typeProcessor; } @@ -60,12 +60,13 @@ public function __construct(\Magento\Framework\Reflection\TypeProcessor $typePro * ), * ... * )</pre> + * @throws \ReflectionException */ public function reflectClassMethods($className, $methods) { $data = []; - $classReflection = new \Laminas\Code\Reflection\ClassReflection($className); - /** @var \Laminas\Code\Reflection\MethodReflection $methodReflection */ + $classReflection = new ClassReflection($className); + /** @var MethodReflection $methodReflection */ foreach ($classReflection->getMethods() as $methodReflection) { $methodName = $methodReflection->getName(); if (in_array($methodName, $methods) || array_key_exists($methodName, $methods)) { @@ -78,11 +79,12 @@ public function reflectClassMethods($className, $methods) /** * Retrieve method interface and documentation description. * - * @param \Laminas\Code\Reflection\MethodReflection $method + * @param MethodReflection $method * @return array * @throws \InvalidArgumentException + * @throws \ReflectionException */ - public function extractMethodData(\Laminas\Code\Reflection\MethodReflection $method) + public function extractMethodData(MethodReflection $method) { $methodData = ['documentation' => $this->extractMethodDescription($method), 'interface' => []]; /** @var \Laminas\Code\Reflection\ParameterReflection $parameter */ @@ -116,10 +118,11 @@ public function extractMethodData(\Laminas\Code\Reflection\MethodReflection $met /** * Retrieve method full documentation description. * - * @param \Laminas\Code\Reflection\MethodReflection $method + * @param MethodReflection $method * @return string + * @throws \ReflectionException */ - protected function extractMethodDescription(\Laminas\Code\Reflection\MethodReflection $method) + protected function extractMethodDescription(MethodReflection $method) { $methodReflection = new MethodReflection( $method->getDeclaringClass()->getName(), @@ -141,10 +144,11 @@ protected function extractMethodDescription(\Laminas\Code\Reflection\MethodRefle * * @param string $className * @return string + * @throws \ReflectionException */ public function extractClassDescription($className) { - $classReflection = new \Laminas\Code\Reflection\ClassReflection($className); + $classReflection = new ClassReflection($className); $docBlock = $classReflection->getDocBlock(); if (!$docBlock) { return ''; diff --git a/app/code/Magento/Webapi/Model/Soap/Wsdl.php b/app/code/Magento/Webapi/Model/Soap/Wsdl.php index 870dfec1fc072..14e9990d25c3e 100644 --- a/app/code/Magento/Webapi/Model/Soap/Wsdl.php +++ b/app/code/Magento/Webapi/Model/Soap/Wsdl.php @@ -14,12 +14,12 @@ class Wsdl extends \Laminas\Soap\Wsdl { /** - * Constructor. * Save URI for targetNamespace generation. * * @param string $name * @param string|\Laminas\Uri\Uri $uri * @param ComplexTypeStrategy $strategy + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function __construct($name, $uri, ComplexTypeStrategy $strategy) { diff --git a/app/code/Magento/Webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php b/app/code/Magento/Webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php index 2087f9e5e3d6e..93a0cf9d835c7 100644 --- a/app/code/Magento/Webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php +++ b/app/code/Magento/Webapi/Model/Soap/Wsdl/ComplexTypeStrategy.php @@ -184,7 +184,7 @@ protected function _processArrayParameter($type, $callInfo = []) * Revert required call info data if needed. * * @param bool $isRequired - * @param array &$callInfo + * @param array $callInfo * @return void */ protected function _revertRequiredCallInfo($isRequired, &$callInfo) @@ -236,19 +236,7 @@ public function addAnnotation(\DOMElement $element, $documentation, $default = n $tagValue = $matches[2][$i]; switch ($tagName) { case 'callInfo': - $callInfoRegExp = '/([a-z].+):(returned|requiredInput):(yes|no|always|conditionally)/i'; - if (preg_match($callInfoRegExp, $tagValue)) { - list($callName, $direction, $condition) = explode(':', $tagValue); - $condition = strtolower($condition); - if (preg_match('/allCallsExcept\(([a-zA-Z].+)\)/', $callName, $calls)) { - $callInfo[$direction][$condition] = [ - 'allCallsExcept' => $calls[1], - ]; - } elseif (!isset($callInfo[$direction][$condition]['allCallsExcept'])) { - $this->_overrideCallInfoName($callInfo, $callName); - $callInfo[$direction][$condition]['calls'][] = $callName; - } - } + $this->processCallInfo($callInfo, $tagValue); break; case 'seeLink': $this->_processSeeLink($appInfoNode, $tagValue); @@ -451,4 +439,27 @@ protected function _overrideCallInfoName(&$callInfo, $callName) } } } + + /** + * Process CallInfo data + * + * @param array $callInfo + * @param string $tagValue + */ + private function processCallInfo(array &$callInfo, string $tagValue): void + { + $callInfoRegExp = '/([a-z].+):(returned|requiredInput):(yes|no|always|conditionally)/i'; + if (preg_match($callInfoRegExp, $tagValue)) { + list($callName, $direction, $condition) = explode(':', $tagValue); + $condition = strtolower($condition); + if (preg_match('/allCallsExcept\(([a-zA-Z].+)\)/', $callName, $calls)) { + $callInfo[$direction][$condition] = [ + 'allCallsExcept' => $calls[1], + ]; + } elseif (!isset($callInfo[$direction][$condition]['allCallsExcept'])) { + $this->_overrideCallInfoName($callInfo, $callName); + $callInfo[$direction][$condition]['calls'][] = $callName; + } + } + } } diff --git a/app/etc/di.xml b/app/etc/di.xml index 85d05b6be38fa..b5255e73b6d27 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -285,6 +285,7 @@ <type name="Magento\Framework\App\Request\Http"> <arguments> <argument name="pathInfoProcessor" xsi:type="object">Magento\Backend\App\Request\PathInfoProcessor\Proxy</argument> + <argument name="routeConfig" xsi:type="object">Magento\Framework\App\Route\ConfigInterface\Proxy</argument> </arguments> </type> <type name="Magento\Framework\App\Response\Http"> diff --git a/composer.json b/composer.json index 882fb5f93b0ef..421e29123151b 100644 --- a/composer.json +++ b/composer.json @@ -323,7 +323,7 @@ "Magento\\Framework\\": "lib/internal/Magento/Framework/", "Magento\\Setup\\": "setup/src/Magento/Setup/", "Magento\\": "app/code/Magento/", - "Laminas\\Mvc\\Controller\\": "setup/src/Zend/Mvc/Controller/" + "Zend\\Mvc\\Controller\\": "setup/src/Zend/Mvc/Controller/" }, "psr-0": { "": [ diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php index 2d7fbae640ef1..eb5cedf9e477c 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Authentication/OauthHelper.php @@ -7,12 +7,18 @@ */ namespace Magento\TestFramework\Authentication; +use Magento\Framework\Exception\IntegrationException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Oauth\Exception; use Magento\TestFramework\Authentication\Rest\OauthClient; use Magento\TestFramework\Helper\Bootstrap; use OAuth\Common\Consumer\Credentials; use Laminas\Stdlib\Exception\LogicException; use Magento\Integration\Model\Integration; +/** + * Authentication Oauth helper + */ class OauthHelper { /** @var array */ @@ -20,6 +26,7 @@ class OauthHelper /** * Generate authentication credentials + * * @param string $date consumer creation date * @return array * <pre> @@ -31,6 +38,8 @@ class OauthHelper * 'token' => $token // retrieved token Model * ); * </pre> + * @throws LocalizedException + * @throws Exception */ public static function getConsumerCredentials($date = null) { @@ -69,6 +78,9 @@ public static function getConsumerCredentials($date = null) * 'oauth_client' => $oauthClient // OauthClient instance used to fetch the access token * ); * </pre> + * @throws LocalizedException + * @throws Exception + * @throws \OAuth\Common\Http\Exception\TokenResponseException */ public static function getAccessToken() { @@ -104,7 +116,8 @@ public static function getAccessToken() * 'integration' => $integration // Integration instance associated with access token * ); * </pre> - * @throws LogicException + * @throws LocalizedException + * @throws Exception */ public static function getApiAccessCredentials($resources = null, Integration $integrationModel = null) { @@ -170,7 +183,8 @@ protected static function _rmRecursive($dir, $doSaveRoot = false) * * @param array $resources * @return \Magento\Integration\Model\Integration - * @throws \Laminas\Stdlib\Exception\LogicException + * @throws LogicException + * @throws IntegrationException */ protected static function _createIntegration($resources) { diff --git a/dev/tests/integration/framework/Magento/TestFramework/Request.php b/dev/tests/integration/framework/Magento/TestFramework/Request.php index ede2f5a54bf05..b6afc6e4c4ae3 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Request.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Request.php @@ -9,6 +9,7 @@ /** * HTTP request implementation that is used instead core one for testing + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Request extends \Magento\Framework\App\Request\Http { @@ -21,6 +22,7 @@ class Request extends \Magento\Framework\App\Request\Http /** * Retrieve HTTP HOST. + * * This method is a stub - all parameters are ignored, just static value returned. * * @param bool $trimPort diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Controller/Cards/DeleteActionTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Cards/DeleteActionTest.php index 9257130cea121..325f02bd621c1 100644 --- a/dev/tests/integration/testsuite/Magento/Braintree/Controller/Cards/DeleteActionTest.php +++ b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Cards/DeleteActionTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Braintree\Controller\Cards; use Magento\Customer\Model\Session; @@ -12,7 +13,7 @@ use Laminas\Http\Request; /** - * Class DeleteActionTest + * Test for \Magento\Vault\Controller\Cards\DeleteAction */ class DeleteActionTest extends AbstractController { @@ -26,7 +27,7 @@ public function testExecute() /** @var Session $session */ $session = $this->_objectManager->get(Session::class); $session->setCustomerId($customerId); - + /** @var CustomerTokenManagement $tokenManagement */ $tokenManagement = $this->_objectManager->get(CustomerTokenManagement::class); $tokens = $tokenManagement->getCustomerSessionTokens(); @@ -44,7 +45,7 @@ public function testExecute() ]) ->setMethod(Request::METHOD_POST); $this->dispatch('vault/cards/deleteaction'); - + static::assertTrue($this->getResponse()->isRedirect()); static::assertRedirect(static::stringContains('vault/cards/listaction')); static::assertSessionMessages(static::equalTo(['Stored Payment Method was successfully removed'])); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php index 0bc86f36a6357..d76167a9af6ed 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php @@ -152,6 +152,7 @@ public function public71( */ public function public71Another(?\DateTime $arg1, $arg2 = false): ?string { + // phpstan:ignore } /** @@ -164,5 +165,6 @@ public function public71Another(?\DateTime $arg1, $arg2 = false): ?string */ public function publicWithSelf($arg = false): self { + // phpstan:ignore } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index 1e72485a9859f..d5eb962b7cb91 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -57,10 +57,12 @@ protected function setUp() ['fileResolver' => $fileResolverMock] ); $reader = $this->objectManager->create( + // phpstan:ignore \Magento\Framework\GraphQlSchemaStitching\Reader::class, ['readers' => ['graphql_reader' => $graphQlReader]] ); $data = $this->objectManager->create( + // phpstan:ignore \Magento\Framework\GraphQl\Config\Data ::class, ['reader' => $reader] ); diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php index 0f07d8b31d13b..d64702f80fe61 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Controller/Subscriber/NewActionTest.php @@ -14,7 +14,7 @@ use Magento\Newsletter\Model\ResourceModel\Subscriber as SubscriberResource; use Magento\Newsletter\Model\ResourceModel\Subscriber\CollectionFactory; use Magento\TestFramework\TestCase\AbstractController; -use Zend\Stdlib\Parameters; +use Laminas\Stdlib\Parameters; /** * Class checks subscription behaviour from frontend diff --git a/dev/tests/integration/testsuite/Magento/Phpserver/PhpserverTest.php b/dev/tests/integration/testsuite/Magento/Phpserver/PhpserverTest.php index 237bb0aa118bc..2c0c3e5233d51 100644 --- a/dev/tests/integration/testsuite/Magento/Phpserver/PhpserverTest.php +++ b/dev/tests/integration/testsuite/Magento/Phpserver/PhpserverTest.php @@ -42,6 +42,7 @@ public static function setUpBeforeClass() $baseDir, static::BASE_URL ); + // phpcs:ignore exec($command, $return); static::$serverPid = (int) $return[0]; } diff --git a/dev/tests/static/framework/Magento/PhpStan/Formatters/FilteredErrorFormatter.php b/dev/tests/static/framework/Magento/PhpStan/Formatters/FilteredErrorFormatter.php index b3a4bd9ae0791..3da08f324d761 100644 --- a/dev/tests/static/framework/Magento/PhpStan/Formatters/FilteredErrorFormatter.php +++ b/dev/tests/static/framework/Magento/PhpStan/Formatters/FilteredErrorFormatter.php @@ -61,6 +61,7 @@ public function formatErrors(AnalysisResult $analysisResult, Output $output): in $clearedAnalysisResult = new AnalysisResult( $fileSpecificErrorsWithoutIgnoredErrors, $analysisResult->getNotFileSpecificErrors(), + $analysisResult->getWarnings(), $analysisResult->isDefaultLevelUsed(), $analysisResult->hasInferrablePropertyTypesFromConstructor(), $analysisResult->getProjectConfigFile() diff --git a/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php b/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php index c4b69cc5150ba..524a4f7be3616 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php +++ b/dev/tests/static/framework/Magento/TestFramework/Integrity/Library/Injectable.php @@ -10,6 +10,7 @@ use Laminas\Code\Reflection\ParameterReflection; /** + * Provide dependencies for the file */ class Injectable { @@ -19,6 +20,8 @@ class Injectable protected $dependencies = []; /** + * Get dependencies + * * @param FileReflection $fileReflection * @return \ReflectionException[] * @throws \ReflectionException diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php index 856ba1f80ac68..2ead129e34043 100644 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/Test/Integrity/Library/InjectableTest.php @@ -8,6 +8,7 @@ use Magento\TestFramework\Integrity\Library\Injectable; /** + * Test for Magento\TestFramework\Integrity\Library\Injectable */ class InjectableTest extends \PHPUnit\Framework\TestCase { @@ -106,7 +107,7 @@ public function testGetDependencies() )->method( 'getName' )->will( - $this->returnValue(\Magento\Core\Model\Object::class) + $this->returnValue(\Magento\Framework\DataObject::class) ); $this->parameterReflection->expects( @@ -118,7 +119,7 @@ public function testGetDependencies() ); $this->assertEquals( - [\Magento\Core\Model\Object::class], + [\Magento\Framework\DataObject::class], $this->injectable->getDependencies($this->fileReflection) ); } @@ -133,13 +134,14 @@ public function testGetDependenciesWithException() $this->parameterReflection->expects($this->once())->method('getClass')->will( $this->returnCallback( function () { - throw new \ReflectionException('Class Magento\Core\Model\Object does not exist'); + throw new \ReflectionException('Class Magento\Framework\DataObject does not exist'); } ) ); $this->assertEquals( - [\Magento\Core\Model\Object::class], + + [\Magento\Framework\DataObject::class], $this->injectable->getDependencies($this->fileReflection) ); } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php index 23c72593c9fc8..9076c16981a49 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Api/ExtensibleInterfacesTest.php @@ -155,7 +155,7 @@ private function checkSetExtensionAttributes( /** * Ensure that all classes extended from extensible classes implement getter and setter for extension attributes. */ - public function testExtensibleClassesWithMissingInterface() + public function testExtensibleClassesWithMissingInterface() //phpcs:ignore Generic.Metrics.NestingLevel { $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this); $invoker( @@ -241,6 +241,7 @@ protected function getFiles($dir, $pattern) { $files = glob($dir . '/' . $pattern, GLOB_NOSORT); foreach (glob($dir . '/*', GLOB_ONLYDIR | GLOB_NOSORT) as $newDir) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $files = array_merge($files, $this->getFiles($newDir, $pattern)); } return $files; diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php index 849a57911a77e..a3dc60641795c 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/PublicCodeTest.php @@ -42,6 +42,7 @@ private function getWhitelist(): array ); $whiteListItems = []; foreach (glob($whiteListFiles) as $fileName) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $whiteListItems = array_merge( $whiteListItems, file($fileName, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) diff --git a/lib/internal/Magento/Framework/App/Feed.php b/lib/internal/Magento/Framework/App/Feed.php index c504f463f68dc..d6a38a90bdf17 100644 --- a/lib/internal/Magento/Framework/App/Feed.php +++ b/lib/internal/Magento/Framework/App/Feed.php @@ -29,7 +29,7 @@ public function __construct(array $data) } /** - * {@inheritdoc} + * @inheritDoc */ public function getFormattedContent() : string { diff --git a/lib/internal/Magento/Framework/App/Request/Http.php b/lib/internal/Magento/Framework/App/Request/Http.php index fbcf20b9f4f99..5fc4716f4bbf8 100644 --- a/lib/internal/Magento/Framework/App/Request/Http.php +++ b/lib/internal/Magento/Framework/App/Request/Http.php @@ -8,7 +8,7 @@ use Magento\Framework\App\HttpRequestInterface; use Magento\Framework\App\RequestContentInterface; use Magento\Framework\App\RequestSafetyInterface; -use Magento\Framework\App\Route\ConfigInterface\Proxy as ConfigInterface; +use Magento\Framework\App\Route\ConfigInterface; use Magento\Framework\HTTP\PhpEnvironment\Request; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Stdlib\Cookie\CookieReaderInterface; @@ -16,6 +16,7 @@ /** * Http request + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Http extends Request implements RequestContentInterface, RequestSafetyInterface, HttpRequestInterface { diff --git a/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php b/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php index 397d2c09fbf47..b3521698bf396 100644 --- a/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php +++ b/lib/internal/Magento/Framework/Code/Generator/ClassGenerator.php @@ -3,13 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\Code\Generator; use Laminas\Code\Generator\MethodGenerator; use Laminas\Code\Generator\PropertyGenerator; +/** + * Class code generator + */ class ClassGenerator extends \Laminas\Code\Generator\ClassGenerator implements - \Magento\Framework\Code\Generator\CodeGeneratorInterface + CodeGeneratorInterface { /** * Possible doc block options @@ -64,6 +68,8 @@ class ClassGenerator extends \Laminas\Code\Generator\ClassGenerator implements ]; /** + * Set data to object + * * @param object $object * @param array $data * @param array $map @@ -94,7 +100,7 @@ public function setClassDocBlock(array $docBlock) } /** - * addMethods() + * Add methods * * @param array $methods * @return $this @@ -157,7 +163,7 @@ public function addMethodFromGenerator(MethodGenerator $method) } /** - * addProperties() + * Add properties * * @param array $properties * @return $this @@ -212,6 +218,8 @@ protected function createMethodGenerator() } /** + * Get namespace name + * * @return string|null */ public function getNamespaceName() diff --git a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php index 78360b9b7b1ad..f29474f476b45 100644 --- a/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php +++ b/lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php @@ -7,10 +7,13 @@ use Laminas\Code\Generator\ValueGenerator; +/** + * Abstract entity + */ abstract class EntityAbstract { /** - * Entity type + * Entity type abstract */ const ENTITY_TYPE = 'abstract'; @@ -183,7 +186,6 @@ protected function _getDefaultResultClassName($modelClassName) */ protected function _getClassProperties() { - // protected $_objectManager = null; $objectManager = [ 'name' => '_objectManager', 'visibility' => 'protected', @@ -238,6 +240,8 @@ protected function _addError($message) } /** + * Validate data + * * @return bool */ protected function _validateData() @@ -263,6 +267,8 @@ protected function _validateData() } /** + * Get class DocBlock + * * @return array */ protected function _getClassDocBlock() @@ -272,6 +278,8 @@ protected function _getClassDocBlock() } /** + * Get generated code + * * @return string */ protected function _getGeneratedCode() @@ -281,6 +289,8 @@ protected function _getGeneratedCode() } /** + * Fix code style + * * @param string $sourceCode * @return string */ @@ -305,8 +315,9 @@ protected function _getNullDefaultValue() } /** - * @param \ReflectionParameter $parameter + * Extract parameter type * + * @param \ReflectionParameter $parameter * @return null|string */ private function extractParameterType( @@ -336,9 +347,11 @@ private function extractParameterType( } /** - * @param \ReflectionParameter $parameter + * Extract parameter default value * + * @param \ReflectionParameter $parameter * @return null|ValueGenerator + * @throws \ReflectionException */ private function extractParameterDefaultValue( \ReflectionParameter $parameter @@ -362,6 +375,7 @@ private function extractParameterDefaultValue( * * @param \ReflectionParameter $parameter * @return array + * @throws \ReflectionException */ protected function _getMethodParameterInfo(\ReflectionParameter $parameter) { diff --git a/lib/internal/Magento/Framework/Code/Generator/InterfaceMethodGenerator.php b/lib/internal/Magento/Framework/Code/Generator/InterfaceMethodGenerator.php index ddcabdcf85724..173906f0fffcd 100644 --- a/lib/internal/Magento/Framework/Code/Generator/InterfaceMethodGenerator.php +++ b/lib/internal/Magento/Framework/Code/Generator/InterfaceMethodGenerator.php @@ -6,12 +6,12 @@ namespace Magento\Framework\Code\Generator; /** - * Interface method generator. + * Interface method code generator */ class InterfaceMethodGenerator extends \Laminas\Code\Generator\MethodGenerator { /** - * {@inheritdoc} + * @inheritDoc */ public function generate() { diff --git a/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php b/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php index 08f93ec514b55..c9a8cce706af4 100644 --- a/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php +++ b/lib/internal/Magento/Framework/Code/Reader/ArgumentsReader.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Code\Reader; +/** + * The class arguments reader + */ class ArgumentsReader { const NO_DEFAULT_VALUE = 'NO-DEFAULT'; diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index d42715fee9923..ec2731c667ee6 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -1827,6 +1827,7 @@ public function modifyColumnByDdl($tableName, $columnName, $definition, $flushDa */ protected function _getColumnTypeByDdl($column) { + // phpstan:ignore switch ($column['DATA_TYPE']) { case 'bool': return Table::TYPE_BOOLEAN; @@ -2732,6 +2733,7 @@ public function addIndex( } catch (\Exception $e) { if (in_array(strtolower($indexType), ['primary', 'unique'])) { $match = []; + // phpstan:ignore if (preg_match('#SQLSTATE\[23000\]: [^:]+: 1062[^\']+\'([\d-\.]+)\'#', $e->getMessage(), $match)) { $ids = explode('-', $match[1]); $this->_removeDuplicateEntry($tableName, $fields, $ids); diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php index ab1b1e9cc65f5..7c9682ea444d8 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/FormKeyTest.php @@ -7,11 +7,12 @@ namespace Magento\Framework\Data\Test\Unit\Form; use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\Escaper; use Magento\Framework\Math\Random; use Magento\Framework\Session\SessionManager; /** - * Class FormKeyTest + * Test for Magento\Framework\Data\Form\FormKey */ class FormKeyTest extends \PHPUnit\Framework\TestCase { @@ -26,7 +27,7 @@ class FormKeyTest extends \PHPUnit\Framework\TestCase protected $sessionMock; /** - * @var \Laminas\Escaper\Escaper|\PHPUnit_Framework_MockObject_MockObject + * @var Escaper|\PHPUnit_Framework_MockObject_MockObject */ protected $escaperMock; @@ -40,7 +41,7 @@ protected function setUp() $this->mathRandomMock = $this->createMock(\Magento\Framework\Math\Random::class); $methods = ['setData', 'getData']; $this->sessionMock = $this->createPartialMock(\Magento\Framework\Session\SessionManager::class, $methods); - $this->escaperMock = $this->createMock(\Magento\Framework\Escaper::class); + $this->escaperMock = $this->createMock(Escaper::class); $this->escaperMock->expects($this->any())->method('escapeJs')->willReturnArgument(0); $this->formKey = new FormKey( $this->mathRandomMock, diff --git a/lib/internal/Magento/Framework/Filesystem/Glob.php b/lib/internal/Magento/Framework/Filesystem/Glob.php index e7f049dfdf80d..9d8de333e35fa 100644 --- a/lib/internal/Magento/Framework/Filesystem/Glob.php +++ b/lib/internal/Magento/Framework/Filesystem/Glob.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\Filesystem; use Laminas\Stdlib\Glob as LaminasGlob; @@ -16,9 +17,9 @@ class Glob extends LaminasGlob /** * Find pathnames matching a pattern. * - * @param string $pattern - * @param int $flags - * @param bool $forceFallback + * @param string $pattern + * @param int $flags + * @param bool $forceFallback * @return array */ public static function glob($pattern, $flags = 0, $forceFallback = false) diff --git a/lib/internal/Magento/Framework/Mail/EmailMessage.php b/lib/internal/Magento/Framework/Mail/EmailMessage.php index ccb8a77227a27..5083d5475465e 100644 --- a/lib/internal/Magento/Framework/Mail/EmailMessage.php +++ b/lib/internal/Magento/Framework/Mail/EmailMessage.php @@ -13,7 +13,7 @@ use Laminas\Mime\Message as LaminasMimeMessage; /** - * Email message + * Magento Framework Email message */ class EmailMessage extends Message implements EmailMessageInterface { @@ -28,8 +28,6 @@ class EmailMessage extends Message implements EmailMessageInterface private $addressFactory; /** - * EmailMessage constructor - * * @param MimeMessageInterface $body * @param array $to * @param MimeMessageInterfaceFactory $mimeMessageFactory diff --git a/lib/internal/Magento/Framework/Mail/Message.php b/lib/internal/Magento/Framework/Mail/Message.php index 1d572e29fecc1..b140676466e5f 100644 --- a/lib/internal/Magento/Framework/Mail/Message.php +++ b/lib/internal/Magento/Framework/Mail/Message.php @@ -11,7 +11,7 @@ /** * Class Message for email transportation * - * @deprecated + * @deprecated a new message implementation was added * @see \Magento\Framework\Mail\EmailMessage */ class Message implements MailMessageInterface diff --git a/lib/internal/Magento/Framework/Mail/MimeMessage.php b/lib/internal/Magento/Framework/Mail/MimeMessage.php index 78d2a42637ff2..3482fb33bd848 100644 --- a/lib/internal/Magento/Framework/Mail/MimeMessage.php +++ b/lib/internal/Magento/Framework/Mail/MimeMessage.php @@ -10,7 +10,7 @@ use Laminas\Mime\Message as LaminasMimeMessage; /** - * Class MimeMessage + * Magento Framework Mime message */ class MimeMessage implements MimeMessageInterface { diff --git a/lib/internal/Magento/Framework/Mail/Transport.php b/lib/internal/Magento/Framework/Mail/Transport.php index 0be387f22ac08..c1772075baaf3 100644 --- a/lib/internal/Magento/Framework/Mail/Transport.php +++ b/lib/internal/Magento/Framework/Mail/Transport.php @@ -10,6 +10,9 @@ use Laminas\Mail\Message as LaminasMessage; use Laminas\Mail\Transport\Sendmail; +/** + * Mail transport + */ class Transport implements \Magento\Framework\Mail\TransportInterface { /** diff --git a/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php b/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php index 22d93b2cc7dcf..160d2b4fa8d5a 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php +++ b/lib/internal/Magento/Framework/MessageQueue/Code/Generator/RemoteServiceGenerator.php @@ -74,7 +74,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc */ protected function _getDefaultConstructorDefinition() { @@ -97,7 +97,7 @@ protected function _getDefaultConstructorDefinition() } /** - * {@inheritdoc} + * @inheritDoc */ protected function _getClassProperties() { @@ -119,7 +119,7 @@ protected function _getClassProperties() } /** - * {@inheritdoc} + * @inheritDoc */ protected function _getClassMethods() { @@ -166,7 +166,7 @@ protected function _getClassMethods() } /** - * {@inheritdoc} + * @inheritDoc */ protected function _validateData() { @@ -175,7 +175,7 @@ protected function _validateData() } /** - * {@inheritdoc} + * @inheritDoc */ protected function _generateCode() { diff --git a/lib/internal/Magento/Framework/Oauth/Helper/Request.php b/lib/internal/Magento/Framework/Oauth/Helper/Request.php index d5f2fa45e6843..1134275270bc0 100644 --- a/lib/internal/Magento/Framework/Oauth/Helper/Request.php +++ b/lib/internal/Magento/Framework/Oauth/Helper/Request.php @@ -8,6 +8,9 @@ use Magento\Framework\App\RequestInterface; use Laminas\Uri\UriFactory; +/** + * Request helper + */ class Request { /**#@+ @@ -110,7 +113,7 @@ protected function _processRequest($authHeaderValue, $contentTypeHeader, $reques /** * Retrieve protocol parameters from query string * - * @param array &$protocolParams + * @param array $protocolParams * @param array $queryString * @return void */ diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php index aeca85a6e9ae0..b0687d619fac6 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php @@ -23,7 +23,7 @@ class Repository extends \Magento\Framework\Code\Generator\EntityAbstract { /** - * Entity type + * Entity type repository */ const ENTITY_TYPE = 'repository'; @@ -130,6 +130,7 @@ protected function _getSourcePersistorPropertyName() /** * Returns source collection factory property name + * * @return string */ protected function _getSourceCollectionFactoryPropertyName() @@ -620,7 +621,7 @@ protected function _getClassMethods() } /** - * {@inheritdoc} + * @inheritDoc */ protected function _validateData() { diff --git a/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php b/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php index fb22cf6872b9e..8f3e31811ccec 100644 --- a/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/ExtensionAttributesProcessor.php @@ -140,6 +140,8 @@ public function buildOutputDataArray(ExtensionAttributesInterface $dataObject, $ } /** + * Is attribute permissions valid + * * @param string $dataObjectType * @param string $attributeCode * @return bool @@ -158,6 +160,8 @@ private function isAttributePermissionValid($dataObjectType, $attributeCode) } /** + * Get regular type for extension attribute type + * * @param string $name * @return string */ @@ -167,6 +171,8 @@ private function getRegularTypeForExtensionAttributesType($name) } /** + * Get permissions for attribute type + * * @param string $typeName * @param string $attributeCode * @return string[] A list of permissions diff --git a/lib/internal/Magento/Framework/Reflection/MethodsMap.php b/lib/internal/Magento/Framework/Reflection/MethodsMap.php index 57347c62e4244..c4a738ac28caa 100644 --- a/lib/internal/Magento/Framework/Reflection/MethodsMap.php +++ b/lib/internal/Magento/Framework/Reflection/MethodsMap.php @@ -14,6 +14,7 @@ /** * Gathers method metadata information. + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class MethodsMap { @@ -51,6 +52,11 @@ class MethodsMap */ private $serializer; + /** + * @var \Magento\Framework\Api\AttributeTypeResolverInterface + */ + private $attributeTypeResolver; + /** * @param \Magento\Framework\Cache\FrontendInterface $cache * @param TypeProcessor $typeProcessor @@ -99,6 +105,7 @@ public function getMethodReturnType($typeName, $methodName) */ public function getMethodsMap($interfaceName) { + //phpcs:ignore Magento2.Security.InsecureFunction $key = self::SERVICE_INTERFACE_METHODS_CACHE_PREFIX . "-" . md5($interfaceName); if (!isset($this->serviceInterfaceMethodsMap[$key])) { $methodMap = $this->cache->load($key); diff --git a/lib/internal/Magento/Framework/Reflection/NameFinder.php b/lib/internal/Magento/Framework/Reflection/NameFinder.php index 81eb4782c4c98..476aa23e6e841 100644 --- a/lib/internal/Magento/Framework/Reflection/NameFinder.php +++ b/lib/internal/Magento/Framework/Reflection/NameFinder.php @@ -8,6 +8,9 @@ use Laminas\Code\Reflection\ClassReflection; +/** + * Reflection NameFinder + */ class NameFinder { /** diff --git a/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php b/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php index f4092622e0b01..e145484d22a43 100644 --- a/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php +++ b/lib/internal/Magento/Framework/Session/Config/Validator/CookieDomainValidator.php @@ -6,10 +6,13 @@ namespace Magento\Framework\Session\Config\Validator; +/** + * Session cookie domain validator + */ class CookieDomainValidator extends \Magento\Framework\Validator\AbstractValidator { /** - * {@inheritdoc} + * @inheritDoc */ public function isValid($value) { diff --git a/lib/internal/Magento/Framework/Stdlib/Parameters.php b/lib/internal/Magento/Framework/Stdlib/Parameters.php index 98bb7aa945226..580d94ac983fc 100644 --- a/lib/internal/Magento/Framework/Stdlib/Parameters.php +++ b/lib/internal/Magento/Framework/Stdlib/Parameters.php @@ -10,7 +10,7 @@ use Laminas\Stdlib\Parameters as LaminasParameters; /** - * Class Parameters + * Stdlib parameters */ class Parameters { diff --git a/lib/internal/Magento/Framework/Url/Validator.php b/lib/internal/Magento/Framework/Url/Validator.php index c85853bf48fd2..6ce201e511e3e 100644 --- a/lib/internal/Magento/Framework/Url/Validator.php +++ b/lib/internal/Magento/Framework/Url/Validator.php @@ -4,13 +4,13 @@ * See COPYING.txt for license details. */ -/** - * Validate URL - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Framework\Url; +use Laminas\Validator\Uri; + +/** + * URL validator + */ class Validator extends \Zend_Validate_Abstract { /**#@+ @@ -20,14 +20,15 @@ class Validator extends \Zend_Validate_Abstract /**#@-*/ /** - * @var \Laminas\Validator\Uri + * @var Uri */ private $validator; /** - * Object constructor + * @param Uri $validator + * @throws \Zend_Validate_Exception */ - public function __construct(\Laminas\Validator\Uri $validator) + public function __construct(Uri $validator) { // set translated message template $this->setMessage((string)new \Magento\Framework\Phrase("Invalid URL '%value%'."), self::INVALID_URL); diff --git a/lib/internal/Magento/Framework/Validator/AllowedProtocols.php b/lib/internal/Magento/Framework/Validator/AllowedProtocols.php index c1473097d7262..7e6df4655d990 100644 --- a/lib/internal/Magento/Framework/Validator/AllowedProtocols.php +++ b/lib/internal/Magento/Framework/Validator/AllowedProtocols.php @@ -1,19 +1,15 @@ <?php /** - * Protocol validator - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\Validator; use \Laminas\Uri\Uri; /** - * Check is URI starts from allowed protocol - * - * Class AllowedProtocols - * @package Magento\Framework\Validator + * Protocol validator */ class AllowedProtocols extends AbstractValidator { @@ -29,6 +25,7 @@ class AllowedProtocols extends AbstractValidator /** * Constructor. + * * @param array $listOfProtocols */ public function __construct($listOfProtocols = []) @@ -54,6 +51,7 @@ public function isValid($value) if (!$isValid) { $this->_addMessages(["Protocol isn't allowed"]); } + return $isValid; } } diff --git a/setup/config/module.config.php b/setup/config/module.config.php index 4e6b89ee70d91..7816c8e3599f3 100644 --- a/setup/config/module.config.php +++ b/setup/config/module.config.php @@ -37,7 +37,7 @@ ], 'controllers' => [ 'abstract_factories' => [ - \Laminas\Mvc\Controller\LazyControllerAbstractFactory::class, + \Zend\Mvc\Controller\LazyControllerAbstractFactory::class, ], ], ]; diff --git a/setup/src/Magento/Setup/Application.php b/setup/src/Magento/Setup/Application.php index 5a729dc03e97e..5881bfad3b209 100644 --- a/setup/src/Magento/Setup/Application.php +++ b/setup/src/Magento/Setup/Application.php @@ -10,7 +10,9 @@ use Laminas\ServiceManager\ServiceManager; /** - * This class is wrapper on \Laminas\Mvc\Application and allows to do more customization like services loading, which + * This class is wrapper on \Laminas\Mvc\Application + * + * It allows to do more customization like services loading, which * cannot be loaded via configuration. */ class Application diff --git a/setup/src/Magento/Setup/Controller/AddDatabase.php b/setup/src/Magento/Setup/Controller/AddDatabase.php index 7002f8e7f64a4..4d12ea6a945d6 100644 --- a/setup/src/Magento/Setup/Controller/AddDatabase.php +++ b/setup/src/Magento/Setup/Controller/AddDatabase.php @@ -8,9 +8,14 @@ use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * AddDatabase controller + */ class AddDatabase extends AbstractActionController { /** + * Index action + * * @return array|ViewModel */ public function indexAction() diff --git a/setup/src/Magento/Setup/Controller/BackupActionItems.php b/setup/src/Magento/Setup/Controller/BackupActionItems.php index a79d9a566dab0..a9255d3324374 100644 --- a/setup/src/Magento/Setup/Controller/BackupActionItems.php +++ b/setup/src/Magento/Setup/Controller/BackupActionItems.php @@ -3,57 +3,65 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Controller; +use Laminas\Http\Response; +use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Backup\Factory; use Magento\Framework\Backup\Filesystem; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Setup\BackupRollback; -use Laminas\Json\Json; -use Laminas\Mvc\Controller\AbstractActionController; -use Laminas\View\Model\JsonModel; +use Magento\Setup\Model\ObjectManagerProvider; +use Magento\Setup\Model\WebLogger; +/** + * BackupActionItems controller + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class BackupActionItems extends AbstractActionController { - /** * Handler for BackupRollback * - * @var \Magento\Framework\Setup\BackupRollback + * @var BackupRollback */ private $backupHandler; /** * Filesystem * - * @var \Magento\Framework\Backup\Filesystem + * @var Filesystem */ private $fileSystem; /** * Filesystem Directory List * - * @var \Magento\Framework\App\Filesystem\DirectoryList + * @var DirectoryList */ private $directoryList; /** - * Constructor - * - * @param \Magento\Setup\Model\ObjectManagerProvider $objectManagerProvider - * @param \Magento\Setup\Model\WebLogger $logger - * @param \Magento\Framework\App\Filesystem\DirectoryList $directoryList - * @param \Magento\Framework\Backup\Filesystem $fileSystem + * @param ObjectManagerProvider $objectManagerProvider + * @param WebLogger $logger + * @param DirectoryList $directoryList + * @param Filesystem $fileSystem + * @throws \Magento\Setup\Exception */ public function __construct( - \Magento\Setup\Model\ObjectManagerProvider $objectManagerProvider, - \Magento\Setup\Model\WebLogger $logger, - \Magento\Framework\App\Filesystem\DirectoryList $directoryList, - \Magento\Framework\Backup\Filesystem $fileSystem + ObjectManagerProvider $objectManagerProvider, + WebLogger $logger, + DirectoryList $directoryList, + Filesystem $fileSystem ) { $objectManager = $objectManagerProvider->get(); $this->backupHandler = $objectManager->create( - \Magento\Framework\Setup\BackupRollback::class, + BackupRollback::class, ['log' => $logger] ); $this->directoryList = $directoryList; @@ -63,20 +71,22 @@ public function __construct( /** * No index action, return 404 error page * - * @return \Laminas\View\Model\ViewModel + * @return ViewModel */ public function indexAction() { - $view = new \Laminas\View\Model\ViewModel; + $view = new ViewModel(); $view->setTemplate('/error/404.phtml'); - $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_404); + $this->getResponse()->setStatusCode(Response::STATUS_CODE_404); + return $view; } /** * Checks disk space availability * - * @return \Laminas\View\Model\JsonModel + * @return JsonModel + * @throws FileSystemException */ public function checkAction() { @@ -95,6 +105,7 @@ public function checkAction() $totalSize += $this->backupHandler->getDBDiskSpace(); } $this->fileSystem->validateAvailableDiscSpace($backupDir, $totalSize); + return new JsonModel( [ 'responseType' => ResponseTypeInterface::RESPONSE_TYPE_SUCCESS, @@ -114,7 +125,7 @@ public function checkAction() /** * Takes backup for code, media or DB * - * @return \Laminas\View\Model\JsonModel + * @return JsonModel */ public function createAction() { @@ -131,6 +142,7 @@ public function createAction() if (isset($params['options']['db']) && $params['options']['db']) { $backupFiles[] = $this->backupHandler->dbBackup($time); } + return new JsonModel( [ 'responseType' => ResponseTypeInterface::RESPONSE_TYPE_SUCCESS, diff --git a/setup/src/Magento/Setup/Controller/CompleteBackup.php b/setup/src/Magento/Setup/Controller/CompleteBackup.php index 158e0b724fd8f..9f925d405cc41 100644 --- a/setup/src/Magento/Setup/Controller/CompleteBackup.php +++ b/setup/src/Magento/Setup/Controller/CompleteBackup.php @@ -5,14 +5,17 @@ */ namespace Magento\Setup\Controller; -use Magento\Framework\App\MaintenanceMode; use Laminas\Mvc\Controller\AbstractActionController; -use Laminas\View\Model\JsonModel; use Laminas\View\Model\ViewModel; +/** + * CompleteBackup controller + */ class CompleteBackup extends AbstractActionController { /** + * Index action + * * @return array|ViewModel */ public function indexAction() @@ -24,6 +27,8 @@ public function indexAction() } /** + * Progress action + * * @return array|ViewModel */ public function progressAction() diff --git a/setup/src/Magento/Setup/Controller/CreateAdminAccount.php b/setup/src/Magento/Setup/Controller/CreateAdminAccount.php index d06407796ff9b..79c32c5b932e0 100644 --- a/setup/src/Magento/Setup/Controller/CreateAdminAccount.php +++ b/setup/src/Magento/Setup/Controller/CreateAdminAccount.php @@ -8,9 +8,14 @@ use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * CreateAdminAccount controller + */ class CreateAdminAccount extends AbstractActionController { /** + * Index action + * * @return ViewModel */ public function indexAction() diff --git a/setup/src/Magento/Setup/Controller/CreateBackup.php b/setup/src/Magento/Setup/Controller/CreateBackup.php index 97c6f0deef188..42c86c42a5a15 100644 --- a/setup/src/Magento/Setup/Controller/CreateBackup.php +++ b/setup/src/Magento/Setup/Controller/CreateBackup.php @@ -8,9 +8,14 @@ use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * CreateBackup controller + */ class CreateBackup extends AbstractActionController { /** + * Index action + * * @return array|ViewModel */ public function indexAction() diff --git a/setup/src/Magento/Setup/Controller/CustomizeYourStore.php b/setup/src/Magento/Setup/Controller/CustomizeYourStore.php index cc987e8339008..128e4b42e6d5e 100644 --- a/setup/src/Magento/Setup/Controller/CustomizeYourStore.php +++ b/setup/src/Magento/Setup/Controller/CustomizeYourStore.php @@ -5,7 +5,6 @@ */ namespace Magento\Setup\Controller; -use Magento\Framework\Filesystem; use Magento\Framework\Module\FullModuleList; use Magento\Framework\Setup\Lists; use Magento\Setup\Model\ObjectManagerProvider; @@ -13,6 +12,9 @@ use Laminas\View\Model\ViewModel; use Laminas\View\Model\JsonModel; +/** + * CustomizeYourStore controller + */ class CustomizeYourStore extends AbstractActionController { /** @@ -43,7 +45,10 @@ public function __construct(FullModuleList $moduleList, Lists $list, ObjectManag } /** + * Index action + * * @return ViewModel + * @throws \Magento\Setup\Exception */ public function indexAction() { @@ -76,6 +81,7 @@ public function indexAction() */ public function defaultTimeZoneAction() { + // phpcs:ignore Generic.PHP.NoSilencedErrors $defaultTimeZone = trim(@date_default_timezone_get()); if (empty($defaultTimeZone)) { return new JsonModel(['defaultTimeZone' => 'UTC']); diff --git a/setup/src/Magento/Setup/Controller/DatabaseCheck.php b/setup/src/Magento/Setup/Controller/DatabaseCheck.php index cf6c6ae1b4409..f84b6e680ab25 100644 --- a/setup/src/Magento/Setup/Controller/DatabaseCheck.php +++ b/setup/src/Magento/Setup/Controller/DatabaseCheck.php @@ -12,7 +12,7 @@ use Laminas\View\Model\JsonModel; /** - * Class DatabaseCheck + * DatabaseCheck controller */ class DatabaseCheck extends AbstractActionController { diff --git a/setup/src/Magento/Setup/Controller/DependencyCheck.php b/setup/src/Magento/Setup/Controller/DependencyCheck.php index 44f205ace3925..49c2b661a8681 100644 --- a/setup/src/Magento/Setup/Controller/DependencyCheck.php +++ b/setup/src/Magento/Setup/Controller/DependencyCheck.php @@ -6,7 +6,9 @@ namespace Magento\Setup\Controller; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Module\Status; +use Magento\Framework\Phrase; use Magento\Setup\Model\DependencyReadinessCheck; use Magento\Setup\Model\ModuleStatusFactory; use Magento\Setup\Model\UninstallDependencyCheck; @@ -15,9 +17,7 @@ use Laminas\View\Model\JsonModel; /** - * Class DependencyCheck - * - * Checks dependencies. + * DependencyCheck controller */ class DependencyCheck extends AbstractActionController { @@ -43,8 +43,6 @@ class DependencyCheck extends AbstractActionController protected $moduleStatus; /** - * Constructor - * * @param DependencyReadinessCheck $dependencyReadinessCheck * @param UninstallDependencyCheck $uninstallDependencyCheck * @param ModuleStatusFactory $moduleStatusFactory @@ -63,6 +61,7 @@ public function __construct( * Verifies component dependency * * @return JsonModel + * @throws \Exception */ public function componentDependencyAction() { @@ -119,11 +118,11 @@ public function enableDisableDependencyCheckAction() try { if (empty($data['packages'])) { - throw new \Exception('No packages have been found.'); + throw new LocalizedException(new Phrase('No packages have been found.')); } if (empty($data['type'])) { - throw new \Exception('Can not determine the flow.'); + throw new LocalizedException(new Phrase('Can not determine the flow.')); } $modules = $data['packages']; @@ -133,7 +132,7 @@ public function enableDisableDependencyCheckAction() $modulesToChange = []; foreach ($modules as $module) { if (!isset($module['name'])) { - throw new \Exception('Can not find module name.'); + throw new LocalizedException(new Phrase('Can not find module name.')); } $modulesToChange[] = $module['name']; } diff --git a/setup/src/Magento/Setup/Controller/Environment.php b/setup/src/Magento/Setup/Controller/Environment.php index 2a330bd1453a8..063d23f5a48d6 100644 --- a/setup/src/Magento/Setup/Controller/Environment.php +++ b/setup/src/Magento/Setup/Controller/Environment.php @@ -43,6 +43,11 @@ class Environment extends AbstractActionController */ protected $phpReadinessCheck; + /** + * @var \Magento\Framework\Setup\FilePermissions + */ + private $permissions; + /** * Constructor * diff --git a/setup/src/Magento/Setup/Controller/Home.php b/setup/src/Magento/Setup/Controller/Home.php index a9b45af731b81..f06cfb89cb1d3 100644 --- a/setup/src/Magento/Setup/Controller/Home.php +++ b/setup/src/Magento/Setup/Controller/Home.php @@ -15,6 +15,8 @@ class Home extends AbstractActionController { /** + * Index action + * * @return ViewModel|\Laminas\Http\Response */ public function indexAction() diff --git a/setup/src/Magento/Setup/Controller/Index.php b/setup/src/Magento/Setup/Controller/Index.php index 347ef5738add3..36dd60dbbcf0f 100644 --- a/setup/src/Magento/Setup/Controller/Index.php +++ b/setup/src/Magento/Setup/Controller/Index.php @@ -15,6 +15,8 @@ class Index extends AbstractActionController { /** + * Index action + * * @return ViewModel|\Laminas\Http\Response */ public function indexAction() diff --git a/setup/src/Magento/Setup/Controller/Install.php b/setup/src/Magento/Setup/Controller/Install.php index a47c0e375500f..f110595a8b872 100644 --- a/setup/src/Magento/Setup/Controller/Install.php +++ b/setup/src/Magento/Setup/Controller/Install.php @@ -6,18 +6,17 @@ namespace Magento\Setup\Controller; +use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; +use Laminas\View\Model\JsonModel; +use Laminas\View\Model\ViewModel; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\ConfigOptionsListConstants as SetupConfigOptionsList; -use Magento\SampleData; use Magento\Setup\Model\Installer; use Magento\Setup\Model\Installer\ProgressFactory; use Magento\Setup\Model\InstallerFactory; use Magento\Setup\Model\RequestDataConverter; use Magento\Setup\Model\WebLogger; -use Laminas\Json\Json; -use Laminas\Mvc\Controller\AbstractActionController; -use Laminas\View\Model\JsonModel; -use Laminas\View\Model\ViewModel; /** * Install controller @@ -57,8 +56,6 @@ class Install extends AbstractActionController private $requestDataConverter; /** - * Default Constructor - * * @param WebLogger $logger * @param InstallerFactory $installerFactory * @param ProgressFactory $progressFactory @@ -83,12 +80,15 @@ public function __construct( } /** + * Index action + * * @return ViewModel */ public function indexAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); + return $view; } @@ -100,7 +100,7 @@ public function indexAction() public function startAction() { $this->log->clear(); - $json = new JsonModel; + $json = new JsonModel(); try { $this->checkForPriorInstall(); $content = $this->getRequest()->getContent(); @@ -121,6 +121,7 @@ public function startAction() $json->setVariable('messages', $e->getMessage()); $json->setVariable('success', false); } + return $json; } @@ -155,6 +156,7 @@ public function progressAction() } catch (\Exception $e) { $contents = [(string)$e]; } + return $json->setVariables(['progress' => $percent, 'success' => $success, 'console' => $contents]); } diff --git a/setup/src/Magento/Setup/Controller/License.php b/setup/src/Magento/Setup/Controller/License.php index 69778a4bca908..9cf32e7b31baf 100644 --- a/setup/src/Magento/Setup/Controller/License.php +++ b/setup/src/Magento/Setup/Controller/License.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Controller; use Magento\Setup\Model\License as LicenseModel; @@ -10,9 +11,7 @@ use Laminas\View\Model\ViewModel; /** - * Class LicenseController - * - * @package Magento\Setup\Controller + * License controller */ class License extends AbstractActionController { @@ -41,7 +40,7 @@ public function __construct(LicenseModel $license) public function indexAction() { $contents = $this->license->getContents(); - $view = new ViewModel; + $view = new ViewModel(); if ($contents === false) { $view->setTemplate('error/404'); $view->setVariable('message', 'Cannot find license file.'); diff --git a/setup/src/Magento/Setup/Controller/Maintenance.php b/setup/src/Magento/Setup/Controller/Maintenance.php index d95b23453e2c9..769f961f7fc0e 100644 --- a/setup/src/Magento/Setup/Controller/Maintenance.php +++ b/setup/src/Magento/Setup/Controller/Maintenance.php @@ -3,13 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Controller; -use Magento\Framework\App\MaintenanceMode; +use Laminas\Json\Json; use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\JsonModel; -use Laminas\Json\Json; +use Magento\Framework\App\MaintenanceMode; +/** + * Maintenance controller + */ class Maintenance extends AbstractActionController { /** diff --git a/setup/src/Magento/Setup/Controller/Marketplace.php b/setup/src/Magento/Setup/Controller/Marketplace.php index 99e935baa9169..7746fa08aac5c 100644 --- a/setup/src/Magento/Setup/Controller/Marketplace.php +++ b/setup/src/Magento/Setup/Controller/Marketplace.php @@ -5,13 +5,16 @@ */ namespace Magento\Setup\Controller; -use Laminas\Mvc\Controller\AbstractActionController; -use Laminas\View\Model\ViewModel; use Laminas\Json\Json; +use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\JsonModel; -use Magento\Setup\Model\PackagesData; +use Laminas\View\Model\ViewModel; use Magento\Setup\Model\PackagesAuth; +use Magento\Setup\Model\PackagesData; +/** + * Marketplace controller + */ class Marketplace extends AbstractActionController { /** @@ -41,7 +44,7 @@ public function __construct(PackagesAuth $packagesAuth, PackagesData $packagesDa */ public function indexAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTemplate('/error/404.phtml'); $this->getResponse()->setStatusCode(\Laminas\Http\Response::STATUS_CODE_404); return $view; @@ -118,6 +121,8 @@ public function removeCredentialsAction() } /** + * Popup Auth action + * * @return array|ViewModel */ public function popupAuthAction() diff --git a/setup/src/Magento/Setup/Controller/MarketplaceCredentials.php b/setup/src/Magento/Setup/Controller/MarketplaceCredentials.php index 189acebc2a5f8..9541b8ef7250f 100644 --- a/setup/src/Magento/Setup/Controller/MarketplaceCredentials.php +++ b/setup/src/Magento/Setup/Controller/MarketplaceCredentials.php @@ -8,14 +8,19 @@ use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * MarketplaceCredentials controller + */ class MarketplaceCredentials extends AbstractActionController { /** + * Index action + * * @return ViewModel */ public function indexAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); return $view; } diff --git a/setup/src/Magento/Setup/Controller/Modules.php b/setup/src/Magento/Setup/Controller/Modules.php index 4264e8f986641..35b225d1e6bba 100644 --- a/setup/src/Magento/Setup/Controller/Modules.php +++ b/setup/src/Magento/Setup/Controller/Modules.php @@ -11,6 +11,9 @@ use Laminas\View\Model\JsonModel; use Laminas\Json\Json; +/** + * Modules controller + */ class Modules extends AbstractActionController { /** diff --git a/setup/src/Magento/Setup/Controller/Navigation.php b/setup/src/Magento/Setup/Controller/Navigation.php index 8d7145ccb9751..c1d42d905b3eb 100644 --- a/setup/src/Magento/Setup/Controller/Navigation.php +++ b/setup/src/Magento/Setup/Controller/Navigation.php @@ -5,15 +5,14 @@ */ namespace Magento\Setup\Controller; -use Magento\Setup\Model\Navigation as NavModel; use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\JsonModel; use Laminas\View\Model\ViewModel; use Magento\Setup\Model\Cron\Status; +use Magento\Setup\Model\Navigation as NavModel; /** - * Class Navigation - * + * Navigation controller */ class Navigation extends AbstractActionController { @@ -40,17 +39,19 @@ public function __construct(NavModel $navigation, Status $status) { $this->navigation = $navigation; $this->status = $status; - $this->view = new ViewModel; + $this->view = new ViewModel(); $this->view->setVariable('menu', $this->navigation->getMenuItems()); $this->view->setVariable('main', $this->navigation->getMainItems()); } /** + * Index action + * * @return JsonModel */ public function indexAction() { - $json = new JsonModel; + $json = new JsonModel(); $json->setVariable('nav', $this->navigation->getData()); $json->setVariable('menu', $this->navigation->getMenuItems()); $json->setVariable('main', $this->navigation->getMainItems()); @@ -59,6 +60,8 @@ public function indexAction() } /** + * Menu action + * * @return array|ViewModel */ public function menuAction() @@ -71,6 +74,8 @@ public function menuAction() } /** + * Side menu action + * * @return array|ViewModel */ public function sideMenuAction() @@ -82,6 +87,8 @@ public function sideMenuAction() } /** + * Head bar action + * * @return array|ViewModel */ public function headerBarAction() diff --git a/setup/src/Magento/Setup/Controller/ReadinessCheckInstaller.php b/setup/src/Magento/Setup/Controller/ReadinessCheckInstaller.php index 26bcb8dd2f34d..e507c645c2d02 100644 --- a/setup/src/Magento/Setup/Controller/ReadinessCheckInstaller.php +++ b/setup/src/Magento/Setup/Controller/ReadinessCheckInstaller.php @@ -8,16 +8,21 @@ use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * ReadinessCheckInstaller controller + */ class ReadinessCheckInstaller extends AbstractActionController { const INSTALLER = 'installer'; /** + * Index action + * * @return array|ViewModel */ public function indexAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); $view->setTemplate('/magento/setup/readiness-check.phtml'); $view->setVariable('actionFrom', self::INSTALLER); @@ -25,11 +30,13 @@ public function indexAction() } /** + * Progress action + * * @return array|ViewModel */ public function progressAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTemplate('/magento/setup/readiness-check/progress.phtml'); $view->setTerminal(true); return $view; diff --git a/setup/src/Magento/Setup/Controller/ReadinessCheckUpdater.php b/setup/src/Magento/Setup/Controller/ReadinessCheckUpdater.php index c272e64a4ef62..59220004d2ed8 100644 --- a/setup/src/Magento/Setup/Controller/ReadinessCheckUpdater.php +++ b/setup/src/Magento/Setup/Controller/ReadinessCheckUpdater.php @@ -8,16 +8,21 @@ use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * ReadinessCheckUpdater controller + */ class ReadinessCheckUpdater extends AbstractActionController { const UPDATER = 'updater'; /** + * Index action + * * @return array|ViewModel */ public function indexAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); $view->setTemplate('/magento/setup/readiness-check.phtml'); $view->setVariable('actionFrom', self::UPDATER); @@ -25,11 +30,13 @@ public function indexAction() } /** + * Progress action + * * @return array|ViewModel */ public function progressAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTemplate('/magento/setup/readiness-check/progress.phtml'); $view->setTerminal(true); return $view; diff --git a/setup/src/Magento/Setup/Controller/SelectVersion.php b/setup/src/Magento/Setup/Controller/SelectVersion.php index 613a1504d77e6..f22b41a8614b1 100644 --- a/setup/src/Magento/Setup/Controller/SelectVersion.php +++ b/setup/src/Magento/Setup/Controller/SelectVersion.php @@ -6,11 +6,10 @@ namespace Magento\Setup\Controller; -use Magento\Composer\InfoCommand; -use Magento\Setup\Model\SystemPackage; use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\JsonModel; use Laminas\View\Model\ViewModel; +use Magento\Setup\Model\SystemPackage; /** * Controller for selecting version @@ -32,11 +31,13 @@ public function __construct( } /** + * Index action + * * @return ViewModel|\Laminas\Http\Response */ public function indexAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); $view->setTemplate('/magento/setup/select-version.phtml'); return $view; diff --git a/setup/src/Magento/Setup/Controller/Session.php b/setup/src/Magento/Setup/Controller/Session.php index 76f6f2e859abc..fa25924d01a15 100644 --- a/setup/src/Magento/Setup/Controller/Session.php +++ b/setup/src/Magento/Setup/Controller/Session.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Controller; /** @@ -65,6 +66,7 @@ public function prolongAction() $sessionConfig = $objectManager->get(\Magento\Backend\Model\Session\AdminConfig::class); /** @var \Magento\Backend\Model\Url $backendUrl */ $backendUrl = $objectManager->get(\Magento\Backend\Model\Url::class); + // phpcs:ignore Magento2.Functions.DiscouragedFunction $urlPath = parse_url($backendUrl->getBaseUrl(), PHP_URL_PATH); $cookiePath = $urlPath . 'setup'; $sessionConfig->setCookiePath($cookiePath); @@ -80,6 +82,7 @@ public function prolongAction() $session->prolong(); return new \Laminas\View\Model\JsonModel(['success' => true]); } + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock.DetectedCatch } catch (\Exception $e) { } return new \Laminas\View\Model\JsonModel(['success' => false]); diff --git a/setup/src/Magento/Setup/Controller/Success.php b/setup/src/Magento/Setup/Controller/Success.php index 4df88c5891071..c597dd8b1bc0a 100644 --- a/setup/src/Magento/Setup/Controller/Success.php +++ b/setup/src/Magento/Setup/Controller/Success.php @@ -10,6 +10,9 @@ use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * Success controller + */ class Success extends AbstractActionController { /** @@ -33,7 +36,10 @@ public function __construct(ModuleList $moduleList, ObjectManagerProvider $objec } /** + * Index action + * * @return ViewModel + * @throws \Magento\Setup\Exception */ public function indexAction() { diff --git a/setup/src/Magento/Setup/Controller/SystemConfig.php b/setup/src/Magento/Setup/Controller/SystemConfig.php index f189de9fee95a..0067752997e17 100644 --- a/setup/src/Magento/Setup/Controller/SystemConfig.php +++ b/setup/src/Magento/Setup/Controller/SystemConfig.php @@ -3,19 +3,25 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Controller; use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * SystemConfig controller + */ class SystemConfig extends AbstractActionController { /** + * Index action + * * @return ViewModel */ public function indexAction() { - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); return $view; } diff --git a/setup/src/Magento/Setup/Controller/UpdaterSuccess.php b/setup/src/Magento/Setup/Controller/UpdaterSuccess.php index 4a4a5ce7f665f..c12d9a164ac4c 100644 --- a/setup/src/Magento/Setup/Controller/UpdaterSuccess.php +++ b/setup/src/Magento/Setup/Controller/UpdaterSuccess.php @@ -3,12 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Controller; -use Magento\Framework\App\MaintenanceMode; use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +use Magento\Framework\App\MaintenanceMode; +/** + * UpdaterSuccess controller + */ class UpdaterSuccess extends AbstractActionController { /** @@ -27,12 +31,14 @@ public function __construct(MaintenanceMode $maintenanceMode) } /** + * Index action + * * @return ViewModel */ public function indexAction() { $this->maintenanceMode->set(false); - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); return $view; } diff --git a/setup/src/Magento/Setup/Controller/UrlCheck.php b/setup/src/Magento/Setup/Controller/UrlCheck.php index af7d3738be1e2..1dfe838e6ca2c 100644 --- a/setup/src/Magento/Setup/Controller/UrlCheck.php +++ b/setup/src/Magento/Setup/Controller/UrlCheck.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Controller; use Laminas\Mvc\Controller\AbstractActionController; @@ -10,6 +11,9 @@ use Laminas\Json\Json; use Magento\Framework\Validator\Url as UrlValidator; +/** + * UrlCheck controller + */ class UrlCheck extends AbstractActionController { /** diff --git a/setup/src/Magento/Setup/Controller/WebConfiguration.php b/setup/src/Magento/Setup/Controller/WebConfiguration.php index 68800f0f7404c..eebe01a008afb 100644 --- a/setup/src/Magento/Setup/Controller/WebConfiguration.php +++ b/setup/src/Magento/Setup/Controller/WebConfiguration.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Controller; use Magento\Framework\App\SetupInfo; @@ -10,6 +11,9 @@ use Laminas\Mvc\Controller\AbstractActionController; use Laminas\View\Model\ViewModel; +/** + * WebConfiguration controller + */ class WebConfiguration extends AbstractActionController { /** @@ -19,6 +23,7 @@ class WebConfiguration extends AbstractActionController */ public function indexAction() { + // phpcs:ignore Magento2.Security.Superglobal $setupInfo = new SetupInfo($_SERVER); $view = new ViewModel( [ diff --git a/setup/src/Magento/Setup/Model/AdminAccountFactory.php b/setup/src/Magento/Setup/Model/AdminAccountFactory.php index 86687fd28c23e..0811be79f63f6 100644 --- a/setup/src/Magento/Setup/Model/AdminAccountFactory.php +++ b/setup/src/Magento/Setup/Model/AdminAccountFactory.php @@ -6,11 +6,12 @@ namespace Magento\Setup\Model; -use Magento\Setup\Module\Setup; use Laminas\ServiceManager\ServiceLocatorInterface; -use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\DB\Adapter\AdapterInterface; +/** + * Factory for \Magento\Setup\Model\AdminAccount + */ class AdminAccountFactory { /** @@ -27,6 +28,8 @@ public function __construct(ServiceLocatorInterface $serviceLocator) } /** + * Create object + * * @param AdapterInterface $connection * @param array $data * @return AdminAccount diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsListCollector.php b/setup/src/Magento/Setup/Model/ConfigOptionsListCollector.php index 191d37f00a132..a97685920f13a 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsListCollector.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsListCollector.php @@ -76,9 +76,11 @@ public function __construct( /** * Auto discover ConfigOptionsList class and collect them. + * * These classes should reside in <module>/Setup directories. * - * @return \Magento\Framework\Setup\ConfigOptionsListInterface[] + * @return ConfigOptionsListInterface[] + * @throws \Magento\Setup\Exception */ public function collectOptionsLists() { diff --git a/setup/src/Magento/Setup/Model/Cron/JobFactory.php b/setup/src/Magento/Setup/Model/Cron/JobFactory.php index 26cdc7aa3de80..cae149ed38e8f 100644 --- a/setup/src/Magento/Setup/Model/Cron/JobFactory.php +++ b/setup/src/Magento/Setup/Model/Cron/JobFactory.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Model\Cron; use Magento\Backend\Console\Command\CacheDisableCommand; @@ -64,7 +65,9 @@ public function __construct(ServiceLocatorInterface $serviceLocator) public function create($name, array $params = []) { $cronStatus = $this->serviceLocator->get(\Magento\Setup\Model\Cron\Status::class); + // phpcs:ignore Magento2.Functions.DiscouragedFunction $statusStream = fopen($cronStatus->getStatusFilePath(), 'a+'); + // phpcs:ignore Magento2.Functions.DiscouragedFunction $logStream = fopen($cronStatus->getLogFilePath(), 'a+'); $streamOutput = new MultipleStreamOutput([$statusStream, $logStream]); $objectManagerProvider = $this->serviceLocator->get(\Magento\Setup\Model\ObjectManagerProvider::class); @@ -81,7 +84,6 @@ public function create($name, array $params = []) $name, $params ); - break; case self::JOB_DB_ROLLBACK: return new JobDbRollback( $objectManager->get(\Magento\Framework\Setup\BackupRollbackFactory::class), @@ -91,7 +93,6 @@ public function create($name, array $params = []) $name, $params ); - break; case self::JOB_STATIC_REGENERATE: return new JobStaticRegenerate( $objectManagerProvider, @@ -100,7 +101,6 @@ public function create($name, array $params = []) $name, $params ); - break; case self::JOB_COMPONENT_UNINSTALL: $moduleUninstall = new Helper\ModuleUninstall( $this->serviceLocator->get(\Magento\Setup\Model\ModuleUninstaller::class), @@ -123,7 +123,6 @@ public function create($name, array $params = []) $name, $params ); - break; case self::JOB_MODULE_ENABLE: return new JobModule( $this->serviceLocator->get(ModuleEnableCommand::class), @@ -133,7 +132,6 @@ public function create($name, array $params = []) $name, $params ); - break; case self::JOB_MODULE_DISABLE: return new JobModule( $this->serviceLocator->get(ModuleDisableCommand::class), @@ -143,7 +141,6 @@ public function create($name, array $params = []) $name, $params ); - break; case self::JOB_ENABLE_CACHE: return new JobSetCache( $objectManager->get(CacheEnableCommand::class), @@ -153,7 +150,6 @@ public function create($name, array $params = []) $name, $params ); - break; case self::JOB_DISABLE_CACHE: return new JobSetCache( $objectManager->get(CacheDisableCommand::class), @@ -162,7 +158,6 @@ public function create($name, array $params = []) $cronStatus, $name ); - break; case self::JOB_MAINTENANCE_MODE_ENABLE: return new JobSetMaintenanceMode( $this->serviceLocator->get(MaintenanceEnableCommand::class), @@ -172,7 +167,6 @@ public function create($name, array $params = []) $name, $params ); - break; case self::JOB_MAINTENANCE_MODE_DISABLE: return new JobSetMaintenanceMode( $this->serviceLocator->get(MaintenanceDisableCommand::class), @@ -182,10 +176,8 @@ public function create($name, array $params = []) $name, $params ); - break; default: throw new \RuntimeException(sprintf('"%s" job is not supported.', $name)); - break; } } } diff --git a/setup/src/Magento/Setup/Model/InstallerFactory.php b/setup/src/Magento/Setup/Model/InstallerFactory.php index 24634be6beba8..aeb5be93614fb 100644 --- a/setup/src/Magento/Setup/Model/InstallerFactory.php +++ b/setup/src/Magento/Setup/Model/InstallerFactory.php @@ -7,11 +7,13 @@ namespace Magento\Setup\Model; use Laminas\ServiceManager\ServiceLocatorInterface; -use Magento\Setup\Module\ResourceFactory; use Magento\Framework\App\ErrorHandler; use Magento\Framework\Setup\LoggerInterface; +use Magento\Setup\Module\ResourceFactory; /** + * Factory for \Magento\Setup\Model\Installer + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class InstallerFactory @@ -29,8 +31,6 @@ class InstallerFactory private $resourceFactory; /** - * Constructor - * * @param ServiceLocatorInterface $serviceLocator * @param ResourceFactory $resourceFactory */ @@ -50,6 +50,7 @@ public function __construct( * * @param LoggerInterface $log * @return Installer + * @throws \Magento\Setup\Exception */ public function create(LoggerInterface $log) { @@ -83,7 +84,7 @@ public function create(LoggerInterface $log) } /** - * creates Resource Factory + * Create Resource Factory * * @return Resource */ diff --git a/setup/src/Magento/Setup/Model/Navigation.php b/setup/src/Magento/Setup/Model/Navigation.php index 1c5cee2303c2a..3eaa1fad4016e 100644 --- a/setup/src/Magento/Setup/Model/Navigation.php +++ b/setup/src/Magento/Setup/Model/Navigation.php @@ -9,6 +9,9 @@ use Laminas\ServiceManager\ServiceLocatorInterface; use Magento\Framework\App\DeploymentConfig; +/** + * Navigation model + */ class Navigation { /**#@+ @@ -34,6 +37,8 @@ class Navigation /** * @param ServiceLocatorInterface $serviceLocator * @param DeploymentConfig $deploymentConfig + * @throws \Magento\Framework\Exception\FileSystemException + * @throws \Magento\Framework\Exception\RuntimeException */ public function __construct(ServiceLocatorInterface $serviceLocator, DeploymentConfig $deploymentConfig) { @@ -49,6 +54,8 @@ public function __construct(ServiceLocatorInterface $serviceLocator, DeploymentC } /** + * Get type + * * @return string */ public function getType() @@ -57,6 +64,8 @@ public function getType() } /** + * Get data + * * @return array */ public function getData() diff --git a/setup/src/Magento/Setup/Model/PackagesAuth.php b/setup/src/Magento/Setup/Model/PackagesAuth.php index 502952db1aa16..b0363a5363d41 100644 --- a/setup/src/Magento/Setup/Model/PackagesAuth.php +++ b/setup/src/Magento/Setup/Model/PackagesAuth.php @@ -7,7 +7,8 @@ namespace Magento\Setup\Model; use Magento\Framework\App\Filesystem\DirectoryList; -use Laminas\View\Model\JsonModel; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; /** * Class PackagesAuth, checks, saves and removes auth details related to packages. @@ -70,19 +71,23 @@ public function __construct( $this->serviceLocator = $serviceLocator; $this->curlClient = $curl; $this->filesystem = $filesystem; - $this->serializer = $serializer?: \Magento\Framework\App\ObjectManager::getInstance() + $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() ->get(\Magento\Framework\Serialize\Serializer\Json::class); } /** + * Get packages json URL + * * @return string */ private function getPackagesJsonUrl() { - return $this->urlPrefix . $this->getCredentialBaseUrl() . '/packages.json'; + return $this->urlPrefix . $this->getCredentialBaseUrl() . '/packages.json'; } /** + * Get credentials base URL + * * @return string */ public function getCredentialBaseUrl() @@ -92,6 +97,8 @@ public function getCredentialBaseUrl() } /** + * Check credentials + * * @param string $token * @param string $secretKey * @return string @@ -148,7 +155,7 @@ private function getAuthJson() $data = $directory->readFile(self::PATH_TO_AUTH_FILE); return json_decode($data, true); } catch (\Exception $e) { - throw new \Exception('Error in reading Auth file'); + throw new LocalizedException(new Phrase('Error in reading Auth file')); } } return false; diff --git a/setup/src/Magento/Setup/Module.php b/setup/src/Magento/Setup/Module.php index 635d954dcda84..7808ead3808e3 100644 --- a/setup/src/Magento/Setup/Module.php +++ b/setup/src/Magento/Setup/Module.php @@ -6,27 +6,32 @@ namespace Magento\Setup; -use Magento\Framework\App\Response\HeaderProvider\XssProtection; -use Magento\Setup\Mvc\View\Http\InjectTemplateListener; use Laminas\EventManager\EventInterface; +use Laminas\EventManager\EventManager; use Laminas\ModuleManager\Feature\BootstrapListenerInterface; use Laminas\ModuleManager\Feature\ConfigProviderInterface; use Laminas\Mvc\ModuleRouteListener; use Laminas\Mvc\MvcEvent; +use Laminas\Stdlib\DispatchableInterface; +use Magento\Framework\App\Response\HeaderProvider\XssProtection; +use Magento\Setup\Mvc\View\Http\InjectTemplateListener; +/** + * Laminas module declaration + */ class Module implements BootstrapListenerInterface, ConfigProviderInterface { /** - * {@inheritdoc} + * @inheritDoc */ public function onBootstrap(EventInterface $e) { - /** @var \Laminas\Mvc\MvcEvent $e */ + /** @var MvcEvent $e */ /** @var \Laminas\Mvc\Application $application */ $application = $e->getApplication(); - /** @var \Laminas\EventManager\EventManager $events */ + /** @var EventManager $events */ $events = $application->getEventManager(); /** @var \Laminas\EventManager\SharedEventManager $sharedEvents */ $sharedEvents = $events->getSharedManager(); @@ -38,7 +43,7 @@ public function onBootstrap(EventInterface $e) // to process templates by Vendor/Module $injectTemplateListener = new InjectTemplateListener(); $sharedEvents->attach( - \Laminas\Stdlib\DispatchableInterface::class, + DispatchableInterface::class, MvcEvent::EVENT_DISPATCH, [$injectTemplateListener, 'injectTemplate'], -89 @@ -63,10 +68,11 @@ public function onBootstrap(EventInterface $e) } /** - * {@inheritdoc} + * @inheritDoc */ public function getConfig() { + // phpcs:disable $result = array_merge_recursive( include __DIR__ . '/../../../config/module.config.php', include __DIR__ . '/../../../config/router.config.php', @@ -82,6 +88,7 @@ public function getConfig() include __DIR__ . '/../../../config/languages.config.php', include __DIR__ . '/../../../config/marketplace.config.php' ); + // phpcs:enable return $result; } } diff --git a/setup/src/Magento/Setup/Module/ConnectionFactory.php b/setup/src/Magento/Setup/Module/ConnectionFactory.php index 5f50d5efdf381..dafab58c49130 100644 --- a/setup/src/Magento/Setup/Module/ConnectionFactory.php +++ b/setup/src/Magento/Setup/Module/ConnectionFactory.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Module; use Magento\Framework\Model\ResourceModel\Type\Db\Pdo\Mysql; @@ -31,7 +32,7 @@ public function __construct(ServiceLocatorInterface $serviceLocator) } /** - * {@inheritdoc} + * @inheritDoc */ public function create(array $connectionConfig) { diff --git a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php index 8c80f339a3a70..27ac2b2794bb9 100644 --- a/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php +++ b/setup/src/Magento/Setup/Module/Di/Code/Reader/FileScanner.php @@ -7,6 +7,8 @@ namespace Magento\Setup\Module\Di\Code\Reader; /** + * FileScanner code reader + * * @SuppressWarnings(PHPMD) */ class FileScanner extends \Laminas\Code\Scanner\FileScanner @@ -17,7 +19,7 @@ class FileScanner extends \Laminas\Code\Scanner\FileScanner private $tokenType; /** - * {@inheritdoc} + * @inheritDoc */ protected function scan() { @@ -106,6 +108,7 @@ protected function scan() return $infoIndex; }; + // phpcs:disable /** * START FINITE STATE MACHINE FOR SCANNING TOKENS */ @@ -357,5 +360,6 @@ protected function scan() * END FINITE STATE MACHINE FOR SCANNING TOKENS */ $this->isScanned = true; + // phpcs:enable } } diff --git a/setup/src/Magento/Setup/Module/ResourceFactory.php b/setup/src/Magento/Setup/Module/ResourceFactory.php index 947afda59dc34..0948542f29e0e 100644 --- a/setup/src/Magento/Setup/Module/ResourceFactory.php +++ b/setup/src/Magento/Setup/Module/ResourceFactory.php @@ -3,12 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Module; +use Laminas\ServiceManager\ServiceLocatorInterface; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\ResourceConnection; use Magento\Setup\Module\Setup\ResourceConfig; -use Laminas\ServiceManager\ServiceLocatorInterface; +/** + * Factory for Magento\Framework\App\ResourceConnection + */ class ResourceFactory { /** @@ -19,8 +24,6 @@ class ResourceFactory protected $serviceLocator; /** - * Constructor - * * @param ServiceLocatorInterface $serviceLocator */ public function __construct(ServiceLocatorInterface $serviceLocator) @@ -29,17 +32,20 @@ public function __construct(ServiceLocatorInterface $serviceLocator) } /** - * @param \Magento\Framework\App\DeploymentConfig $deploymentConfig - * @return Resource + * Create object + * + * @param DeploymentConfig $deploymentConfig + * @return ResourceConnection */ - public function create(\Magento\Framework\App\DeploymentConfig $deploymentConfig) + public function create(DeploymentConfig $deploymentConfig) { - $connectionFactory = $this->serviceLocator->get(\Magento\Setup\Module\ConnectionFactory::class); + $connectionFactory = $this->serviceLocator->get(ConnectionFactory::class); $resource = new ResourceConnection( new ResourceConfig(), $connectionFactory, $deploymentConfig ); + return $resource; } } diff --git a/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php b/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php index 1f230ddbaefa5..537acc97ae385 100644 --- a/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php +++ b/setup/src/Magento/Setup/Mvc/View/Http/InjectTemplateListener.php @@ -9,6 +9,9 @@ use Laminas\Mvc\MvcEvent; use Laminas\Mvc\View\Http\InjectTemplateListener as LaminasInjectTemplateListener; +/** + * InjectTemplateListener for HTTP request + */ class InjectTemplateListener extends LaminasInjectTemplateListener { /** @@ -30,6 +33,8 @@ protected function deriveModuleNamespace($controller) } /** + * Get controller sub-namespace + * * @param string $namespace * @return string */ diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/ExtensionGridTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/ExtensionGridTest.php index a97a8d96b34d2..452fa9f404683 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/ExtensionGridTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/ExtensionGridTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Setup\Test\Unit\Controller; use Magento\Setup\Controller\ExtensionGrid; @@ -12,7 +13,7 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject; /** - * Class ExtensionGridTest + * Test for \Magento\Setup\Controller\ExtensionGrid */ class ExtensionGridTest extends \PHPUnit\Framework\TestCase { diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/ModuleGridTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/ModuleGridTest.php index 25bf754676c23..4d4d7e1d1a6ef 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/ModuleGridTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/ModuleGridTest.php @@ -10,7 +10,7 @@ use Magento\Setup\Model\Grid\Module; /** - * Class ModuleGridTest + * Test for \Magento\Setup\Controller\ModuleGrid */ class ModuleGridTest extends \PHPUnit\Framework\TestCase { diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/StartUpdaterTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/StartUpdaterTest.php index 7daa5fc052d5b..412aaa2cfab71 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/StartUpdaterTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/StartUpdaterTest.php @@ -6,11 +6,10 @@ namespace Magento\Setup\Test\Unit\Controller; -use Magento\Setup\Model\Navigation; use Magento\Setup\Controller\StartUpdater; /** - * Class StartUpdaterTest + * Test for \Magento\Setup\Controller\StartUpdater * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class StartUpdaterTest extends \PHPUnit\Framework\TestCase diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/UpdateExtensionGridTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/UpdateExtensionGridTest.php index 8a5286af19a06..e91740c8f558c 100644 --- a/setup/src/Magento/Setup/Test/Unit/Controller/UpdateExtensionGridTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Controller/UpdateExtensionGridTest.php @@ -12,7 +12,7 @@ use Laminas\View\Model\ViewModel; /** - * Class UpdateExtensionGridTest + * CTest for \Magento\Setup\Controller\UpdateExtensionGrid */ class UpdateExtensionGridTest extends \PHPUnit\Framework\TestCase { diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php index 1081ff3888eed..1c1bc6b36db4d 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php @@ -6,18 +6,18 @@ namespace Magento\Setup\Test\Unit\Model; -use Magento\Setup\Model\ObjectManagerProvider; -use Magento\Setup\Model\Bootstrap; use Laminas\ServiceManager\ServiceLocatorInterface; -use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Magento\Framework\App\ObjectManagerFactory; -use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Console\CommandListInterface; -use Symfony\Component\Console\Command\Command; +use Magento\Framework\ObjectManagerInterface; +use Magento\Setup\Model\Bootstrap; +use Magento\Setup\Model\ObjectManagerProvider; +use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; /** - * Class ObjectManagerProviderTest + * Test for \Magento\Setup\Model\ObjectManagerProvider */ class ObjectManagerProviderTest extends \PHPUnit\Framework\TestCase { diff --git a/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php b/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php index b063186c26fca..0c387f923c631 100644 --- a/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php @@ -3,15 +3,37 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Setup\Test\Unit\Mvc\Bootstrap; -use \Magento\Setup\Mvc\Bootstrap\InitParamListener; +namespace Magento\Setup\Test\Unit\Mvc\Bootstrap; +use Laminas\Console\Request; +use Laminas\EventManager\EventManagerInterface; +use Laminas\EventManager\SharedEventManager; +use Laminas\Http\Headers; +use Laminas\Http\Response; +use Laminas\Mvc\Application; +use Laminas\Mvc\Router\Http\RouteMatch; +use Laminas\ServiceManager\ServiceLocatorInterface; +use Laminas\ServiceManager\ServiceManager; +use Magento\Backend\App\BackendApp; +use Magento\Backend\App\BackendAppList; +use Magento\Backend\Model\Auth; +use Magento\Backend\Model\Auth\Session; +use Magento\Backend\Model\Session\AdminConfig; +use Magento\Backend\Model\Url; +use Magento\Framework\App\Area; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\State; +use Magento\Framework\Filesystem; +use Magento\Framework\ObjectManagerInterface; +use Magento\Setup\Model\ObjectManagerProvider; +use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Magento\Framework\App\Bootstrap as AppBootstrap; use Magento\Framework\App\Filesystem\DirectoryList; use Laminas\Mvc\MvcEvent; /** + * Test for \Magento\Setup\Mvc\Bootstrap\InitParamListener * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class InitParamListenerTest extends \PHPUnit\Framework\TestCase @@ -46,28 +68,30 @@ public function testDetach() public function testOnBootstrap() { - /** @var \Laminas\Mvc\MvcEvent|\PHPUnit_Framework_MockObject_MockObject $mvcEvent */ - $mvcEvent = $this->createMock(\Laminas\Mvc\MvcEvent::class); - $mvcApplication = $this->getMockBuilder(\Laminas\Mvc\Application::class)->disableOriginalConstructor()->getMock(); + /** @var MvcEvent|\PHPUnit_Framework_MockObject_MockObject $mvcEvent */ + $mvcEvent = $this->createMock(MvcEvent::class); + $mvcApplication = $this->getMockBuilder(Application::class) + ->disableOriginalConstructor() + ->getMock(); $mvcEvent->expects($this->once())->method('getApplication')->willReturn($mvcApplication); - $serviceManager = $this->createMock(\Laminas\ServiceManager\ServiceManager::class); + $serviceManager = $this->createMock(ServiceManager::class); $initParams[AppBootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS][DirectoryList::ROOT] = ['path' => '/test']; $serviceManager->expects($this->once())->method('get') ->willReturn($initParams); $serviceManager->expects($this->exactly(2))->method('setService') ->withConsecutive( [ - \Magento\Framework\App\Filesystem\DirectoryList::class, - $this->isInstanceOf(\Magento\Framework\App\Filesystem\DirectoryList::class), + DirectoryList::class, + $this->isInstanceOf(DirectoryList::class), ], [ - \Magento\Framework\Filesystem::class, - $this->isInstanceOf(\Magento\Framework\Filesystem::class), + Filesystem::class, + $this->isInstanceOf(Filesystem::class), ] ); $mvcApplication->expects($this->any())->method('getServiceManager')->willReturn($serviceManager); - $eventManager = $this->getMockForAbstractClass(\Laminas\EventManager\EventManagerInterface::class); + $eventManager = $this->getMockForAbstractClass(EventManagerInterface::class); $mvcApplication->expects($this->any())->method('getEventManager')->willReturn($eventManager); $eventManager->expects($this->any())->method('attach'); @@ -95,10 +119,12 @@ public function testCreateDirectoryListException() public function testCreateServiceNotConsole() { /** - * @var \Laminas\ServiceManager\ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject $serviceLocator + * @var ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject $serviceLocator */ - $serviceLocator = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); - $mvcApplication = $this->getMockBuilder(\Laminas\Mvc\Application::class)->disableOriginalConstructor()->getMock(); + $serviceLocator = $this->createMock(ServiceLocatorInterface::class); + $mvcApplication = $this->getMockBuilder(Application::class) + ->disableOriginalConstructor() + ->getMock(); $request = $this->createMock(\Laminas\Stdlib\RequestInterface::class); $mvcApplication->expects($this->any())->method('getRequest')->willReturn($request); $serviceLocator->expects($this->once())->method('get')->with('Application') @@ -121,11 +147,13 @@ public function testCreateService($zfAppConfig, $env, $cliParam, $expectedArray) } $listener = new InitParamListener(); /** - * @var \Laminas\ServiceManager\ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject $serviceLocator + * @var ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject $serviceLocator */ - $serviceLocator = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); - $mvcApplication = $this->getMockBuilder(\Laminas\Mvc\Application::class)->disableOriginalConstructor()->getMock(); - $request = $this->getMockBuilder(\Laminas\Console\Request::class)->disableOriginalConstructor()->getMock(); + $serviceLocator = $this->createMock(ServiceLocatorInterface::class); + $mvcApplication = $this->getMockBuilder(Application::class) + ->disableOriginalConstructor() + ->getMock(); + $request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock(); $request->expects($this->any()) ->method('getContent') ->willReturn( @@ -209,10 +237,10 @@ public function testCreateFilesystem() $testPath = 'test/path/'; /** - * @var \Magento\Framework\App\Filesystem\DirectoryList| + * @var DirectoryList| * \PHPUnit_Framework_MockObject_MockObject $directoryList */ - $directoryList = $this->getMockBuilder(\Magento\Framework\App\Filesystem\DirectoryList::class) + $directoryList = $this->getMockBuilder(DirectoryList::class) ->disableOriginalConstructor()->getMock(); $directoryList->expects($this->any())->method('getPath')->willReturn($testPath); $filesystem = $this->listener->createFilesystem($directoryList); @@ -228,14 +256,14 @@ public function testCreateFilesystem() */ private function prepareEventManager() { - $this->callbacks[] = [$this->listener, 'onBootstrap']; + $this->callbacks[] = [$this->listener, 'onBootstrap']; - /** @var \Laminas\EventManager\EventManagerInterface|\PHPUnit_Framework_MockObject_MockObject $events */ - $eventManager = $this->createMock(\Laminas\EventManager\EventManagerInterface::class); + /** @var EventManagerInterface|\PHPUnit_Framework_MockObject_MockObject $events */ + $eventManager = $this->createMock(EventManagerInterface::class); - $sharedManager = $this->createMock(\Laminas\EventManager\SharedEventManager::class); + $sharedManager = $this->createMock(SharedEventManager::class); $sharedManager->expects($this->once())->method('attach')->with( - \Laminas\Mvc\Application::class, + Application::class, MvcEvent::EVENT_BOOTSTRAP, [$this->listener, 'onBootstrap'] ); @@ -252,53 +280,53 @@ private function prepareEventManager() public function testAuthPreDispatch() { $cookiePath = 'test'; - $eventMock = $this->getMockBuilder(\Laminas\Mvc\MvcEvent::class) + $eventMock = $this->getMockBuilder(MvcEvent::class) ->disableOriginalConstructor() ->getMock(); - $routeMatchMock = $this->getMockBuilder(\Laminas\Mvc\Router\Http\RouteMatch::class) + $routeMatchMock = $this->getMockBuilder(RouteMatch::class) ->disableOriginalConstructor() ->getMock(); - $applicationMock = $this->getMockBuilder(\Laminas\Mvc\Application::class) + $applicationMock = $this->getMockBuilder(Application::class) ->disableOriginalConstructor() ->getMock(); - $serviceManagerMock = $this->getMockBuilder(\Laminas\ServiceManager\ServiceManager::class) + $serviceManagerMock = $this->getMockBuilder(ServiceManager::class) ->disableOriginalConstructor() ->getMock(); - $deploymentConfigMock = $this->getMockBuilder(\Magento\Framework\App\DeploymentConfig::class) + $deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class) ->disableOriginalConstructor() ->getMock(); $deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(true); - $omProvider = $this->getMockBuilder(\Magento\Setup\Model\ObjectManagerProvider::class) + $omProvider = $this->getMockBuilder(ObjectManagerProvider::class) ->disableOriginalConstructor() ->getMock(); - $objectManagerMock = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); - $adminAppStateMock = $this->getMockBuilder(\Magento\Framework\App\State::class) + $objectManagerMock = $this->getMockForAbstractClass(ObjectManagerInterface::class); + $adminAppStateMock = $this->getMockBuilder(State::class) ->disableOriginalConstructor() ->getMock(); - $sessionConfigMock = $this->getMockBuilder(\Magento\Backend\Model\Session\AdminConfig::class) + $sessionConfigMock = $this->getMockBuilder(AdminConfig::class) ->disableOriginalConstructor() ->getMock(); - $backendAppListMock = $this->getMockBuilder(\Magento\Backend\App\BackendAppList::class) + $backendAppListMock = $this->getMockBuilder(BackendAppList::class) ->disableOriginalConstructor() ->getMock(); - $backendAppMock = $this->getMockBuilder(\Magento\Backend\App\BackendApp::class) + $backendAppMock = $this->getMockBuilder(BackendApp::class) ->disableOriginalConstructor() ->getMock(); - $urlMock = $this->getMockBuilder(\Magento\Backend\Model\Url::class) + $urlMock = $this->getMockBuilder(Url::class) ->disableOriginalConstructor() ->getMock(); - $authenticationMock = $this->getMockBuilder(\Magento\Backend\Model\Auth::class) + $authenticationMock = $this->getMockBuilder(Auth::class) ->disableOriginalConstructor() ->getMock(); - $adminSessionMock = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) + $adminSessionMock = $this->getMockBuilder(Session::class) ->disableOriginalConstructor() ->getMock(); - $responseMock = $this->getMockBuilder(\Laminas\Http\Response::class) + $responseMock = $this->getMockBuilder(Response::class) ->disableOriginalConstructor() ->getMock(); - $headersMock = $this->getMockBuilder(\Laminas\Http\Headers::class) + $headersMock = $this->getMockBuilder(Headers::class) ->disableOriginalConstructor() ->getMock(); @@ -329,12 +357,12 @@ public function testAuthPreDispatch() ->willReturnMap( [ [ - \Magento\Framework\App\DeploymentConfig::class, + DeploymentConfig::class, true, $deploymentConfigMock, ], [ - \Magento\Setup\Model\ObjectManagerProvider::class, + ObjectManagerProvider::class, true, $omProvider, ], @@ -345,19 +373,19 @@ public function testAuthPreDispatch() ->willReturnMap( [ [ - \Magento\Framework\App\State::class, + State::class, $adminAppStateMock, ], [ - \Magento\Backend\Model\Session\AdminConfig::class, + AdminConfig::class, $sessionConfigMock, ], [ - \Magento\Backend\App\BackendAppList::class, + BackendAppList::class, $backendAppListMock, ], [ - \Magento\Backend\Model\Auth::class, + Auth::class, $authenticationMock, ], ] @@ -367,7 +395,7 @@ public function testAuthPreDispatch() ->willReturnMap( [ [ - \Magento\Backend\Model\Auth\Session::class, + Session::class, [ 'sessionConfig' => $sessionConfigMock, 'appState' => $adminAppStateMock @@ -375,7 +403,7 @@ public function testAuthPreDispatch() $adminSessionMock, ], [ - \Magento\Backend\Model\Url::class, + Url::class, [], $urlMock, ], @@ -386,7 +414,7 @@ public function testAuthPreDispatch() ->willReturn($objectManagerMock); $adminAppStateMock->expects($this->once()) ->method('setAreaCode') - ->with(\Magento\Framework\App\Area::AREA_ADMINHTML); + ->with(Area::AREA_ADMINHTML); $applicationMock->expects($this->once()) ->method('getServiceManager') ->willReturn($serviceManagerMock); @@ -433,13 +461,13 @@ public function testAuthPreDispatch() public function testAuthPreDispatchSkip() { - $eventMock = $this->getMockBuilder(\Laminas\Mvc\MvcEvent::class) + $eventMock = $this->getMockBuilder(MvcEvent::class) ->disableOriginalConstructor() ->getMock(); - $routeMatchMock = $this->getMockBuilder(\Laminas\Mvc\Router\Http\RouteMatch::class) + $routeMatchMock = $this->getMockBuilder(RouteMatch::class) ->disableOriginalConstructor() ->getMock(); - $deploymentConfigMock = $this->getMockBuilder(\Magento\Framework\App\DeploymentConfig::class) + $deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class) ->disableOriginalConstructor() ->getMock(); diff --git a/setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php b/setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php index b32f4970db1d8..57697cd9cd9bf 100644 --- a/setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php +++ b/setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php @@ -8,11 +8,9 @@ declare(strict_types=1); -namespace Laminas\Mvc\Controller; +namespace Zend\Mvc\Controller; use Interop\Container\ContainerInterface; -use ReflectionClass; -use ReflectionParameter; use Laminas\Console\Adapter\AdapterInterface as ConsoleAdapterInterface; use Laminas\Filter\FilterPluginManager; use Laminas\Hydrator\HydratorPluginManager; @@ -27,6 +25,8 @@ use Laminas\ServiceManager\ServiceLocatorInterface; use Laminas\Stdlib\DispatchableInterface; use Laminas\Validator\ValidatorPluginManager; +use ReflectionClass; +use ReflectionParameter; /** * Reflection-based factory for controllers. @@ -100,9 +100,10 @@ class LazyControllerAbstractFactory implements AbstractFactoryInterface ]; /** - * {@inheritDoc} + * @inheritDoc * * @return DispatchableInterface + * @throws \ReflectionException */ public function __invoke(ContainerInterface $container, $requestedName, array $options = null) { @@ -127,7 +128,7 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o } /** - * {@inheritDoc} + * @inheritDoc */ public function canCreate(ContainerInterface $container, $requestedName) { @@ -168,7 +169,7 @@ private function resolveParameter(ContainerInterface $container, $requestedName) } if (! $parameter->getClass()) { - return; + return null; } $type = $parameter->getClass()->getName(); @@ -191,8 +192,10 @@ private function resolveParameter(ContainerInterface $container, $requestedName) * Determine if we can create a service with name * * @param ServiceLocatorInterface $serviceLocator + * phpcs:disable * @param $name * @param $requestedName + * phpcs:enable * @return bool * @SuppressWarnings("unused") */ @@ -205,10 +208,13 @@ public function canCreateServiceWithName(ServiceLocatorInterface $serviceLocator * Create service with name * * @param ServiceLocatorInterface $serviceLocator + * phpcs:disable * @param $name * @param $requestedName + * phpcs:enable * @return mixed * @SuppressWarnings("unused") + * @throws \ReflectionException */ public function createServiceWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName) { From a8450f4dd847c8e5af63b591579529943e555f29 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Tue, 10 Mar 2020 10:49:12 +0200 Subject: [PATCH 234/369] `The` instead of `This` in error message --- app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php | 2 +- .../testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php | 2 +- .../testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php | 2 +- .../testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php index 32be4332d30e6..21243a4545fa3 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php @@ -72,7 +72,7 @@ public function execute(string $cartHash, ?int $customerId, int $storeId): Quote } if (false === (bool)$cart->getIsActive()) { - throw new GraphQlNoSuchEntityException(__('This cart isn\'t active.')); + throw new GraphQlNoSuchEntityException(__('The cart isn\'t active.')); } if ((int)$cart->getStoreId() !== $storeId) { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php index ec7ea0bbf5e2a..90ee6caec6797 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php @@ -162,7 +162,7 @@ public function testGetNonExistentCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php * * @expectedException Exception - * @expectedExceptionMessage This cart isn't active. + * @expectedExceptionMessage The cart isn't active. */ public function testGetInactiveCart() { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php index 04f9b25c8d7cd..695857f781b23 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php @@ -108,7 +108,7 @@ public function testMergeGuestWithCustomerCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @expectedException \Exception - * @expectedExceptionMessage This cart isn't active. + * @expectedExceptionMessage The cart isn't active. */ public function testGuestCartExpiryAfterMerge() { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php index 2124af961bd8b..1b54e2f57017f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php @@ -119,7 +119,7 @@ public function testGetNonExistentCart() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php * * @expectedException Exception - * @expectedExceptionMessage This cart isn't active. + * @expectedExceptionMessage The cart isn't active. */ public function testGetInactiveCart() { From 23f98498398e58b755ddffbe65601633402ea0af Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Tue, 10 Mar 2020 11:12:30 +0200 Subject: [PATCH 235/369] improvement --- app/code/Magento/Quote/Plugin/UpdateCartId.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Quote/Plugin/UpdateCartId.php b/app/code/Magento/Quote/Plugin/UpdateCartId.php index 3d5dd21f2e494..46aecd176454f 100644 --- a/app/code/Magento/Quote/Plugin/UpdateCartId.php +++ b/app/code/Magento/Quote/Plugin/UpdateCartId.php @@ -42,7 +42,9 @@ public function beforeSave( GuestCartItemRepositoryInterface $guestCartItemRepository, CartItemInterface $cartItem ): void { - if ($cartId = $this->request->getParam('cartId')) { + $cartId = $this->request->getParam('cartId'); + + if ($cartId) { $cartItem->setQuoteId($cartId); } } From 9ad08cb7e2d38226036345f1381c7fddb03c777c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Mon, 9 Mar 2020 20:50:46 +0100 Subject: [PATCH 236/369] Cleanup ObjectManager usage - Magento_Translation --- .../Magento/Translation/Model/FileManager.php | 51 +++++---- .../Translation/Model/Inline/Parser.php | 99 +++++++++-------- .../Translation/Model/Json/PreProcessor.php | 10 +- .../Model/ResourceModel/StringUtils.php | 77 +++++++------ .../Model/ResourceModel/Translate.php | 73 ++++++------ .../Test/Unit/Model/Inline/ParserTest.php | 105 ++++++++---------- 6 files changed, 210 insertions(+), 205 deletions(-) diff --git a/app/code/Magento/Translation/Model/FileManager.php b/app/code/Magento/Translation/Model/FileManager.php index 387173f6de0ba..95fb3f2a5d4e9 100644 --- a/app/code/Magento/Translation/Model/FileManager.php +++ b/app/code/Magento/Translation/Model/FileManager.php @@ -3,11 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Translation\Model; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\App\ObjectManager; -use Magento\Translation\Model\Inline\File as TranslationFile; +use Magento\Framework\Filesystem\Driver\File; +use Magento\Framework\View\Asset\Repository; /** * A service for handling Translation config files @@ -20,41 +22,33 @@ class FileManager const TRANSLATION_CONFIG_FILE_NAME = 'Magento_Translation/js/i18n-config.js'; /** - * @var \Magento\Framework\View\Asset\Repository + * @var Repository */ private $assetRepo; /** - * @var \Magento\Framework\App\Filesystem\DirectoryList + * @var DirectoryList */ private $directoryList; /** - * @var \Magento\Framework\Filesystem\Driver\File + * @var File */ private $driverFile; /** - * @var TranslationFile - */ - private $translationFile; - - /** - * @param \Magento\Framework\View\Asset\Repository $assetRepo - * @param \Magento\Framework\App\Filesystem\DirectoryList $directoryList - * @param \Magento\Framework\Filesystem\Driver\File $driverFile - * @param TranslationFile $translationFile + * @param Repository $assetRepo + * @param DirectoryList $directoryList + * @param File $driverFile */ public function __construct( - \Magento\Framework\View\Asset\Repository $assetRepo, - \Magento\Framework\App\Filesystem\DirectoryList $directoryList, - \Magento\Framework\Filesystem\Driver\File $driverFile, - \Magento\Translation\Model\Inline\File $translationFile = null + Repository $assetRepo, + DirectoryList $directoryList, + File $driverFile ) { $this->assetRepo = $assetRepo; $this->directoryList = $directoryList; $this->driverFile = $driverFile; - $this->translationFile = $translationFile ?: ObjectManager::getInstance()->get(TranslationFile::class); } /** @@ -71,7 +65,7 @@ public function createTranslateConfigAsset() } /** - * gets current js-translation.json timestamp + * Gets current js-translation.json timestamp * * @return string|void */ @@ -87,18 +81,22 @@ public function getTranslationFileTimestamp() } /** + * Retrieve full path for translation file + * * @return string */ protected function getTranslationFileFullPath() { return $this->directoryList->getPath(DirectoryList::STATIC_VIEW) . - \DIRECTORY_SEPARATOR . - $this->assetRepo->getStaticViewFileContext()->getPath() . - \DIRECTORY_SEPARATOR . - Js\Config::DICTIONARY_FILE_NAME; + \DIRECTORY_SEPARATOR . + $this->assetRepo->getStaticViewFileContext()->getPath() . + \DIRECTORY_SEPARATOR . + Js\Config::DICTIONARY_FILE_NAME; } /** + * Retrieve path for translation file + * * @return string */ public function getTranslationFilePath() @@ -107,7 +105,10 @@ public function getTranslationFilePath() } /** + * Update translation file with content + * * @param string $content + * * @return void */ public function updateTranslationFileContent($content) @@ -115,9 +116,11 @@ public function updateTranslationFileContent($content) $translationDir = $this->directoryList->getPath(DirectoryList::STATIC_VIEW) . \DIRECTORY_SEPARATOR . $this->assetRepo->getStaticViewFileContext()->getPath(); + if (!$this->driverFile->isExists($this->getTranslationFileFullPath())) { $this->driverFile->createDirectory($translationDir); } + $this->driverFile->filePutContents($this->getTranslationFileFullPath(), $content); } diff --git a/app/code/Magento/Translation/Model/Inline/Parser.php b/app/code/Magento/Translation/Model/Inline/Parser.php index ca66d288d6f60..9c0c8dc696c1b 100644 --- a/app/code/Magento/Translation/Model/Inline/Parser.php +++ b/app/code/Magento/Translation/Model/Inline/Parser.php @@ -3,24 +3,27 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Translation\Model\Inline; +use Magento\Backend\App\Area\FrontNameResolver; +use Magento\Framework\Translate\Inline\ParserInterface; +use Magento\Translation\Model\ResourceModel\StringFactory; +use Magento\Translation\Model\ResourceModel\StringUtils; use Magento\Translation\Model\ResourceModel\StringUtilsFactory; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\App\State; use Magento\Framework\App\Cache\TypeListInterface; use Magento\Framework\Translate\InlineInterface; use Magento\Framework\Escaper; -use Magento\Framework\App\ObjectManager; -use Magento\Translation\Model\Inline\CacheManager; /** * Parses content and applies necessary html element wrapping and client scripts for inline translation. * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Parser implements \Magento\Framework\Translate\Inline\ParserInterface +class Parser implements ParserInterface { /** * data-translate html element attribute name @@ -105,7 +108,7 @@ class Parser implements \Magento\Framework\Translate\Inline\ParserInterface ]; /** - * @var \Magento\Translation\Model\ResourceModel\StringFactory + * @var StringFactory */ protected $_resourceFactory; @@ -144,23 +147,6 @@ class Parser implements \Magento\Framework\Translate\Inline\ParserInterface */ private $relatedCacheTypes; - /** - * Return cache manager - * - * @return CacheManager - * - * @deprecated 100.1.0 - */ - private function getCacheManger() - { - if (!$this->cacheManager instanceof CacheManager) { - $this->cacheManager = ObjectManager::getInstance()->get( - CacheManager::class - ); - } - return $this->cacheManager; - } - /** * Initialize base inline translation model * @@ -170,8 +156,9 @@ private function getCacheManger() * @param State $appState * @param TypeListInterface $appCache * @param InlineInterface $translateInline + * @param Escaper $escaper + * @param CacheManager $cacheManager * @param array $relatedCacheTypes - * @param Escaper|null $escaper */ public function __construct( StringUtilsFactory $resource, @@ -180,8 +167,9 @@ public function __construct( State $appState, TypeListInterface $appCache, InlineInterface $translateInline, - array $relatedCacheTypes = [], - Escaper $escaper = null + Escaper $escaper, + CacheManager $cacheManager, + array $relatedCacheTypes = [] ) { $this->_resourceFactory = $resource; $this->_storeManager = $storeManager; @@ -189,16 +177,16 @@ public function __construct( $this->_appState = $appState; $this->_appCache = $appCache; $this->_translateInline = $translateInline; + $this->escaper = $escaper; + $this->cacheManager = $cacheManager; $this->relatedCacheTypes = $relatedCacheTypes; - $this->escaper = $escaper ?? ObjectManager::getInstance()->get( - Escaper::class - ); } /** * Parse and save edited translation * * @param array $translateParams + * * @return array */ public function processAjaxPost(array $translateParams) @@ -206,6 +194,7 @@ public function processAjaxPost(array $translateParams) if (!$this->_translateInline->isAllowed()) { return ['inline' => 'not allowed']; } + if (!empty($this->relatedCacheTypes)) { $this->_appCache->invalidate($this->relatedCacheTypes); } @@ -216,18 +205,16 @@ public function processAjaxPost(array $translateParams) /** @var $validStoreId int */ $validStoreId = $this->_storeManager->getStore()->getId(); - /** @var $resource \Magento\Translation\Model\ResourceModel\StringUtils */ + /** @var $resource StringUtils */ $resource = $this->_resourceFactory->create(); foreach ($translateParams as $param) { - if ($this->_appState->getAreaCode() == \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) { + if ($this->_appState->getAreaCode() == FrontNameResolver::AREA_CODE) { + $storeId = 0; + } elseif (empty($param['perstore'])) { + $resource->deleteTranslate($param['original'], null, false); $storeId = 0; } else { - if (empty($param['perstore'])) { - $resource->deleteTranslate($param['original'], null, false); - $storeId = 0; - } else { - $storeId = $validStoreId; - } + $storeId = $validStoreId; } $resource->saveTranslate( $param['original'], @@ -237,13 +224,14 @@ public function processAjaxPost(array $translateParams) ); } - return $this->getCacheManger()->updateAndGetTranslations(); + return $this->cacheManager->updateAndGetTranslations(); } /** * Validate the structure of translation parameters * * @param array $translateParams + * * @return void * @throws \InvalidArgumentException */ @@ -263,6 +251,7 @@ protected function _validateTranslationParams(array $translateParams) * * @param array $translateParams * @param array $fieldNames Names of fields values of which are to be filtered + * * @return void */ protected function _filterTranslationParams(array &$translateParams, array $fieldNames) @@ -278,6 +267,7 @@ protected function _filterTranslationParams(array &$translateParams, array $fiel * Replace html body with translation wrapping. * * @param string $body + * * @return string */ public function processResponseBodyString($body) @@ -305,6 +295,7 @@ public function getContent() * Sets the body content that is being parsed passed upon the passed in string. * * @param string $content + * * @return void */ public function setContent($content) @@ -316,6 +307,7 @@ public function setContent($content) * Set flag about parsed content is Json * * @param bool $flag + * * @return $this */ public function setIsJson($flag) @@ -329,7 +321,9 @@ public function setIsJson($flag) * * @param array $matches * @param array $options + * * @return string + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _getAttributeLocation($matches, $options) @@ -343,26 +337,25 @@ protected function _getAttributeLocation($matches, $options) * * @param array $matches * @param array $options + * * @return string + * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _getTagLocation($matches, $options) { $tagName = strtolower($options['tagName']); - if (isset($options['tagList'][$tagName])) { - return $options['tagList'][$tagName]; - } - - return ucfirst($tagName) . ' Text'; + return $options['tagList'][$tagName] ?? (ucfirst($tagName) . ' Text'); } /** - * Format translation for special tags. Adding translate mode attribute for vde requests. + * Format translation for special tags. Adding translate mode attribute for vde requests. * * @param string $tagHtml * @param string $tagName * @param array $trArr + * * @return string */ protected function _applySpecialTagsFormat($tagHtml, $tagName, $trArr) @@ -378,6 +371,7 @@ protected function _applySpecialTagsFormat($tagHtml, $tagName, $trArr) $specialTags .= '>' . strtoupper($tagName); } $specialTags .= '</span>'; + return $specialTags; } @@ -387,6 +381,7 @@ protected function _applySpecialTagsFormat($tagHtml, $tagName, $trArr) * @param string $tagHtml * @param string $tagName * @param array $trArr + * * @return string */ protected function _applySimpleTagsFormat($tagHtml, $tagName, $trArr) @@ -404,6 +399,7 @@ protected function _applySimpleTagsFormat($tagHtml, $tagName, $trArr) $simpleTags .= ' ' . $additionalAttr; } $simpleTags .= substr($tagHtml, strlen($tagName) + 1); + return $simpleTags; } @@ -414,6 +410,7 @@ protected function _applySimpleTagsFormat($tagHtml, $tagName, $trArr) * @param string $text * @param callable $locationCallback * @param array $options + * * @return array */ private function _getTranslateData(string $regexp, string &$text, callable $locationCallback, array $options = []) @@ -449,6 +446,7 @@ private function _tagAttributes() * Prepare tags inline translates for the content * * @param string &$content + * * @return void */ private function _prepareTagAttributesForContent(&$content) @@ -494,6 +492,7 @@ private function _prepareTagAttributesForContent(&$content) * * @param string $name * @param string $value + * * @return string */ private function _getHtmlAttribute($name, $value) @@ -505,6 +504,7 @@ private function _getHtmlAttribute($name, $value) * Add data-translate-mode attribute * * @param string $trAttr + * * @return string */ private function _addTranslateAttribute($trAttr) @@ -514,6 +514,7 @@ private function _addTranslateAttribute($trAttr) if ($additionalAttr !== null) { $translateAttr .= ' ' . $additionalAttr . ' '; } + return $translateAttr; } @@ -526,9 +527,9 @@ private function _getHtmlQuote() { if ($this->_isJson) { return '\"'; - } else { - return '"'; } + + return '"'; } /** @@ -560,6 +561,7 @@ function ($tagHtml, $tagName, $trArr) { * @param string $content * @param array $tagsList * @param callable $formatCallback + * * @return void */ private function _translateTags(string &$content, array $tagsList, callable $formatCallback) @@ -642,6 +644,7 @@ private function _translateTags(string &$content, array $tagsList, callable $for * @param string $body * @param string $tagName * @param int $from + * * @return bool|int return false if end of tag is not found */ private function _findEndOfTag($body, $tagName, $from) @@ -658,11 +661,11 @@ private function _findEndOfTag($body, $tagName, $from) } $length = $end - $from + $tagLength + 3; } - if (preg_match('#<\\\\?\/' . $tagName . '\s*?>#i', $body, $tagMatch, null, $end)) { + if (preg_match('#<\\\\?\/' . $tagName . '\s*?>#i', $body, $tagMatch, 0, $end)) { return $end + strlen($tagMatch[0]); - } else { - return false; } + + return false; } /** @@ -700,6 +703,7 @@ private function _otherText() * * @param string $data * @param string $text + * * @return string */ protected function _getDataTranslateSpan($data, $text) @@ -717,6 +721,7 @@ protected function _getDataTranslateSpan($data, $text) * Add an additional html attribute if needed. * * @param mixed $tagName + * * @return string */ protected function _getAdditionalHtmlAttribute($tagName = null) diff --git a/app/code/Magento/Translation/Model/Json/PreProcessor.php b/app/code/Magento/Translation/Model/Json/PreProcessor.php index f19d6a8fc80c4..a5ea52f8df581 100644 --- a/app/code/Magento/Translation/Model/Json/PreProcessor.php +++ b/app/code/Magento/Translation/Model/Json/PreProcessor.php @@ -3,12 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Translation\Model\Json; use Magento\Framework\App\Area; use Magento\Framework\App\AreaList; -use Magento\Framework\App\ObjectManager; use Magento\Framework\TranslateInterface; use Magento\Framework\View\Asset\File\FallbackContext; use Magento\Framework\View\Asset\PreProcessor\Chain; @@ -57,26 +57,27 @@ class PreProcessor implements PreProcessorInterface * @param DataProviderInterface $dataProvider * @param AreaList $areaList * @param TranslateInterface $translate - * @param DesignInterface|null $viewDesign + * @param DesignInterface $viewDesign */ public function __construct( Config $config, DataProviderInterface $dataProvider, AreaList $areaList, TranslateInterface $translate, - DesignInterface $viewDesign = null + DesignInterface $viewDesign ) { $this->config = $config; $this->dataProvider = $dataProvider; $this->areaList = $areaList; $this->translate = $translate; - $this->viewDesign = $viewDesign ?? ObjectManager::getInstance()->get(DesignInterface::class); + $this->viewDesign = $viewDesign; } /** * Transform content and/or content type for the specified preprocessing chain object * * @param Chain $chain + * * @return void */ public function process(Chain $chain) @@ -110,6 +111,7 @@ public function process(Chain $chain) * Is provided path the path to translation dictionary * * @param string $path + * * @return bool */ protected function isDictionaryPath($path) diff --git a/app/code/Magento/Translation/Model/ResourceModel/StringUtils.php b/app/code/Magento/Translation/Model/ResourceModel/StringUtils.php index be7656fbf61a7..4cefdd58609a6 100644 --- a/app/code/Magento/Translation/Model/ResourceModel/StringUtils.php +++ b/app/code/Magento/Translation/Model/ResourceModel/StringUtils.php @@ -3,15 +3,23 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Translation\Model\ResourceModel; +use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\DB\Select; use Magento\Framework\Escaper; -use Magento\Framework\App\ObjectManager; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\ResourceModel\Db\Context; +use Magento\Store\Model\Store; /** * String translation utilities */ -class StringUtils extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +class StringUtils extends AbstractDb { /** * @var Escaper @@ -19,12 +27,12 @@ class StringUtils extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb private $escaper; /** - * @var \Magento\Framework\Locale\ResolverInterface + * @var ResolverInterface */ protected $_localeResolver; /** - * @var \Magento\Framework\App\ScopeResolverInterface + * @var ScopeResolverInterface */ protected $scopeResolver; @@ -34,27 +42,25 @@ class StringUtils extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $scope; /** - * @param \Magento\Framework\Model\ResourceModel\Db\Context $context - * @param \Magento\Framework\Locale\ResolverInterface $localeResolver - * @param \Magento\Framework\App\ScopeResolverInterface $scopeResolver + * @param Context $context + * @param ResolverInterface $localeResolver + * @param ScopeResolverInterface $scopeResolver + * @param Escaper $escaper * @param string $connectionName * @param string|null $scope - * @param Escaper|null $escaper */ public function __construct( - \Magento\Framework\Model\ResourceModel\Db\Context $context, - \Magento\Framework\Locale\ResolverInterface $localeResolver, - \Magento\Framework\App\ScopeResolverInterface $scopeResolver, + Context $context, + ResolverInterface $localeResolver, + ScopeResolverInterface $scopeResolver, + Escaper $escaper, $connectionName = null, - $scope = null, - Escaper $escaper = null + $scope = null ) { $this->_localeResolver = $localeResolver; $this->scopeResolver = $scopeResolver; + $this->escaper = $escaper; $this->scope = $scope; - $this->escaper = $escaper ?? ObjectManager::getInstance()->get( - Escaper::class - ); parent::__construct($context, $connectionName); } @@ -69,14 +75,15 @@ protected function _construct() } /** - * Load + * Load an object * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @param String $value * @param String $field + * * @return array|$this */ - public function load(\Magento\Framework\Model\AbstractModel $object, $value, $field = null) + public function load(AbstractModel $object, $value, $field = null) { if (is_string($value)) { $select = $this->getConnection()->select()->from( @@ -88,9 +95,9 @@ public function load(\Magento\Framework\Model\AbstractModel $object, $value, $fi $object->setData($result); $this->_afterLoad($object); return $result; - } else { - return parent::load($object, $value, $field); } + + return parent::load($object, $value, $field); } /** @@ -98,23 +105,25 @@ public function load(\Magento\Framework\Model\AbstractModel $object, $value, $fi * * @param String $field * @param String $value - * @param \Magento\Framework\Model\AbstractModel $object - * @return \Magento\Framework\DB\Select + * @param AbstractModel $object + * + * @return Select */ protected function _getLoadSelect($field, $value, $object) { $select = parent::_getLoadSelect($field, $value, $object); - $select->where('store_id = ?', \Magento\Store\Model\Store::DEFAULT_STORE_ID); + $select->where('store_id = ?', Store::DEFAULT_STORE_ID); return $select; } /** * After translation loading * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object + * * @return $this */ - public function _afterLoad(\Magento\Framework\Model\AbstractModel $object) + public function _afterLoad(AbstractModel $object) { $connection = $this->getConnection(); $select = $connection->select()->from( @@ -131,10 +140,11 @@ public function _afterLoad(\Magento\Framework\Model\AbstractModel $object) /** * Before save * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object + * * @return $this */ - protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) + protected function _beforeSave(AbstractModel $object) { $connection = $this->getConnection(); $select = $connection->select() @@ -142,7 +152,7 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) ->where('string = :string') ->where('store_id = :store_id'); - $bind = ['string' => $object->getString(), 'store_id' => \Magento\Store\Model\Store::DEFAULT_STORE_ID]; + $bind = ['string' => $object->getString(), 'store_id' => Store::DEFAULT_STORE_ID]; $object->setId($connection->fetchOne($select, $bind)); return parent::_beforeSave($object); @@ -151,10 +161,11 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) /** * After save * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object + * * @return $this */ - protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) + protected function _afterSave(AbstractModel $object) { $connection = $this->getConnection(); $select = $connection->select()->from( @@ -192,6 +203,7 @@ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) * @param string $string * @param string $locale * @param int|null $storeId + * * @return $this */ public function deleteTranslate($string, $locale = null, $storeId = null) @@ -203,7 +215,7 @@ public function deleteTranslate($string, $locale = null, $storeId = null) $where = ['locale = ?' => $locale, 'string = ?' => $string]; if ($storeId === false) { - $where['store_id > ?'] = \Magento\Store\Model\Store::DEFAULT_STORE_ID; + $where['store_id > ?'] = Store::DEFAULT_STORE_ID; } elseif ($storeId !== null) { $where['store_id = ?'] = $storeId; } @@ -220,6 +232,7 @@ public function deleteTranslate($string, $locale = null, $storeId = null) * @param String $translate * @param String $locale * @param int|null $storeId + * * @return $this */ public function saveTranslate($string, $translate, $locale = null, $storeId = null) diff --git a/app/code/Magento/Translation/Model/ResourceModel/Translate.php b/app/code/Magento/Translation/Model/ResourceModel/Translate.php index cf379254d4320..ef14758f9f917 100644 --- a/app/code/Magento/Translation/Model/ResourceModel/Translate.php +++ b/app/code/Magento/Translation/Model/ResourceModel/Translate.php @@ -3,26 +3,29 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Translation\Model\ResourceModel; -use Magento\Framework\App\ObjectManager; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\Config; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\ResourceModel\Db\Context; +use Magento\Framework\Translate\ResourceInterface; use Magento\Translation\App\Config\Type\Translation; -class Translate extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb implements - \Magento\Framework\Translate\ResourceInterface +/** + * Translate data resource model + */ +class Translate extends AbstractDb implements ResourceInterface { /** - * @var \Magento\Framework\App\ScopeResolverInterface + * @var ScopeResolverInterface */ protected $scopeResolver; - /** - * @var null|string - */ - protected $scope; - /** * @var Config */ @@ -34,19 +37,30 @@ class Translate extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb imp private $deployedConfig; /** - * @param \Magento\Framework\Model\ResourceModel\Db\Context $context - * @param \Magento\Framework\App\ScopeResolverInterface $scopeResolver + * @var null|string + */ + protected $scope; + + /** + * @param Context $context + * @param ScopeResolverInterface $scopeResolver * @param string $connectionName * @param null|string $scope + * @param Config|null $appConfig + * @param DeploymentConfig|null $deployedConfig */ public function __construct( - \Magento\Framework\Model\ResourceModel\Db\Context $context, - \Magento\Framework\App\ScopeResolverInterface $scopeResolver, + Context $context, + ScopeResolverInterface $scopeResolver, $connectionName = null, - $scope = null + $scope = null, + ?Config $appConfig = null, + ?DeploymentConfig $deployedConfig = null ) { $this->scopeResolver = $scopeResolver; $this->scope = $scope; + $this->appConfig = $appConfig ?? ObjectManager::getInstance()->get(Config::class); + $this->deployedConfig = $deployedConfig ?? ObjectManager::getInstance()->get(DeploymentConfig::class); parent::__construct($context, $connectionName); } @@ -65,6 +79,7 @@ protected function _construct() * * @param int $storeId * @param string $locale + * * @return array */ public function getTranslationArray($storeId = null, $locale = null) @@ -74,7 +89,7 @@ public function getTranslationArray($storeId = null, $locale = null) } $locale = (string) $locale; - $data = $this->getAppConfig()->get( + $data = $this->appConfig->get( Translation::CONFIG_TYPE, $locale . '/' . $this->getStoreCode($storeId), [] @@ -98,6 +113,7 @@ public function getTranslationArray($storeId = null, $locale = null) * * @param array $strings * @param int|null $storeId + * * @return array */ public function getTranslationArrayByStrings(array $strings, $storeId = null) @@ -141,7 +157,7 @@ public function getMainChecksum() */ public function getConnection() { - if (!$this->getDeployedConfig()->isDbAvailable()) { + if (!$this->deployedConfig->isDbAvailable()) { return false; } return parent::getConnection(); @@ -161,34 +177,11 @@ protected function getStoreId() * Retrieve store code by store id * * @param int $storeId + * * @return string */ private function getStoreCode($storeId) { return $this->scopeResolver->getScope($storeId)->getCode(); } - - /** - * @deprecated 100.1.2 - * @return DeploymentConfig - */ - private function getDeployedConfig() - { - if ($this->deployedConfig === null) { - $this->deployedConfig = ObjectManager::getInstance()->get(DeploymentConfig::class); - } - return $this->deployedConfig; - } - - /** - * @deprecated 100.1.2 - * @return Config - */ - private function getAppConfig() - { - if ($this->appConfig === null) { - $this->appConfig = ObjectManager::getInstance()->get(Config::class); - } - return $this->appConfig; - } } diff --git a/app/code/Magento/Translation/Test/Unit/Model/Inline/ParserTest.php b/app/code/Magento/Translation/Test/Unit/Model/Inline/ParserTest.php index e11618831552f..552f3118748f8 100644 --- a/app/code/Magento/Translation/Test/Unit/Model/Inline/ParserTest.php +++ b/app/code/Magento/Translation/Test/Unit/Model/Inline/ParserTest.php @@ -3,22 +3,27 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Translation\Test\Unit\Model\Inline; -use Magento\Translation\Model\Inline\Parser; +use Magento\Framework\App\Cache\TypeListInterface; +use Magento\Framework\App\State; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\Translate\InlineInterface; -use Magento\Framework\App\Cache\TypeListInterface; -use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Api\Data\StoreInterface; -use Magento\Translation\Model\ResourceModel\StringUtilsFactory; -use Magento\Translation\Model\ResourceModel\StringUtils; +use Magento\Store\Model\StoreManagerInterface; use Magento\Translation\Model\Inline\CacheManager; +use Magento\Translation\Model\Inline\Parser; +use Magento\Translation\Model\ResourceModel\StringUtils; +use Magento\Translation\Model\ResourceModel\StringUtilsFactory; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * Class ParserTest to test \Magento\Translation\Model\Inline\Parser */ -class ParserTest extends \PHPUnit\Framework\TestCase +class ParserTest extends TestCase { /** * @var Parser @@ -31,47 +36,47 @@ class ParserTest extends \PHPUnit\Framework\TestCase private $objectManager; /** - * @var InlineInterface|\PHPUnit_Framework_MockObject_MockObject + * @var InlineInterface|MockObject */ private $translateInlineMock; /** - * @var TypeListInterface|\PHPUnit_Framework_MockObject_MockObject + * @var TypeListInterface|MockObject */ private $appCacheMock; /** - * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|MockObject */ private $storeManagerMock; /** - * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreInterface|MockObject */ private $storeMock; /** - * @var \Zend_Filter_Interface|\PHPUnit_Framework_MockObject_MockObject + * @var \Zend_Filter_Interface|MockObject */ private $inputFilterMock; /** - * @var StringUtilsFactory|\PHPUnit_Framework_MockObject_MockObject + * @var StringUtilsFactory|MockObject */ private $resourceFactoryMock; /** - * @var \Magento\Framework\App\State|\PHPUnit_Framework_MockObject_MockObject + * @var State|MockObject */ private $appStateMock; /** - * @var StringUtils|\PHPUnit_Framework_MockObject_MockObject + * @var StringUtils|MockObject */ private $resourceMock; /** - * @var CacheManager|\PHPUnit_Framework_MockObject_MockObject + * @var CacheManager|MockObject */ private $cacheManagerMock; @@ -79,37 +84,49 @@ protected function setUp() { $this->objectManager = new ObjectManager($this); $this->translateInlineMock = - $this->getMockForAbstractClass(\Magento\Framework\Translate\InlineInterface::class); - $this->appCacheMock = $this->getMockForAbstractClass(\Magento\Framework\App\Cache\TypeListInterface::class); - $this->storeManagerMock = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class); - $this->storeMock = $this->getMockForAbstractClass(\Magento\Store\Api\Data\StoreInterface::class); - $this->storeManagerMock->expects($this->any()) - ->method('getStore') + $this->getMockForAbstractClass(InlineInterface::class); + $this->appCacheMock = $this->getMockForAbstractClass(TypeListInterface::class); + $this->storeManagerMock = $this->getMockForAbstractClass(StoreManagerInterface::class); + $this->storeMock = $this->getMockForAbstractClass(StoreInterface::class); + $this->storeManagerMock->method('getStore') ->willReturn($this->storeMock); $this->resourceFactoryMock = $this->getMockBuilder( - \Magento\Translation\Model\ResourceModel\StringUtilsFactory::class + StringUtilsFactory::class ) ->disableOriginalConstructor() + ->setMethods(['create']) ->getMock(); - $this->resourceMock = $this->getMockBuilder(\Magento\Translation\Model\ResourceModel\StringUtils::class) + $this->resourceMock = $this->getMockBuilder(StringUtils::class) ->disableOriginalConstructor() ->setMethods([]) ->getMock(); - $this->inputFilterMock = $this->getMockBuilder('Zend_Filter_Interface'); + $this->inputFilterMock = $this->getMockForAbstractClass('Zend_Filter_Interface'); - $this->resourceFactoryMock->expects($this->any()) - ->method('create') + $this->resourceFactoryMock->method('create') ->willReturn($this->resourceMock); - $this->cacheManagerMock = $this->getMockBuilder(\Magento\Translation\Model\Inline\CacheManager::class) + $this->cacheManagerMock = $this->getMockBuilder(CacheManager::class) ->disableOriginalConstructor() ->setMethods([]) ->getMock(); - $this->appStateMock = $this->getMockBuilder(\Magento\Framework\App\State::class) + $this->appStateMock = $this->getMockBuilder(State::class) ->disableOriginalConstructor() ->setMethods([]) ->getMock(); + + $this->model = $this->objectManager->getObject( + Parser::class, + [ + 'resource' => $this->resourceFactoryMock, + 'storeManager' => $this->storeManagerMock, + 'inputFilter' => $this->inputFilterMock, + 'appState' => $this->appStateMock, + 'appCache' => $this->appCacheMock, + 'translateInline' => $this->translateInlineMock, + 'cacheManager' => $this->cacheManagerMock, + ] + ); } public function testProcessAjaxPostNotAllowed() @@ -118,10 +135,6 @@ public function testProcessAjaxPostNotAllowed() $this->translateInlineMock->expects($this->once()) ->method('isAllowed') ->willReturn(false); - $this->model = $this->objectManager->getObject( - Parser::class, - ['translateInline' => $this->translateInlineMock] - ); $this->assertEquals($expected, $this->model->processAjaxPost([])); } @@ -130,15 +143,6 @@ public function testProcessAjaxPost() $this->translateInlineMock->expects($this->once()) ->method('isAllowed') ->willReturn(true); - $this->model = $this->objectManager->getObject( - Parser::class, - [ - 'cacheManager' => $this->cacheManagerMock, - 'resource' => $this->resourceFactoryMock, - 'storeManager' => $this->storeManagerMock, - 'translateInline' => $this->translateInlineMock - ] - ); $this->model->processAjaxPost([]); } @@ -153,26 +157,11 @@ public function testProcessResponseBodyStringProcessingAttributesCorrectly() "data-translate=\"[{'shown':'Password','translated':'Password','original':'Password'," . "'location':'Tag attribute (ALT, TITLE, etc.)'}]\"" ]; - $this->translateInlineMock->expects($this->any())->method('getAdditionalHtmlAttribute')->willReturn(null); - - $this->model = $this->objectManager->getObject( - Parser::class, - [ - 'cacheManager' => $this->cacheManagerMock, - 'resource' => $this->resourceFactoryMock, - 'storeManager' => $this->storeManagerMock, - 'translateInline' => $this->translateInlineMock, - '_resourceFactory' => $this->resourceMock, - '_inputFilter' => $this->inputFilterMock, - '_appState' => $this->appStateMock, - '_appCache' => $this->appCacheMock, - '_translateInline' => $this->translateInlineMock - ] - ); + $this->translateInlineMock->method('getAdditionalHtmlAttribute')->willReturn(null); $processedContent = $this->model->processResponseBodyString($testContent); foreach ($processedAttributes as $attribute) { - $this->assertContains($attribute, $processedContent, "data-translate attribute not processed correctly"); + $this->assertContains($attribute, $processedContent, 'data-translate attribute not processed correctly'); } } } From 45eb2d11b7431e904bfd1a36fc28ed5a33b5deda Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Tue, 10 Mar 2020 12:00:07 +0200 Subject: [PATCH 237/369] MC-31837: Restricted admin - issue with saving products --- .../ResourceModel/Product/Website/Link.php | 43 +++++--- .../ProductWebsiteAssignmentHandler.php | 56 ++++++++++ .../ProductWebsiteAssignmentHandlerTest.php | 101 ++++++++++++++++++ app/code/Magento/Catalog/etc/di.xml | 5 + 4 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 app/code/Magento/Catalog/Model/ResourceModel/ProductWebsiteAssignmentHandler.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ProductWebsiteAssignmentHandlerTest.php diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Website/Link.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Website/Link.php index de4ffc5d862f9..d9016d8a852e8 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Website/Link.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Website/Link.php @@ -7,8 +7,10 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Framework\App\ResourceConnection; -use Magento\Framework\EntityManager\MetadataPool; +/** + * Class Link used for assign website to the product + */ class Link { /** @@ -28,6 +30,7 @@ public function __construct( /** * Retrieve associated with product websites ids + * * @param int $productId * @return array */ @@ -48,29 +51,53 @@ public function getWebsiteIdsByProductId($productId) /** * Return true - if websites was changed, and false - if not + * * @param ProductInterface $product * @param array $websiteIds * @return bool */ public function saveWebsiteIds(ProductInterface $product, array $websiteIds) + { + $productId = (int) $product->getId(); + return $this->updateProductWebsite($productId, $websiteIds); + } + + /** + * Get Product website table + * + * @return string + */ + private function getProductWebsiteTable() + { + return $this->resourceConnection->getTableName('catalog_product_website'); + } + + /** + * Update product website table + * + * @param int $productId + * @param array $websiteIds + * @return bool + */ + public function updateProductWebsite(int $productId, array $websiteIds): bool { $connection = $this->resourceConnection->getConnection(); - $oldWebsiteIds = $this->getWebsiteIdsByProductId($product->getId()); + $oldWebsiteIds = $this->getWebsiteIdsByProductId($productId); $insert = array_diff($websiteIds, $oldWebsiteIds); $delete = array_diff($oldWebsiteIds, $websiteIds); if (!empty($insert)) { $data = []; foreach ($insert as $websiteId) { - $data[] = ['product_id' => (int) $product->getId(), 'website_id' => (int) $websiteId]; + $data[] = ['product_id' => $productId, 'website_id' => (int)$websiteId]; } $connection->insertMultiple($this->getProductWebsiteTable(), $data); } if (!empty($delete)) { foreach ($delete as $websiteId) { - $condition = ['product_id = ?' => (int) $product->getId(), 'website_id = ?' => (int) $websiteId]; + $condition = ['product_id = ?' => $productId, 'website_id = ?' => (int)$websiteId]; $connection->delete($this->getProductWebsiteTable(), $condition); } } @@ -81,12 +108,4 @@ public function saveWebsiteIds(ProductInterface $product, array $websiteIds) return false; } - - /** - * @return string - */ - private function getProductWebsiteTable() - { - return $this->resourceConnection->getTableName('catalog_product_website'); - } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/ProductWebsiteAssignmentHandler.php b/app/code/Magento/Catalog/Model/ResourceModel/ProductWebsiteAssignmentHandler.php new file mode 100644 index 0000000000000..10e0a09593208 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/ProductWebsiteAssignmentHandler.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); +namespace Magento\Catalog\Model\ResourceModel; + +use Magento\Catalog\Model\ResourceModel\Product\Website\Link; +use Magento\Framework\EntityManager\Operation\AttributeInterface; + +/** + * Class purpose is to handle product websites assignment + */ +class ProductWebsiteAssignmentHandler implements AttributeInterface +{ + /** + * @var Link + */ + private $productLink; + + /** + * ProductWebsiteAssignmentHandler constructor + * + * @param Link $productLink + */ + public function __construct( + Link $productLink + ) { + $this->productLink = $productLink; + } + + /** + * Assign product website entity to the product repository + * + * @param string $entityType + * @param array $entityData + * @param array $arguments + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws \Exception + */ + public function execute($entityType, $entityData, $arguments = []): array + { + $websiteIds = array_key_exists('website_ids', $entityData) ? + array_filter($entityData['website_ids'], function ($websiteId) { + return $websiteId !== null; + }) : []; + $productId = array_key_exists('entity_id', $entityData) ? (int) $entityData['entity_id'] : null; + + if (!empty($productId) && !empty($websiteIds)) { + $this->productLink->updateProductWebsite($productId, $websiteIds); + } + return $entityData; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ProductWebsiteAssignmentHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ProductWebsiteAssignmentHandlerTest.php new file mode 100644 index 0000000000000..664aa311008f2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/ProductWebsiteAssignmentHandlerTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); +namespace Magento\Catalog\Test\Unit\Model\ResourceModel; + +use Magento\Catalog\Model\ResourceModel\Product\Website\Link; +use Magento\Catalog\Model\ResourceModel\ProductWebsiteAssignmentHandler; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; + +class ProductWebsiteAssignmentHandlerTest extends TestCase +{ + /** + * @var ProductWebsiteAssignmentHandler + */ + protected $handler; + + /** + * @var Link|\PHPUnit_Framework_MockObject_MockObject + */ + protected $productLinkMock; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $this->productLinkMock = $this->createPartialMock( + Link::class, + ['updateProductWebsite'] + ); + $this->handler = $objectManager->getObject( + ProductWebsiteAssignmentHandler::class, + [ + 'productLink' => $this->productLinkMock + ] + ); + } + + /** + * @param $actualData + * @param $expectedResult + * @dataProvider productWebsitesDataProvider + * @throws \Exception + */ + public function testUpdateProductWebsiteReturnValidResult($actualData, $expectedResult) + { + $this->productLinkMock->expects($this->any())->method('updateProductWebsite')->willReturn($expectedResult); + $this->assertEquals( + $actualData['entityData'], + $this->handler->execute($actualData['entityType'], $actualData['entityData']) + ); + } + + /** + * @return array + */ + public function productWebsitesDataProvider(): array + { + return [ + [ + [ + 'entityType' => 'product', + 'entityData' => [ + 'entity_id' => '12345', + 'website_ids' => ['1', '2', '3'], + 'name' => 'test-1', + 'sku' => 'test-1' + ] + ], + true + ], + [ + [ + 'entityType' => 'product', + 'entityData' => [ + 'entity_id' => null, + 'website_ids' => ['1', '2', '3'], + 'name' => 'test-1', + 'sku' => 'test-1' + ] + ], + false + ], + [ + [ + 'entityType' => 'product', + 'entityData' => [ + 'entity_id' => '12345', + 'website_ids' => [null], + 'name' => 'test-1', + 'sku' => 'test-1' + ] + ], + false + ] + ]; + } +} diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 223d690d28327..ff67989d337bb 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -891,6 +891,11 @@ <item name="update" xsi:type="string">Magento\Catalog\Model\ResourceModel\UpdateHandler</item> </item> </item> + <item name="websites" xsi:type="array"> + <item name="Magento\Catalog\Api\Data\ProductInterface" xsi:type="array"> + <item name="create" xsi:type="string">Magento\Catalog\Model\ResourceModel\ProductWebsiteAssignmentHandler</item> + </item> + </item> </argument> </arguments> </type> From f8e5bf06a16319849407f39c2312b30451f5765c Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Tue, 10 Mar 2020 14:19:06 +0200 Subject: [PATCH 238/369] MC-31878: [Magento Cloud] - Order bulk update using rest api --- .../Model/MassSchedule.php | 21 +-- .../Model/OperationRepositoryInterface.php | 31 ++++ .../Operation/OperationRepository.php | 27 ++- .../Magento/AsynchronousOperations/etc/di.xml | 1 + .../Controller/Rest/InputParamsResolver.php | 18 +- .../Rest/Asynchronous/InputParamsResolver.php | 29 ++- .../WebapiAsync/Model/OperationRepository.php | 102 ++++++++++ app/code/Magento/WebapiAsync/etc/di.xml | 21 +++ .../Model/OrderRepositoryInterfaceTest.php | 174 ++++++++++++++++++ 9 files changed, 393 insertions(+), 31 deletions(-) create mode 100644 app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php create mode 100644 app/code/Magento/WebapiAsync/Model/OperationRepository.php create mode 100644 dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/OrderRepositoryInterfaceTest.php diff --git a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php index 89d468159c6e9..1c1ca9c196d19 100644 --- a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php +++ b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php @@ -8,19 +8,18 @@ namespace Magento\AsynchronousOperations\Model; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\DataObject\IdentityGeneratorInterface; -use Magento\Framework\Exception\LocalizedException; -use Magento\AsynchronousOperations\Api\Data\ItemStatusInterfaceFactory; use Magento\AsynchronousOperations\Api\Data\AsyncResponseInterface; use Magento\AsynchronousOperations\Api\Data\AsyncResponseInterfaceFactory; use Magento\AsynchronousOperations\Api\Data\ItemStatusInterface; +use Magento\AsynchronousOperations\Api\Data\ItemStatusInterfaceFactory; +use Magento\Authorization\Model\UserContextInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Bulk\BulkManagementInterface; +use Magento\Framework\DataObject\IdentityGeneratorInterface; +use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Exception\BulkException; +use Magento\Framework\Exception\LocalizedException; use Psr\Log\LoggerInterface; -use Magento\AsynchronousOperations\Model\ResourceModel\Operation\OperationRepository; -use Magento\Authorization\Model\UserContextInterface; -use Magento\Framework\Encryption\Encryptor; /** * Class MassSchedule used for adding multiple entities as Operations to Bulk Management with the status tracking @@ -55,7 +54,7 @@ class MassSchedule private $logger; /** - * @var OperationRepository + * @var OperationRepositoryInterface */ private $operationRepository; @@ -77,7 +76,7 @@ class MassSchedule * @param AsyncResponseInterfaceFactory $asyncResponseFactory * @param BulkManagementInterface $bulkManagement * @param LoggerInterface $logger - * @param OperationRepository $operationRepository + * @param OperationRepositoryInterface $operationRepository * @param UserContextInterface $userContext * @param Encryptor|null $encryptor */ @@ -87,7 +86,7 @@ public function __construct( AsyncResponseInterfaceFactory $asyncResponseFactory, BulkManagementInterface $bulkManagement, LoggerInterface $logger, - OperationRepository $operationRepository, + OperationRepositoryInterface $operationRepository, UserContextInterface $userContext = null, Encryptor $encryptor = null ) { @@ -139,7 +138,7 @@ public function publishMass($topicName, array $entitiesArray, $groupId = null, $ $requestItem = $this->itemStatusInterfaceFactory->create(); try { - $operation = $this->operationRepository->createByTopic($topicName, $entityParams, $groupId); + $operation = $this->operationRepository->create($topicName, $entityParams, $groupId, $key); $operations[] = $operation; $requestItem->setId($key); $requestItem->setStatus(ItemStatusInterface::STATUS_ACCEPTED); diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php b/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php new file mode 100644 index 0000000000000..945692fed7c99 --- /dev/null +++ b/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AsynchronousOperations\Model; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface; + +/** + * Repository interface to create operation + */ +interface OperationRepositoryInterface +{ + /** + * Create operation by topic, parameters and group ID + * + * @param string $topicName + * @param array $entityParams + * format: array( + * '<arg1-name>' => '<arg1-value>', + * '<arg2-name>' => '<arg2-value>', + * ) + * @param string $groupId + * @param int|null $operationId + * @return OperationInterface + */ + public function create($topicName, $entityParams, $groupId, $operationId = null): OperationInterface; +} diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php index 54e65cc3470dd..40f776ad81099 100644 --- a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php @@ -10,6 +10,7 @@ use Magento\AsynchronousOperations\Api\Data\OperationInterface; use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\AsynchronousOperations\Model\OperationRepositoryInterface; use Magento\Framework\MessageQueue\MessageValidator; use Magento\Framework\MessageQueue\MessageEncoder; use Magento\Framework\Serialize\Serializer\Json; @@ -18,10 +19,10 @@ /** * Create operation for list of bulk operations. */ -class OperationRepository +class OperationRepository implements OperationRepositoryInterface { /** - * @var \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory + * @var OperationInterfaceFactory */ private $operationFactory; @@ -67,10 +68,14 @@ public function __construct( } /** - * @param $topicName - * @param $entityParams - * @param $groupId - * @return mixed + * Create operation by topic, parameters and group ID + * + * @param string $topicName + * @param array $entityParams + * @param string $groupId + * @return OperationInterface + * @deprecated No longer used. + * @see create() */ public function createByTopic($topicName, $entityParams, $groupId) { @@ -91,8 +96,16 @@ public function createByTopic($topicName, $entityParams, $groupId) ], ]; - /** @var \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation */ + /** @var OperationInterface $operation */ $operation = $this->operationFactory->create($data); return $this->entityManager->save($operation); } + + /** + * @inheritDoc + */ + public function create($topicName, $entityParams, $groupId, $operationId = null): OperationInterface + { + return $this->createByTopic($topicName, $entityParams, $groupId); + } } diff --git a/app/code/Magento/AsynchronousOperations/etc/di.xml b/app/code/Magento/AsynchronousOperations/etc/di.xml index 171a01cedf289..0d8126358abf4 100644 --- a/app/code/Magento/AsynchronousOperations/etc/di.xml +++ b/app/code/Magento/AsynchronousOperations/etc/di.xml @@ -18,6 +18,7 @@ <preference for="Magento\AsynchronousOperations\Api\Data\BulkOperationsStatusInterface" type="Magento\AsynchronousOperations\Model\BulkStatus\Short" /> <preference for="Magento\AsynchronousOperations\Api\Data\OperationSearchResultsInterface" type="Magento\AsynchronousOperations\Model\OperationSearchResults" /> <preference for="Magento\AsynchronousOperations\Api\OperationRepositoryInterface" type="Magento\AsynchronousOperations\Model\OperationRepository" /> + <preference for="Magento\AsynchronousOperations\Model\OperationRepositoryInterface" type="Magento\AsynchronousOperations\Model\ResourceModel\Operation\OperationRepository" /> <type name="Magento\Framework\EntityManager\MetadataPool"> <arguments> <argument name="metadata" xsi:type="array"> diff --git a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php index 07d1b4e07fe9d..723e274d1e5fa 100644 --- a/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php +++ b/app/code/Magento/Webapi/Controller/Rest/InputParamsResolver.php @@ -8,7 +8,6 @@ use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Rest\Request as RestRequest; -use Magento\Webapi\Controller\Rest\Router; use Magento\Webapi\Controller\Rest\Router\Route; /** @@ -81,7 +80,20 @@ public function resolve() $route = $this->getRoute(); $serviceMethodName = $route->getServiceMethod(); $serviceClassName = $route->getServiceClass(); + $inputData = $this->getInputData(); + return $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); + } + /** + * Get API input data + * + * @return array + */ + public function getInputData() + { + $route = $this->getRoute(); + $serviceMethodName = $route->getServiceMethod(); + $serviceClassName = $route->getServiceClass(); /* * Valid only for updates using PUT when passing id value both in URL and body */ @@ -97,9 +109,7 @@ public function resolve() $inputData = $this->request->getRequestData(); } - $inputData = $this->paramsOverrider->override($inputData, $route->getParameters()); - $inputParams = $this->serviceInputProcessor->process($serviceClassName, $serviceMethodName, $inputData); - return $inputParams; + return $this->paramsOverrider->override($inputData, $route->getParameters()); } /** diff --git a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php index 93bddd09faef8..064bd99b9b6bf 100644 --- a/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php +++ b/app/code/Magento/WebapiAsync/Controller/Rest/Asynchronous/InputParamsResolver.php @@ -8,12 +8,12 @@ namespace Magento\WebapiAsync\Controller\Rest\Asynchronous; -use Magento\Framework\Webapi\ServiceInputProcessor; use Magento\Framework\Webapi\Rest\Request as RestRequest; -use Magento\Webapi\Controller\Rest\Router; +use Magento\Framework\Webapi\ServiceInputProcessor; +use Magento\Webapi\Controller\Rest\InputParamsResolver as WebapiInputParamsResolver; use Magento\Webapi\Controller\Rest\ParamsOverrider; use Magento\Webapi\Controller\Rest\RequestValidator; -use Magento\Webapi\Controller\Rest\InputParamsResolver as WebapiInputParamsResolver; +use Magento\Webapi\Controller\Rest\Router; /** * This class is responsible for retrieving resolved input data @@ -96,6 +96,22 @@ public function resolve() } $this->requestValidator->validate(); $webapiResolvedParams = []; + foreach ($this->getInputData() as $key => $singleEntityParams) { + $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams); + } + return $webapiResolvedParams; + } + + /** + * Get API input data + * + * @return array + */ + public function getInputData() + { + if ($this->isBulk === false) { + return [$this->inputParamsResolver->getInputData()]; + } $inputData = $this->request->getRequestData(); $httpMethod = $this->request->getHttpMethod(); @@ -103,12 +119,7 @@ public function resolve() $requestBodyParams = $this->request->getBodyParams(); $inputData = array_merge($requestBodyParams, $inputData); } - - foreach ($inputData as $key => $singleEntityParams) { - $webapiResolvedParams[$key] = $this->resolveBulkItemParams($singleEntityParams); - } - - return $webapiResolvedParams; + return $inputData; } /** diff --git a/app/code/Magento/WebapiAsync/Model/OperationRepository.php b/app/code/Magento/WebapiAsync/Model/OperationRepository.php new file mode 100644 index 0000000000000..05dab58b945c0 --- /dev/null +++ b/app/code/Magento/WebapiAsync/Model/OperationRepository.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\WebapiAsync\Model; + +use Magento\AsynchronousOperations\Api\Data\OperationInterface; +use Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory; +use Magento\AsynchronousOperations\Model\OperationRepositoryInterface; +use Magento\Framework\MessageQueue\MessageValidator; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\EntityManager\EntityManager; +use Magento\WebapiAsync\Controller\Rest\Asynchronous\InputParamsResolver; + +/** + * Repository class to create operation + */ +class OperationRepository implements OperationRepositoryInterface +{ + /** + * @var OperationInterfaceFactory + */ + private $operationFactory; + + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var EntityManager + */ + private $entityManager; + + /** + * @var MessageValidator + */ + private $messageValidator; + /** + * @var InputParamsResolver + */ + private $inputParamsResolver; + + /** + * Initialize dependencies. + * + * @param OperationInterfaceFactory $operationFactory + * @param EntityManager $entityManager + * @param MessageValidator $messageValidator + * @param Json $jsonSerializer + * @param InputParamsResolver $inputParamsResolver + */ + public function __construct( + OperationInterfaceFactory $operationFactory, + EntityManager $entityManager, + MessageValidator $messageValidator, + Json $jsonSerializer, + InputParamsResolver $inputParamsResolver + ) { + $this->operationFactory = $operationFactory; + $this->jsonSerializer = $jsonSerializer; + $this->messageValidator = $messageValidator; + $this->entityManager = $entityManager; + $this->inputParamsResolver = $inputParamsResolver; + } + + /** + * @inheritDoc + */ + public function create($topicName, $entityParams, $groupId, $operationId = null): OperationInterface + { + $this->messageValidator->validate($topicName, $entityParams); + $requestData = $this->inputParamsResolver->getInputData(); + if ($operationId === null || !isset($requestData[$operationId])) { + throw new \InvalidArgumentException( + 'Parameter "$operationId" must not be NULL and must exist in input data' + ); + } + $encodedMessage = $this->jsonSerializer->serialize($requestData[$operationId]); + + $serializedData = [ + 'entity_id' => null, + 'entity_link' => '', + 'meta_information' => $encodedMessage, + ]; + $data = [ + 'data' => [ + OperationInterface::BULK_ID => $groupId, + OperationInterface::TOPIC_NAME => $topicName, + OperationInterface::SERIALIZED_DATA => $this->jsonSerializer->serialize($serializedData), + OperationInterface::STATUS => OperationInterface::STATUS_TYPE_OPEN, + ], + ]; + + /** @var OperationInterface $operation */ + $operation = $this->operationFactory->create($data); + return $this->entityManager->save($operation); + } +} diff --git a/app/code/Magento/WebapiAsync/etc/di.xml b/app/code/Magento/WebapiAsync/etc/di.xml index 7411ec0561d24..cfe1a5dbae53f 100644 --- a/app/code/Magento/WebapiAsync/etc/di.xml +++ b/app/code/Magento/WebapiAsync/etc/di.xml @@ -34,10 +34,31 @@ <argument name="isBulk" xsi:type="boolean">true</argument> </arguments> </virtualType> + <virtualType name="Magento\WebapiAsync\Model\Bulk\OperationRepository" type="Magento\WebapiAsync\Model\OperationRepository"> + <arguments> + <argument name="inputParamsResolver" xsi:type="object">Magento\WebapiAsync\Controller\VirtualType\InputParamsResolver</argument> + </arguments> + </virtualType> + <virtualType name="Magento\WebapiAsync\Model\MassSchedule" type="Magento\AsynchronousOperations\Model\MassSchedule"> + <arguments> + <argument name="operationRepository" xsi:type="object">Magento\WebapiAsync\Model\OperationRepository</argument> + </arguments> + </virtualType> + <virtualType name="Magento\WebapiAsync\Model\Bulk\MassSchedule" type="Magento\AsynchronousOperations\Model\MassSchedule"> + <arguments> + <argument name="operationRepository" xsi:type="object">Magento\WebapiAsync\Model\Bulk\OperationRepository</argument> + </arguments> + </virtualType> + <type name="Magento\WebapiAsync\Controller\Rest\AsynchronousRequestProcessor"> + <arguments> + <argument name="asyncBulkPublisher" xsi:type="object">Magento\WebapiAsync\Model\MassSchedule</argument> + </arguments> + </type> <virtualType name="Magento\WebapiAsync\Controller\Rest\VirtualType\AsynchronousBulkRequestProcessor" type="Magento\WebapiAsync\Controller\Rest\AsynchronousRequestProcessor"> <arguments> <argument name="inputParamsResolver" xsi:type="object">Magento\WebapiAsync\Controller\VirtualType\InputParamsResolver</argument> <argument name="processorPath" xsi:type="const">Magento\WebapiAsync\Controller\Rest\AsynchronousRequestProcessor::BULK_PROCESSOR_PATH</argument> + <argument name="asyncBulkPublisher" xsi:type="object">Magento\WebapiAsync\Model\Bulk\MassSchedule</argument> </arguments> </virtualType> <virtualType name="Magento\WebapiAsync\Controller\Rest\VirtualType\AsynchronousBulkSchemaRequestProcessor" type="Magento\WebapiAsync\Controller\Rest\AsynchronousSchemaRequestProcessor"> diff --git a/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/OrderRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/OrderRepositoryInterfaceTest.php new file mode 100644 index 0000000000000..bc7940ca35f35 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/OrderRepositoryInterfaceTest.php @@ -0,0 +1,174 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\WebapiAsync\Model; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Webapi\Rest\Request; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\Order; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\MessageQueue\EnvironmentPreconditionException; +use Magento\TestFramework\MessageQueue\PreconditionFailedException; +use Magento\TestFramework\MessageQueue\PublisherConsumerController; +use Magento\TestFramework\TestCase\WebapiAbstract; + +/** + * Test order repository interface via async webapi + */ +class OrderRepositoryInterfaceTest extends WebapiAbstract +{ + private const ASYNC_BULK_SAVE_ORDER = '/async/bulk/V1/orders'; + private const ASYNC_SAVE_ORDER = '/async/V1/orders'; + /** + * @var ObjectManagerInterface + */ + private $objectManager; + /** + * @var PublisherConsumerController + */ + private $publisherConsumerController; + + /** + * @inheritDoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + + $params = array_merge_recursive( + Bootstrap::getInstance()->getAppInitParams(), + ['MAGE_DIRS' => ['cache' => ['path' => TESTS_TEMP_DIR . '/cache']]] + ); + + /** @var PublisherConsumerController publisherConsumerController */ + $this->publisherConsumerController = $this->objectManager->create( + PublisherConsumerController::class, + [ + 'consumers' => ['async.operations.all'], + 'logFilePath' => TESTS_TEMP_DIR . "/MessageQueueTestLog.txt", + 'appInitParams' => $params, + ] + ); + + try { + $this->publisherConsumerController->initialize(); + } catch (EnvironmentPreconditionException $e) { + $this->markTestSkipped($e->getMessage()); + } catch (PreconditionFailedException $e) { + $this->fail( + $e->getMessage() + ); + } + } + + /** + * @inheritDoc + */ + public function tearDown() + { + $this->publisherConsumerController->stopConsumers(); + parent::tearDown(); + } + + /** + * Check that order is updated successfuly via async webapi + * + * @magentoApiDataFixture Magento/Sales/_files/order.php + * @dataProvider saveDataProvider + * @param array $data + * @param bool $isBulk + * @return void + */ + public function testSave(array $data, bool $isBulk = true): void + { + $this->_markTestAsRestOnly(); + /** @var Order $beforeUpdateOrder */ + $beforeUpdateOrder = $this->objectManager->get(Order::class)->loadByIncrementId('100000001'); + $requestData = [ + 'entity' => array_merge($data, [OrderInterface::ENTITY_ID => $beforeUpdateOrder->getEntityId()]) + ]; + if ($isBulk) { + $requestData = [$requestData]; + } + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => $isBulk ? self::ASYNC_BULK_SAVE_ORDER : self::ASYNC_SAVE_ORDER, + 'httpMethod' => Request::HTTP_METHOD_POST, + ] + ]; + $this->makeAsyncRequest($serviceInfo, $requestData); + try { + $this->publisherConsumerController->waitForAsynchronousResult( + function (Order $beforeUpdateOrder, array $data) { + /** @var Order $afterUpdateOrder */ + $afterUpdateOrder = $this->objectManager->get(Order::class)->load($beforeUpdateOrder->getId()); + foreach ($data as $attribute => $value) { + $getter = 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $attribute))); + if ($value !== $afterUpdateOrder->$getter()) { + return false; + } + } + //check that base_grand_total and grand_total are not overwritten + $this->assertEquals( + $beforeUpdateOrder->getBaseGrandTotal(), + $afterUpdateOrder->getBaseGrandTotal() + ); + $this->assertEquals( + $beforeUpdateOrder->getGrandTotal(), + $afterUpdateOrder->getGrandTotal() + ); + return true; + }, + [$beforeUpdateOrder, $data] + ); + } catch (PreconditionFailedException $e) { + $this->fail("Order update via async webapi failed"); + } + } + + /** + * Data provider for tesSave() + * + * @return array + */ + public function saveDataProvider(): array + { + return [ + 'update order in bulk mode' => [ + [ + OrderInterface::CUSTOMER_EMAIL => 'customer.email.modified@magento.test' + ], + true + ], + 'update order in single mode' => [ + [ + OrderInterface::CUSTOMER_EMAIL => 'customer.email.modified@magento.test' + ], + false + ] + ]; + } + + /** + * Make async webapi request + * + * @param array $serviceInfo + * @param array $requestData + * @return void + */ + private function makeAsyncRequest(array $serviceInfo, array $requestData): void + { + $response = $this->_webApiCall($serviceInfo, $requestData); + $this->assertNotEmpty($response['request_items']); + foreach ($response['request_items'] as $requestItem) { + $this->assertEquals('accepted', $requestItem['status']); + } + $this->assertFalse($response['errors']); + } +} From cf9dbc39da2ea762ad1ea509690390defaddd109 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Tue, 10 Mar 2020 14:30:37 +0200 Subject: [PATCH 239/369] MC-32225: MFTF test for Recently Viewed products issues does not work on store level --- ...oreFrontRecentlyViewedAtStoreLevelTest.xml | 166 ++++++++++++++++++ ...rontRecentlyViewedAtStoreViewLevelTest.xml | 2 +- 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml new file mode 100644 index 0000000000000..8243f21cb6510 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml @@ -0,0 +1,166 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontRecentlyViewedAtStoreLevelTest"> + <annotations> + <stories value="Recently Viewed Product"/> + <title value="Recently Viewed Product at store level"/> + <description value="Recently Viewed Product should not be displayed on second store , if configured as, Per Store "/> + <testCaseId value="MC-32018"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create Simple Product and Category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct3"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct4"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create store1 for default website --> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createFirstStore"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStore.name}}"/> + <argument name="storeGroupCode" value="{{customStore.code}}"/> + </actionGroup> + <!-- Create Storeview1 for Store1--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + <!--Create storeView 2--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewTwo"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <!-- Set Stores > Configurations > Catalog > Recently Viewed/Compared Products > Show for Current = store --> + <magentoCLI command="config:set {{RecentlyViewedProductScopeStoreGroup.path}} {{RecentlyViewedProductScopeStoreGroup.value}}" stepKey="RecentlyViewedProductScopeStoreGroup"/> + </before> + <after> + <!-- Delete Product and Category --> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createSimpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="createSimpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete store1 for default website --> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteFirstStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + + <!--Reset Stores > Configurations > Catalog > Recently Viewed/Compared Products > Show for Current = Website--> + <magentoCLI command="config:set {{RecentlyViewedProductScopeWebsite.path}} {{RecentlyViewedProductScopeWebsite.value}}" stepKey="RecentlyViewedProductScopeWebsite"/> + + <!-- Clear Widget--> + <actionGroup ref="AdminEditCMSPageContentActionGroup" stepKey="clearRecentlyViewedWidgetsFromCMSContent"> + <argument name="content" value="{{CmsHomePageContent.content}}"/> + <argument name="pageId" value="{{CmsHomePageContent.page_id}}"/> + </actionGroup> + <!-- Logout Admin --> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterDeletion"/> + </after> + <!--Create widget for recently viewed products--> + <actionGroup ref="AdminEditCMSPageContentActionGroup" stepKey="clearRecentlyViewedWidgetsFromCMSContentBefore"> + <argument name="content" value="{{CmsHomePageContent.content}}"/> + <argument name="pageId" value="{{CmsHomePageContent.page_id}}"/> + </actionGroup> + + <amOnPage url="{{AdminCmsPageEditPage.url(CmsHomePageContent.page_id)}}" stepKey="navigateToEditCmsHomePage"/> + <waitForPageLoad time="50" stepKey="waitForCmsContentPageToLoad"/> + + <actionGroup ref="AdminInsertRecentlyViewedWidgetActionGroup" stepKey="insertRecentlyViewedWidget"> + <argument name="attributeSelector1" value="show_attributes"/> + <argument name="attributeSelector2" value="show_buttons"/> + <argument name="productAttributeSection1" value="1"/> + <argument name="productAttributeSection2" value="4"/> + <argument name="buttonToShowSection1" value="1"/> + <argument name="buttonToShowSection2" value="3"/> + </actionGroup> + <!-- Warm up cache --> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterWidgetCreated"/> + <!-- Navigate to product 3 on store front --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct2.name$)}}" stepKey="goToStoreOneProductPageTwo"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct3.name$)}}" stepKey="goToStoreOneProductPageThree"/> + <!-- Go to Home Page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStoreFrontHomePage"/> + <waitForPageLoad time="30" stepKey="waitForHomeContentPageToLoad"/> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertStoreOneRecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertStoreOneRecentlyViewedProduct3"> + <argument name="productName" value="$$createSimpleProduct3.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + <!-- Switch to second store and add second product (visible on second store) to wishlist --> + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="clickSwitchStoreButtonOnDefaultStore"/> + <click selector="{{StorefrontFooterSection.storeLink(customStore.name)}}" stepKey="selectCustomStore"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct1.name$)}}" stepKey="goToStore2ProductPage1"/> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct2.name$)}}" stepKey="goToStore2ProductPage2"/> + <!-- Go to Home Page --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePage2"/> + <waitForPageLoad time="30" stepKey="waitForStoreHomeContentPageToLoad"/> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStore1RecentlyViewedProduct1"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabDontSeeHomeProduct3"/> + <assertNotContains expected="$$createSimpleProduct3.name$$" actual="$grabDontSeeHomeProduct3" stepKey="assertNotSeeProduct3"/> + + <!-- Switch Storeview--> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreViewActionGroup"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStoreView2RecentlyViewedProduct1"> + <argument name="productName" value="$$createSimpleProduct1.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertNextStoreView2RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabStoreView2DontSeeHomeProduct3"/> + <assertNotContains expected="$$createSimpleProduct3.name$$" actual="$grabDontSeeHomeProduct3" stepKey="assertStoreView2NotSeeProduct3"/> + + <!-- Switch to default store--> + + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="clickSwitchStoreButtonOnHomeDefaultStore"/> + <click selector="{{StorefrontFooterSection.storeLink('Main Website Store')}}" stepKey="selectDefaultStoreToSwitchOn"/> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertSwitchStore1RecentlyViewedProduct2"> + <argument name="productName" value="$$createSimpleProduct2.name$$"/> + <argument name="productPosition" value="2"/> + </actionGroup> + + <actionGroup ref="AssertSeeProductDetailsOnStorefrontRecentlyViewedWidgetActionGroup" stepKey="assertSwitchStore1RecentlyViewedProduct3"> + <argument name="productName" value="$$createSimpleProduct3.name$$"/> + <argument name="productPosition" value="1"/> + </actionGroup> + <grabTextFrom selector="{{StoreFrontRecentlyViewedProductSection.ProductName('2')}}" stepKey="grabDontSeeHomeProduct1"/> + <assertNotContains expected="$$createSimpleProduct1.name$$" actual="$grabDontSeeHomeProduct1" stepKey="assertNotSeeProduct1"/> + + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml index afcf42fb0431a..f8ee9e562a6a9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml @@ -12,7 +12,7 @@ <stories value="Recently Viewed Product"/> <title value="Recently Viewed Product at store view level"/> <description value="Recently Viewed Product should not be displayed on second store view, if configured as, Per Store View "/> - <testCaseId value="MC-30453"/> + <testCaseId value="MC-31877"/> <severity value="CRITICAL"/> <group value="catalog"/> </annotations> From 06d33fe294c4310f2a649ca70d4be6439a5a4030 Mon Sep 17 00:00:00 2001 From: Piotr Markiewicz <piotr.markiewicz@vaimo.com> Date: Tue, 10 Mar 2020 15:08:28 +0100 Subject: [PATCH 240/369] Added unit tests + prevent Path Traversal in Delete controller --- .../Adminhtml/Export/File/Delete.php | 2 +- .../Adminhtml/Export/File/DeleteTest.php | 181 +++++++++++++++++ .../Adminhtml/Export/File/DownloadTest.php | 188 ++++++++++++++++++ 3 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php create mode 100644 app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php index 316607bb247fb..1b7fdc2881073 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php @@ -62,7 +62,7 @@ public function execute() $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('adminhtml/export/index'); $fileName = $this->getRequest()->getParam('filename'); - if (empty($fileName)) { + if (empty($fileName) || preg_match('/\.\.(\\\|\/)/', $fileName) !== 0) { $this->messageManager->addErrorMessage(__('Please provide valid export file name')); return $resultRedirect; diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php new file mode 100644 index 0000000000000..eceaab3d1d1f3 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php @@ -0,0 +1,181 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ImportExport\Controller\Adminhtml\Export\File; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DeleteTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Backend\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject + */ + protected $context; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $objectManagerHelper; + + /** + * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject + */ + protected $request; + + /** + * @var \Magento\Framework\Controller\Result\Raw|\PHPUnit_Framework_MockObject_MockObject + */ + protected $redirect; + + /** + * @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $resultRedirectFactory; + + /** + * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fileSystem; + + /** + * @var \Magento\Framework\Filesystem\DriverInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $file; + + /** + * @var \Magento\ImportExport\Controller\Adminhtml\Export\File\Delete|\PHPUnit_Framework_MockObject_MockObject + */ + protected $deleteController; + + /** + * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $messageManager; + + /** + * @var \Magento\Framework\Filesystem\Directory\ReadInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $directory; + + /** + * Set up + */ + protected function setUp() + { + $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileSystem = $this->getMockBuilder(\Magento\Framework\Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->directory = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\ReadInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->file = $this->getMockBuilder(\Magento\Framework\Filesystem\DriverInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->context = $this->createPartialMock( + \Magento\Backend\App\Action\Context::class, + ['getRequest', 'getResultRedirectFactory', 'getMessageManager'] + ); + + $this->redirect = $this->createPartialMock(\Magento\Backend\Model\View\Result\Redirect::class, ['setPath']); + + $this->resultRedirectFactory = $this->createPartialMock( + \Magento\Framework\Controller\Result\RedirectFactory::class, + ['create'] + ); + $this->resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->redirect); + $this->context->expects($this->any())->method('getRequest')->willReturn($this->request); + $this->context->expects($this->any()) + ->method('getResultRedirectFactory') + ->willReturn($this->resultRedirectFactory); + + $this->context->expects($this->any()) + ->method('getMessageManager') + ->willReturn($this->messageManager); + + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->deleteController = $this->objectManagerHelper->getObject( + Delete::class, + [ + 'context' => $this->context, + 'filesystem' => $this->fileSystem, + 'file' => $this->file + ] + ); + } + + /** + * Tests download controller with different file names in request. + */ + public function testExecuteSuccess() + { + $this->request->method('getParam') + ->with('filename') + ->willReturn('sampleFile'); + + $this->fileSystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); + $this->directory->expects($this->once())->method('isFile')->willReturn(true); + $this->file->expects($this->once())->method('deleteFile')->willReturn(true); + $this->messageManager->expects($this->once())->method('addSuccessMessage'); + + $this->deleteController->execute(); + } + + /** + * Tests download controller with different file names in request. + + */ + public function testExecuteFileDoesntExists() + { + $this->request->method('getParam') + ->with('filename') + ->willReturn('sampleFile'); + + $this->fileSystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); + $this->directory->expects($this->once())->method('isFile')->willReturn(false); + $this->messageManager->expects($this->once())->method('addErrorMessage'); + + $this->deleteController->execute(); + } + + /** + * Test execute() with invalid file name + * @param string $requestFilename + * @dataProvider executeDataProvider + */ + public function testExecuteInvalidFileName($requestFilename) + { + $this->request->method('getParam')->with('filename')->willReturn($requestFilename); + $this->messageManager->expects($this->once())->method('addErrorMessage'); + + $this->deleteController->execute(); + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + 'Relative file name' => ['../.htaccess'], + 'Empty file name' => [''], + 'Null file name' => [null], + ]; + } +} diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php new file mode 100644 index 0000000000000..e2b5395ac2231 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php @@ -0,0 +1,188 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ImportExport\Controller\Adminhtml\Export\File; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DownloadTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Backend\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject + */ + protected $context; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $objectManagerHelper; + + /** + * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject + */ + protected $request; + + /** + * @var \Magento\Framework\Controller\Result\Raw|\PHPUnit_Framework_MockObject_MockObject + */ + protected $redirect; + + /** + * @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $resultRedirectFactory; + + /** + * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fileSystem; + + /** + * @var \Magento\Framework\App\Response\Http\FileFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fileFactory; + + /** + * @var \Magento\ImportExport\Controller\Adminhtml\Export\File\Download|\PHPUnit_Framework_MockObject_MockObject + */ + protected $downloadController; + + /** + * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $messageManager; + + /** + * @var \Magento\Framework\Filesystem\Directory\ReadInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $directory; + + /** + * Set up + */ + protected function setUp() + { + $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileSystem = $this->getMockBuilder(\Magento\Framework\Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->directory = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\ReadInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->fileFactory = $this->getMockBuilder(\Magento\Framework\App\Response\Http\FileFactory::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->context = $this->createPartialMock( + \Magento\Backend\App\Action\Context::class, + ['getRequest', 'getResultRedirectFactory', 'getMessageManager'] + ); + + $this->redirect = $this->createPartialMock( + \Magento\Backend\Model\View\Result\Redirect::class, + ['setPath'] + ); + + $this->resultRedirectFactory = $this->createPartialMock( + \Magento\Framework\Controller\Result\RedirectFactory::class, + ['create'] + ); + $this->resultRedirectFactory->expects($this->any()) + ->method('create') + ->willReturn($this->redirect); + + $this->context->expects($this->any()) + ->method('getRequest') + ->willReturn($this->request); + + $this->context->expects($this->any()) + ->method('getResultRedirectFactory') + ->willReturn($this->resultRedirectFactory); + + $this->context->expects($this->any()) + ->method('getMessageManager') + ->willReturn($this->messageManager); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->downloadController = $this->objectManagerHelper->getObject( + Download::class, + [ + 'context' => $this->context, + 'filesystem' => $this->fileSystem, + 'fileFactory' => $this->fileFactory + ] + ); + } + + /** + * Tests download controller with successful file downloads + */ + public function testExecuteSuccess() + { + $this->request->method('getParam') + ->with('filename') + ->willReturn('sampleFile.csv'); + + $this->fileSystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); + $this->directory->expects($this->once())->method('isFile')->willReturn(true); + $this->fileFactory->expects($this->once())->method('create'); + + $this->downloadController->execute(); + } + + /** + * Tests download controller with file that doesn't exist + + */ + public function testExecuteFileDoesntExists() + { + $this->request->method('getParam') + ->with('filename') + ->willReturn('sampleFile'); + + $this->fileSystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); + $this->directory->expects($this->once())->method('isFile')->willReturn(false); + $this->messageManager->expects($this->once())->method('addErrorMessage'); + + $this->downloadController->execute(); + } + + /** + * Test execute() with invalid file name + * @param string $requestFilename + * @dataProvider executeDataProvider + */ + public function testExecuteInvalidFileName($requestFilename) + { + $this->request->method('getParam')->with('filename')->willReturn($requestFilename); + $this->messageManager->expects($this->once())->method('addErrorMessage'); + + $this->downloadController->execute(); + } + + /** + * @return array + */ + public function executeDataProvider() + { + return [ + 'Relative file name' => ['../.htaccess'], + 'Empty file name' => [''], + 'Null file name' => [null], + ]; + } +} From acfc589b812278750e6545edc9f0e7b71a80cea3 Mon Sep 17 00:00:00 2001 From: Vaha <vaha@atwix.com> Date: Fri, 6 Mar 2020 16:04:03 +0200 Subject: [PATCH 241/369] Added improvements to product attribute repository (save method) to convert frontend input and add swatch input type for rest area --- ...ttributeByCodeOnProductFormActionGroup.xml | 21 +++++ .../Product/Attribute/Plugin/Save.php | 41 ++++----- .../ConvertSwatchAttributeFrontendInput.php | 46 ++++++++++ .../Product/Attribute/RepositoryPlugin.php | 51 +++++++++++ .../Test/Mftf/Data/SwatchAttributeData.xml | 3 + ...heckTextSwatchAttributeAddedViaApiTest.xml | 44 ++++++++++ .../Product/Attribute/Plugin/SaveTest.php | 70 --------------- ...onvertSwatchAttributeFrontendInputTest.php | 86 +++++++++++++++++++ .../Magento/Swatches/etc/webapi_rest/di.xml | 12 +++ 9 files changed, 282 insertions(+), 92 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductAttributeByCodeOnProductFormActionGroup.xml create mode 100644 app/code/Magento/Swatches/Model/ConvertSwatchAttributeFrontendInput.php create mode 100644 app/code/Magento/Swatches/Plugin/Catalog/Model/Product/Attribute/RepositoryPlugin.php create mode 100644 app/code/Magento/Swatches/Test/Mftf/Test/AdminCheckTextSwatchAttributeAddedViaApiTest.xml delete mode 100644 app/code/Magento/Swatches/Test/Unit/Controller/Adminhtml/Product/Attribute/Plugin/SaveTest.php create mode 100644 app/code/Magento/Swatches/Test/Unit/Model/ConvertSwatchAttributeFrontendInputTest.php create mode 100644 app/code/Magento/Swatches/etc/webapi_rest/di.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductAttributeByCodeOnProductFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductAttributeByCodeOnProductFormActionGroup.xml new file mode 100644 index 0000000000000..2432c974b79f4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductAttributeByCodeOnProductFormActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminProductAttributeByCodeOnProductFormActionGroup"> + <annotations> + <description>Requires the navigation to the Product page. Provided dropdown attribute presents on the page.</description> + </annotations> + <arguments> + <argument name="productAttributeCode" type="string" defaultValue="{{textSwatchProductAttribute.attribute_code}}"/> + </arguments> + + <seeElement selector="{{AdminProductAttributesSection.attributeDropdownByCode(productAttributeCode)}}" stepKey="assertAttributeIsPresentOnForm"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php b/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php index 72d27152d639a..c264614b1a0cf 100644 --- a/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php +++ b/app/code/Magento/Swatches/Controller/Adminhtml/Product/Attribute/Plugin/Save.php @@ -3,18 +3,33 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Swatches\Controller\Adminhtml\Product\Attribute\Plugin; use Magento\Catalog\Controller\Adminhtml\Product\Attribute; use Magento\Framework\App\RequestInterface; -use Magento\Swatches\Model\Swatch; +use Magento\Swatches\Model\ConvertSwatchAttributeFrontendInput; /** * Plugin for product attribute save controller. */ class Save { + /** + * @var ConvertSwatchAttributeFrontendInput + */ + private $convertSwatchAttributeFrontendInput; + + /** + * @param ConvertSwatchAttributeFrontendInput $convertSwatchAttributeFrontendInput + */ + public function __construct( + ConvertSwatchAttributeFrontendInput $convertSwatchAttributeFrontendInput + ) { + $this->convertSwatchAttributeFrontendInput = $convertSwatchAttributeFrontendInput; + } + /** * Performs the conversion of the frontend input value. * @@ -23,30 +38,12 @@ class Save * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeDispatch(Attribute\Save $subject, RequestInterface $request) + public function beforeDispatch(Attribute\Save $subject, RequestInterface $request): array { $data = $request->getPostValue(); + $data = $this->convertSwatchAttributeFrontendInput->execute($data); + $request->setPostValue($data); - if (isset($data['frontend_input'])) { - switch ($data['frontend_input']) { - case 'swatch_visual': - $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_VISUAL; - $data['frontend_input'] = 'select'; - $request->setPostValue($data); - break; - case 'swatch_text': - $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_TEXT; - $data['use_product_image_for_swatch'] = 0; - $data['frontend_input'] = 'select'; - $request->setPostValue($data); - break; - case 'select': - $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_DROPDOWN; - $data['frontend_input'] = 'select'; - $request->setPostValue($data); - break; - } - } return [$request]; } } diff --git a/app/code/Magento/Swatches/Model/ConvertSwatchAttributeFrontendInput.php b/app/code/Magento/Swatches/Model/ConvertSwatchAttributeFrontendInput.php new file mode 100644 index 0000000000000..698d0fcc4aea2 --- /dev/null +++ b/app/code/Magento/Swatches/Model/ConvertSwatchAttributeFrontendInput.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Model; + +/** + * Performs the conversion of the frontend input value for attribute data + */ +class ConvertSwatchAttributeFrontendInput +{ + /** + * Performs the conversion of the frontend input value for attribute data + * + * @param array|null $data + * + * @return array|null + */ + public function execute(?array $data): ?array + { + if (!isset($data['frontend_input'])) { + return $data; + } + + switch ($data['frontend_input']) { + case 'swatch_visual': + $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_VISUAL; + $data['frontend_input'] = 'select'; + break; + case 'swatch_text': + $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_TEXT; + $data['use_product_image_for_swatch'] = 0; + $data['frontend_input'] = 'select'; + break; + case 'select': + $data[Swatch::SWATCH_INPUT_TYPE_KEY] = Swatch::SWATCH_INPUT_TYPE_DROPDOWN; + $data['frontend_input'] = 'select'; + break; + } + + return $data; + } +} diff --git a/app/code/Magento/Swatches/Plugin/Catalog/Model/Product/Attribute/RepositoryPlugin.php b/app/code/Magento/Swatches/Plugin/Catalog/Model/Product/Attribute/RepositoryPlugin.php new file mode 100644 index 0000000000000..cbc5ce7889110 --- /dev/null +++ b/app/code/Magento/Swatches/Plugin/Catalog/Model/Product/Attribute/RepositoryPlugin.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Plugin\Catalog\Model\Product\Attribute; + +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Model\Product\Attribute\Repository as ProductAttributeRepository; +use Magento\Swatches\Model\ConvertSwatchAttributeFrontendInput; + +/** + * Plugin for product attribute repository + */ +class RepositoryPlugin +{ + /** + * @var ConvertSwatchAttributeFrontendInput + */ + private $convertSwatchAttributeFrontendInput; + + /** + * @param ConvertSwatchAttributeFrontendInput $convertSwatchAttributeFrontendInput + */ + public function __construct( + ConvertSwatchAttributeFrontendInput $convertSwatchAttributeFrontendInput + ) { + $this->convertSwatchAttributeFrontendInput = $convertSwatchAttributeFrontendInput; + } + + /** + * Performs the conversion of the frontend input value. + * + * @param ProductAttributeRepository $subject + * @param ProductAttributeInterface $attribute + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeSave( + ProductAttributeRepository $subject, + ProductAttributeInterface $attribute + ): array { + $data = $attribute->getData(); + $data = $this->convertSwatchAttributeFrontendInput->execute($data); + $attribute->setData($data); + + return [$attribute]; + } +} diff --git a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml index b05c9cc9e7a9a..6070ae25f570f 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml @@ -18,4 +18,7 @@ <data key="default_label" unique="suffix">TextSwatchAttr</data> <data key="attribute_code" unique="suffix">text_swatch_attr</data> </entity> + <entity name="textSwatchProductAttribute" type="ProductAttribute" extends="productDropDownAttribute"> + <data key="frontend_input">swatch_text</data> + </entity> </entities> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCheckTextSwatchAttributeAddedViaApiTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCheckTextSwatchAttributeAddedViaApiTest.xml new file mode 100644 index 0000000000000..5e5515aa25a74 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCheckTextSwatchAttributeAddedViaApiTest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckTextSwatchAttributeAddedViaApiTest"> + <annotations> + <stories value="Add Swatch Text Product Attribute via API"/> + <title value="Add Swatch Text Product Attribute via API"/> + <description value="Login as admin, create swatch text product attribute.Go to New Product page, + check the created attribute is available on the page."/> + <group value="swatches"/> + </annotations> + <before> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <!-- Create an attribute with two options to be used in the first child product --> + <createData entity="textSwatchProductAttribute" stepKey="createTextSwatchConfigProductAttribute"/> + <!-- Add the attribute just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createTextSwatchConfigProductAttribute"/> + </createData> + </before> + <after> + <!-- Delete Created Data --> + <deleteData createDataKey="createTextSwatchConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="logout" stepKey="logout"/> + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + </after> + + <!-- Open the new simple product page --> + <actionGroup ref="AdminOpenNewProductFormPageActionGroup" stepKey="openNewProductPage"/> + <!-- Check created attribute presents on the page --> + <actionGroup ref="AssertAdminProductAttributeByCodeOnProductFormActionGroup" stepKey="checkTextSwatchConfigProductAttributeOnThePage"> + <argument name="productAttributeCode" value="$createTextSwatchConfigProductAttribute.attribute_code$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Swatches/Test/Unit/Controller/Adminhtml/Product/Attribute/Plugin/SaveTest.php b/app/code/Magento/Swatches/Test/Unit/Controller/Adminhtml/Product/Attribute/Plugin/SaveTest.php deleted file mode 100644 index c9c826b3a7831..0000000000000 --- a/app/code/Magento/Swatches/Test/Unit/Controller/Adminhtml/Product/Attribute/Plugin/SaveTest.php +++ /dev/null @@ -1,70 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Swatches\Test\Unit\Controller\Adminhtml\Product\Attribute\Plugin; - -class SaveTest extends \PHPUnit\Framework\TestCase -{ - /** - * @dataProvider dataRequest - */ - public function testBeforeDispatch($dataRequest, $runTimes) - { - $subject = $this->createMock(\Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save::class); - $request = $this->createPartialMock(\Magento\Framework\App\RequestInterface::class, [ - 'getPostValue', - 'setPostValue', - 'getModuleName', - 'setModuleName', - 'getActionName', - 'setActionName', - 'getParam', - 'setParams', - 'getParams', - 'getCookie', - 'isSecure' - ]); - - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $controller = $objectManager->getObject( - \Magento\Swatches\Controller\Adminhtml\Product\Attribute\Plugin\Save::class - ); - - $request->expects($this->once())->method('getPostValue')->willReturn($dataRequest); - $request->expects($this->exactly($runTimes))->method('setPostValue')->willReturn($this->returnSelf()); - - $controller->beforeDispatch($subject, $request); - } - - /** - * @return array - */ - public function dataRequest() - { - return [ - [ - ['frontend_input' => 'swatch_visual'], - 1 - ], - [ - ['frontend_input' => 'swatch_text'], - 1 - ], - [ - ['frontend_input' => 'select'], - 1 - ], - [ - [], - 0 - ], - [ - null, - 0 - ], - ]; - } -} diff --git a/app/code/Magento/Swatches/Test/Unit/Model/ConvertSwatchAttributeFrontendInputTest.php b/app/code/Magento/Swatches/Test/Unit/Model/ConvertSwatchAttributeFrontendInputTest.php new file mode 100644 index 0000000000000..1d27a33e5c244 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Unit/Model/ConvertSwatchAttributeFrontendInputTest.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Swatches\Test\Unit\Model; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Swatches\Model\Swatch; +use Magento\Swatches\Model\ConvertSwatchAttributeFrontendInput; + +/** + * Tests for \Magento\Swatches\Model\ConvertSwatchAttributeFrontendInput. + */ +class ConvertSwatchAttributeFrontendInputTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ConvertSwatchAttributeFrontendInput + */ + private $convertSwatchAttributeFrontendInput; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = new ObjectManager($this); + $this->convertSwatchAttributeFrontendInput = + $objectManager->getObject(ConvertSwatchAttributeFrontendInput::class); + } + + /** + * @dataProvider attributeData + */ + public function testExecute($inputData, $outputData) + { + $result = $this->convertSwatchAttributeFrontendInput->execute($inputData); + $this->assertEquals($outputData, $result); + } + + /** + * @return array + */ + public function attributeData() + { + return [ + [ + [ + 'frontend_input' => 'swatch_visual' + ], + [ + 'frontend_input' => 'select', + Swatch::SWATCH_INPUT_TYPE_KEY => Swatch::SWATCH_INPUT_TYPE_VISUAL, + ] + ], + [ + [ + 'frontend_input' => 'swatch_text' + ], + [ + 'frontend_input' => 'select', + Swatch::SWATCH_INPUT_TYPE_KEY => Swatch::SWATCH_INPUT_TYPE_TEXT, + 'use_product_image_for_swatch' => 0 + ] + ], + [ + [ + 'frontend_input' => 'select' + ], + [ + 'frontend_input' => 'select', + Swatch::SWATCH_INPUT_TYPE_KEY => Swatch::SWATCH_INPUT_TYPE_DROPDOWN, + ] + ], + [ + [], + [] + ], + [ + null, + null + ], + ]; + } +} diff --git a/app/code/Magento/Swatches/etc/webapi_rest/di.xml b/app/code/Magento/Swatches/etc/webapi_rest/di.xml new file mode 100644 index 0000000000000..f05b9727db28a --- /dev/null +++ b/app/code/Magento/Swatches/etc/webapi_rest/di.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Catalog\Model\Product\Attribute\Repository"> + <plugin name="swatches_product_attribute_repository_plugin" type="Magento\Swatches\Plugin\Catalog\Model\Product\Attribute\RepositoryPlugin"/> + </type> +</config> From aabff222676aa2c8c040c31f8f245c90910ab365 Mon Sep 17 00:00:00 2001 From: Alex Paliarush <paliarus@adobe.com> Date: Tue, 10 Mar 2020 10:05:56 -0500 Subject: [PATCH 242/369] Reverted: ECP-261 Offload Catalog Image Resizing from Magento ECP-262 Offload Catalog Image Watermarking from Magento ECP-263 Deprecate Rotation Support in Magento --- .../Magento/Catalog/Block/Rss/Category.php | 4 +- .../Catalog/Block/Rss/Product/NewProducts.php | 28 +- .../Catalog/Block/Rss/Product/Special.php | 15 +- .../Adminhtml/Product/Gallery/Upload.php | 2 +- app/code/Magento/Catalog/Helper/Image.php | 43 +- .../Model/Config/CatalogMediaConfig.php | 50 -- .../Source/Web/CatalogMediaUrlFormat.php | 30 -- .../Magento/Catalog/Model/Product/Image.php | 23 +- .../Catalog/Model/View/Asset/Image.php | 90 +--- .../Observer/ImageResizeAfterProductSave.php | 21 +- .../Helper/Form/Gallery/ContentTest.php | 442 ++++++++++++++++++ .../Test/Unit/Model/ImageUploaderTest.php | 154 ++++++ .../Model/View/Asset/Image/ContextTest.php | 76 +++ .../Test/Unit/Model/View/Asset/ImageTest.php | 213 +++++++++ .../Product/Listing/Collector/ImageTest.php | 13 +- .../Component/Listing/Columns/Thumbnail.php | 12 +- .../Product/Form/Modifier/Related.php | 6 +- .../Product/Listing/Collector/Image.php | 16 +- .../Magento/Catalog/etc/adminhtml/system.xml | 7 - app/code/Magento/Catalog/etc/config.xml | 5 - .../Checkout/CustomerData/DefaultItem.php | 8 +- .../Checkout/Model/DefaultConfigProvider.php | 2 +- .../Console/Command/ImagesResizeCommand.php | 6 +- .../Wishlist/CustomerData/Wishlist.php | 27 +- .../Test/Unit/CustomerData/WishlistTest.php | 6 + .../Block/Product/View/GalleryTest.php | 102 ---- .../ResourceModel/Catalog/ProductTest.php | 2 - lib/internal/Magento/Framework/Image.php | 15 +- .../Image/Adapter/AbstractAdapter.php | 4 +- .../Image/Adapter/AdapterInterface.php | 4 - .../Magento/Framework/Image/Adapter/Gd2.php | 1 - .../Framework/Image/Adapter/ImageMagick.php | 21 +- nginx.conf.sample | 25 - 33 files changed, 981 insertions(+), 492 deletions(-) delete mode 100644 app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php delete mode 100644 app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/View/Asset/Image/ContextTest.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php diff --git a/app/code/Magento/Catalog/Block/Rss/Category.php b/app/code/Magento/Catalog/Block/Rss/Category.php index f149114f2eab8..50967d2eb8dca 100644 --- a/app/code/Magento/Catalog/Block/Rss/Category.php +++ b/app/code/Magento/Catalog/Block/Rss/Category.php @@ -10,7 +10,9 @@ use Magento\Framework\Exception\NoSuchEntityException; /** - * Category feed block + * Class Category + * + * @package Magento\Catalog\Block\Rss * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ diff --git a/app/code/Magento/Catalog/Block/Rss/Product/NewProducts.php b/app/code/Magento/Catalog/Block/Rss/Product/NewProducts.php index 9ade8b198656c..20c4bef0845d6 100644 --- a/app/code/Magento/Catalog/Block/Rss/Product/NewProducts.php +++ b/app/code/Magento/Catalog/Block/Rss/Product/NewProducts.php @@ -8,7 +8,8 @@ use Magento\Framework\App\Rss\DataProviderInterface; /** - * New products feed block + * Class NewProducts + * @package Magento\Catalog\Block\Rss\Product */ class NewProducts extends \Magento\Framework\View\Element\AbstractBlock implements DataProviderInterface { @@ -54,8 +55,6 @@ public function __construct( } /** - * Init - * * @return void */ protected function _construct() @@ -65,7 +64,7 @@ protected function _construct() } /** - * @inheritdoc + * {@inheritdoc} */ public function isAllowed() { @@ -73,7 +72,7 @@ public function isAllowed() } /** - * @inheritdoc + * {@inheritdoc} */ public function getRssData() { @@ -98,13 +97,10 @@ public function getRssData() $item->setAllowedInRss(true); $item->setAllowedPriceInRss(true); - $this->_eventManager->dispatch( - 'rss_catalog_new_xml_callback', - [ - 'row' => $item->getData(), - 'product' => $item - ] - ); + $this->_eventManager->dispatch('rss_catalog_new_xml_callback', [ + 'row' => $item->getData(), + 'product' => $item + ]); if (!$item->getAllowedInRss()) { continue; @@ -136,8 +132,6 @@ public function getRssData() } /** - * Get store id - * * @return int */ protected function getStoreId() @@ -183,7 +177,7 @@ protected function renderPriceHtml(\Magento\Catalog\Model\Product $product) } /** - * @inheritdoc + * {@inheritdoc} */ public function getCacheLifetime() { @@ -191,8 +185,6 @@ public function getCacheLifetime() } /** - * Get feeds - * * @return array */ public function getFeeds() @@ -207,7 +199,7 @@ public function getFeeds() } /** - * @inheritdoc + * {@inheritdoc} */ public function isAuthRequired() { diff --git a/app/code/Magento/Catalog/Block/Rss/Product/Special.php b/app/code/Magento/Catalog/Block/Rss/Product/Special.php index 5e459413bb5a2..a9107f14cc5e4 100644 --- a/app/code/Magento/Catalog/Block/Rss/Product/Special.php +++ b/app/code/Magento/Catalog/Block/Rss/Product/Special.php @@ -9,7 +9,8 @@ use Magento\Framework\App\Rss\DataProviderInterface; /** - * Special products feed block + * Class Special + * @package Magento\Catalog\Block\Rss\Product * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Special extends \Magento\Framework\View\Element\AbstractBlock implements DataProviderInterface @@ -97,8 +98,6 @@ public function __construct( } /** - * Init - * * @return void */ protected function _construct() @@ -108,8 +107,6 @@ protected function _construct() } /** - * Get RSS data - * * @return array */ public function getRssData() @@ -159,8 +156,6 @@ public function getRssData() } /** - * Get entry data - * * @param \Magento\Catalog\Model\Product $item * @return array */ @@ -250,7 +245,7 @@ public function isAllowed() } /** - * @inheritdoc + * {@inheritdoc} */ public function getCacheLifetime() { @@ -258,8 +253,6 @@ public function getCacheLifetime() } /** - * Get feeds - * * @return array */ public function getFeeds() @@ -273,7 +266,7 @@ public function getFeeds() } /** - * @inheritdoc + * {@inheritdoc} */ public function isAuthRequired() { diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php index 3e7cc3ee962b9..d43b313c43b3e 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php @@ -11,7 +11,7 @@ use Magento\Framework\Exception\LocalizedException; /** - * Upload product image action controller + * Class Upload */ class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterface { diff --git a/app/code/Magento/Catalog/Helper/Image.php b/app/code/Magento/Catalog/Helper/Image.php index 5b0aa0c496ecd..110b798df9df9 100644 --- a/app/code/Magento/Catalog/Helper/Image.php +++ b/app/code/Magento/Catalog/Helper/Image.php @@ -5,19 +5,14 @@ */ namespace Magento\Catalog\Helper; -use Magento\Catalog\Model\Config\CatalogMediaConfig; -use Magento\Catalog\Model\View\Asset\PlaceholderFactory; use Magento\Framework\App\Helper\AbstractHelper; -use Magento\Framework\App\ObjectManager; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\View\Element\Block\ArgumentInterface; /** - * Catalog image helper. + * Catalog image helper * * @api * @SuppressWarnings(PHPMD.TooManyFields) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ class Image extends AbstractHelper implements ArgumentInterface @@ -45,7 +40,6 @@ class Image extends AbstractHelper implements ArgumentInterface * Scheduled for rotate image * * @var bool - * @deprecated unused */ protected $_scheduleRotate = false; @@ -53,7 +47,6 @@ class Image extends AbstractHelper implements ArgumentInterface * Angle * * @var int - * @deprecated unused */ protected $_angle; @@ -136,38 +129,31 @@ class Image extends AbstractHelper implements ArgumentInterface protected $attributes = []; /** - * @var PlaceholderFactory + * @var \Magento\Catalog\Model\View\Asset\PlaceholderFactory */ private $viewAssetPlaceholderFactory; - /** - * @var CatalogMediaConfig - */ - private $mediaConfig; - /** * @param \Magento\Framework\App\Helper\Context $context * @param \Magento\Catalog\Model\Product\ImageFactory $productImageFactory * @param \Magento\Framework\View\Asset\Repository $assetRepo * @param \Magento\Framework\View\ConfigInterface $viewConfig - * @param PlaceholderFactory $placeholderFactory - * @param CatalogMediaConfig $mediaConfig + * @param \Magento\Catalog\Model\View\Asset\PlaceholderFactory $placeholderFactory */ public function __construct( \Magento\Framework\App\Helper\Context $context, \Magento\Catalog\Model\Product\ImageFactory $productImageFactory, \Magento\Framework\View\Asset\Repository $assetRepo, \Magento\Framework\View\ConfigInterface $viewConfig, - PlaceholderFactory $placeholderFactory = null, - CatalogMediaConfig $mediaConfig = null + \Magento\Catalog\Model\View\Asset\PlaceholderFactory $placeholderFactory = null ) { $this->_productImageFactory = $productImageFactory; parent::__construct($context); $this->_assetRepo = $assetRepo; $this->viewConfig = $viewConfig; $this->viewAssetPlaceholderFactory = $placeholderFactory - ?: ObjectManager::getInstance()->get(PlaceholderFactory::class); - $this->mediaConfig = $mediaConfig ?: ObjectManager::getInstance()->get(CatalogMediaConfig::class); + ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Catalog\Model\View\Asset\PlaceholderFactory::class); } /** @@ -396,10 +382,9 @@ public function constrainOnly($flag) */ public function backgroundColor($colorRGB) { - $args = func_get_args(); // assume that 3 params were given instead of array if (!is_array($colorRGB)) { - $colorRGB = $args; + $colorRGB = func_get_args(); } $this->_getModel()->setBackgroundColor($colorRGB); return $this; @@ -410,7 +395,6 @@ public function backgroundColor($colorRGB) * * @param int $angle * @return $this - * @deprecated unused */ public function rotate($angle) { @@ -542,16 +526,7 @@ protected function isScheduledActionsAllowed() public function getUrl() { try { - switch ($this->mediaConfig->getMediaUrlFormat()) { - case CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS: - $this->initBaseFile(); - break; - case CatalogMediaConfig::HASH: - $this->applyScheduledActions(); - break; - default: - throw new LocalizedException(__("The specified Catalog media URL format is not supported.")); - } + $this->applyScheduledActions(); return $this->_getModel()->getUrl(); } catch (\Exception $e) { return $this->getDefaultPlaceholderUrl(); @@ -620,7 +595,6 @@ protected function _getModel() * * @param int $angle * @return $this - * @deprecated unused */ protected function setAngle($angle) { @@ -632,7 +606,6 @@ protected function setAngle($angle) * Get Rotation Angle * * @return int - * @deprecated unused */ protected function getAngle() { diff --git a/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php b/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php deleted file mode 100644 index 9e5394f0d6585..0000000000000 --- a/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php +++ /dev/null @@ -1,50 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Model\Config; - -use Magento\Framework\App\Config\ScopeConfigInterface; - -/** - * Config for catalog media - */ -class CatalogMediaConfig -{ - private const XML_PATH_CATALOG_MEDIA_URL_FORMAT = 'web/url/catalog_media_url_format'; - - const IMAGE_OPTIMIZATION_PARAMETERS = 'image_optimization_parameters'; - const HASH = 'hash'; - - /** - * @var ScopeConfigInterface - */ - private $scopeConfig; - - /** - * Constructor - * - * @param ScopeConfigInterface $scopeConfig - */ - public function __construct(ScopeConfigInterface $scopeConfig) - { - $this->scopeConfig = $scopeConfig; - } - - /** - * Get media URL format for catalog images - * - * @param string $scopeType - * @param null|int|string $scopeCode - * @return string - */ - public function getMediaUrlFormat($scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null) - { - return $this->scopeConfig->getValue( - CatalogMediaConfig::XML_PATH_CATALOG_MEDIA_URL_FORMAT, - $scopeType, - $scopeCode - ); - } -} diff --git a/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php b/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php deleted file mode 100644 index f24044fc92c95..0000000000000 --- a/app/code/Magento/Catalog/Model/Config/Source/Web/CatalogMediaUrlFormat.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Model\Config\Source\Web; - -use Magento\Catalog\Model\Config\CatalogMediaConfig; - -/** - * Option provider for catalog media URL format system setting. - */ -class CatalogMediaUrlFormat implements \Magento\Framework\Data\OptionSourceInterface -{ - /** - * Get a list of supported catalog media URL formats. - * - * @codeCoverageIgnore - */ - public function toOptionArray() - { - return [ - [ - 'value' => CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS, - 'label' => __('Image optimization based on query parameters') - ], - ['value' => CatalogMediaConfig::HASH, 'label' => __('Unique hash per image variant (Legacy mode)')] - ]; - } -} diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index 7c2a53768fd47..a0be36c5a327c 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -10,11 +10,9 @@ use Magento\Catalog\Model\View\Asset\PlaceholderFactory; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Image as MagentoImage; use Magento\Framework\Serialize\SerializerInterface; use Magento\Catalog\Model\Product\Image\ParamsBuilder; -use Magento\Framework\Filesystem\Driver\File as FilesystemDriver; /** * Image operations @@ -103,7 +101,6 @@ class Image extends \Magento\Framework\Model\AbstractModel /** * @var int - * @deprecated unused */ protected $_angle; @@ -202,11 +199,6 @@ class Image extends \Magento\Framework\Model\AbstractModel */ private $serializer; - /** - * @var FilesystemDriver - */ - private $filesystemDriver; - /** * Constructor * @@ -227,8 +219,6 @@ class Image extends \Magento\Framework\Model\AbstractModel * @param array $data * @param SerializerInterface $serializer * @param ParamsBuilder $paramsBuilder - * @param FilesystemDriver $filesystemDriver - * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ @@ -249,8 +239,7 @@ public function __construct( \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], SerializerInterface $serializer = null, - ParamsBuilder $paramsBuilder = null, - FilesystemDriver $filesystemDriver = null + ParamsBuilder $paramsBuilder = null ) { $this->_storeManager = $storeManager; $this->_catalogProductMediaConfig = $catalogProductMediaConfig; @@ -265,7 +254,6 @@ public function __construct( $this->viewAssetPlaceholderFactory = $viewAssetPlaceholderFactory; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); $this->paramsBuilder = $paramsBuilder ?: ObjectManager::getInstance()->get(ParamsBuilder::class); - $this->filesystemDriver = $filesystemDriver ?: ObjectManager::getInstance()->get(FilesystemDriver::class); } /** @@ -536,7 +524,6 @@ public function resize() * * @param int $angle * @return $this - * @deprecated unused */ public function rotate($angle) { @@ -552,7 +539,6 @@ public function rotate($angle) * * @param int $angle * @return $this - * @deprecated unused */ public function setAngle($angle) { @@ -677,12 +663,7 @@ public function getDestinationSubdir() public function isCached() { $path = $this->imageAsset->getPath(); - try { - $isCached = is_array($this->loadImageInfoFromCache($path)) || $this->filesystemDriver->isExists($path); - } catch (FileSystemException $e) { - $isCached = false; - } - return $isCached; + return is_array($this->loadImageInfoFromCache($path)) || file_exists($path); } /** diff --git a/app/code/Magento/Catalog/Model/View/Asset/Image.php b/app/code/Magento/Catalog/Model/View/Asset/Image.php index da1009ab1125c..c547ec612bb94 100644 --- a/app/code/Magento/Catalog/Model/View/Asset/Image.php +++ b/app/code/Magento/Catalog/Model/View/Asset/Image.php @@ -6,16 +6,11 @@ namespace Magento\Catalog\Model\View\Asset; -use Magento\Catalog\Model\Config\CatalogMediaConfig; use Magento\Catalog\Model\Product\Media\ConfigInterface; use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Encryption\EncryptorInterface; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\View\Asset\ContextInterface; use Magento\Framework\View\Asset\LocalInterface; -use Magento\Catalog\Helper\Image as ImageHelper; -use Magento\Framework\App\ObjectManager; -use Magento\Store\Model\StoreManagerInterface; /** * A locally available image file asset that can be referred with a file path @@ -63,21 +58,6 @@ class Image implements LocalInterface */ private $encryptor; - /** - * @var ImageHelper - */ - private $imageHelper; - - /** - * @var CatalogMediaConfig - */ - private $catalogMediaConfig; - - /** - * @var StoreManagerInterface - */ - private $storeManager; - /** * Image constructor. * @@ -86,19 +66,13 @@ class Image implements LocalInterface * @param EncryptorInterface $encryptor * @param string $filePath * @param array $miscParams - * @param ImageHelper $imageHelper - * @param CatalogMediaConfig $catalogMediaConfig - * @param StoreManagerInterface $storeManager */ public function __construct( ConfigInterface $mediaConfig, ContextInterface $context, EncryptorInterface $encryptor, $filePath, - array $miscParams, - ImageHelper $imageHelper = null, - CatalogMediaConfig $catalogMediaConfig = null, - StoreManagerInterface $storeManager = null + array $miscParams ) { if (isset($miscParams['image_type'])) { $this->sourceContentType = $miscParams['image_type']; @@ -111,72 +85,14 @@ public function __construct( $this->filePath = $filePath; $this->miscParams = $miscParams; $this->encryptor = $encryptor; - $this->imageHelper = $imageHelper ?: ObjectManager::getInstance()->get(ImageHelper::class); - $this->catalogMediaConfig = $catalogMediaConfig ?: ObjectManager::getInstance()->get(CatalogMediaConfig::class); - $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** - * Get catalog image URL. - * - * @return string - * @throws LocalizedException + * @inheritdoc */ public function getUrl() { - $mediaUrlFormat = $this->catalogMediaConfig->getMediaUrlFormat(); - switch ($mediaUrlFormat) { - case CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS: - return $this->getUrlWithTransformationParameters(); - case CatalogMediaConfig::HASH: - return $this->context->getBaseUrl() . DIRECTORY_SEPARATOR . $this->getImageInfo(); - default: - throw new LocalizedException( - __("The specified Catalog media URL format '$mediaUrlFormat' is not supported.") - ); - } - } - - /** - * Get image URL with transformation parameters - * - * @return string - */ - private function getUrlWithTransformationParameters() - { - return $this->getOriginalImageUrl() . '?' . http_build_query($this->getImageTransformationParameters()); - } - - /** - * The list of parameters to be used during image transformations (e.g. resizing or applying watermarks). - * - * This method can be used as an extension point. - * - * @return string[] - */ - public function getImageTransformationParameters() - { - return [ - 'width' => $this->miscParams['image_width'], - 'height' => $this->miscParams['image_height'], - 'store' => $this->storeManager->getStore()->getCode(), - 'image-type' => $this->sourceContentType - ]; - } - - /** - * Get URL to the original version of the product image. - * - * @return string - */ - private function getOriginalImageUrl() - { - $originalImageFile = $this->getSourceFile(); - if (!$originalImageFile) { - return $this->imageHelper->getDefaultPlaceholderUrl(); - } else { - return $this->context->getBaseUrl() . $this->getFilePath(); - } + return $this->context->getBaseUrl() . DIRECTORY_SEPARATOR . $this->getImageInfo(); } /** diff --git a/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php b/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php index 54b655a217a08..91d2868afab8c 100644 --- a/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php +++ b/app/code/Magento/Catalog/Observer/ImageResizeAfterProductSave.php @@ -10,11 +10,7 @@ use Magento\Framework\Event\ObserverInterface; use Magento\Framework\App\State; use Magento\MediaStorage\Service\ImageResize; -use Magento\Catalog\Model\Config\CatalogMediaConfig; -/** - * Resize product images after the product is saved - */ class ImageResizeAfterProductSave implements ObserverInterface { /** @@ -27,26 +23,17 @@ class ImageResizeAfterProductSave implements ObserverInterface */ private $state; - /** - * @var CatalogMediaConfig - */ - private $catalogMediaConfig; - /** * Product constructor. - * * @param ImageResize $imageResize * @param State $state - * @param CatalogMediaConfig $catalogMediaConfig */ public function __construct( ImageResize $imageResize, - State $state, - CatalogMediaConfig $catalogMediaConfig + State $state ) { $this->imageResize = $imageResize; $this->state = $state; - $this->catalogMediaConfig = $catalogMediaConfig; } /** @@ -57,12 +44,6 @@ public function __construct( */ public function execute(\Magento\Framework\Event\Observer $observer) { - $catalogMediaUrlFormat = $this->catalogMediaConfig->getMediaUrlFormat(); - if ($catalogMediaUrlFormat == CatalogMediaConfig::IMAGE_OPTIMIZATION_PARAMETERS) { - // Skip image resizing on the Magento side when it is offloaded to a web server or CDN - return; - } - /** @var $product \Magento\Catalog\Model\Product */ $product = $observer->getEvent()->getProduct(); diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php new file mode 100644 index 0000000000000..9a2199859a1df --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php @@ -0,0 +1,442 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Block\Adminhtml\Product\Helper\Form\Gallery; + +use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content; +use Magento\Catalog\Model\Entity\Attribute; +use Magento\Catalog\Model\Product; +use Magento\Framework\Phrase; +use Magento\MediaStorage\Helper\File\Storage\Database; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ContentTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + protected $fileSystemMock; + + /** + * @var \Magento\Framework\Filesystem\Directory\Read|\PHPUnit_Framework_MockObject_MockObject + */ + protected $readMock; + + /** + * @var Content|\PHPUnit_Framework_MockObject_MockObject + */ + protected $content; + + /** + * @var \Magento\Catalog\Model\Product\Media\Config|\PHPUnit_Framework_MockObject_MockObject + */ + protected $mediaConfigMock; + + /** + * @var \Magento\Framework\Json\EncoderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $jsonEncoderMock; + + /** + * @var \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery|\PHPUnit_Framework_MockObject_MockObject + */ + protected $galleryMock; + + /** + * @var \Magento\Catalog\Helper\Image|\PHPUnit_Framework_MockObject_MockObject + */ + protected $imageHelper; + + /** + * @var \Magento\MediaStorage\Helper\File\Storage\Database|\PHPUnit_Framework_MockObject_MockObject + */ + protected $databaseMock; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $objectManager; + + public function setUp() + { + $this->fileSystemMock = $this->createPartialMock( + \Magento\Framework\Filesystem::class, + ['stat', 'getDirectoryRead'] + ); + $this->readMock = $this->createMock(\Magento\Framework\Filesystem\Directory\ReadInterface::class); + $this->galleryMock = $this->createMock(\Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery::class); + $this->mediaConfigMock = $this->createPartialMock( + \Magento\Catalog\Model\Product\Media\Config::class, + ['getMediaUrl', 'getMediaPath'] + ); + $this->jsonEncoderMock = $this->getMockBuilder(\Magento\Framework\Json\EncoderInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->databaseMock = $this->getMockBuilder(Database::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->content = $this->objectManager->getObject( + \Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content::class, + [ + 'mediaConfig' => $this->mediaConfigMock, + 'jsonEncoder' => $this->jsonEncoderMock, + 'filesystem' => $this->fileSystemMock, + 'fileStorageDatabase' => $this->databaseMock + ] + ); + } + + public function testGetImagesJson() + { + $url = [ + ['file_1.jpg', 'url_to_the_image/image_1.jpg'], + ['file_2.jpg', 'url_to_the_image/image_2.jpg'] + ]; + $mediaPath = [ + ['file_1.jpg', 'catalog/product/image_1.jpg'], + ['file_2.jpg', 'catalog/product/image_2.jpg'] + ]; + + $sizeMap = [ + ['catalog/product/image_1.jpg', ['size' => 399659]], + ['catalog/product/image_2.jpg', ['size' => 879394]] + ]; + + $imagesResult = [ + [ + 'value_id' => '2', + 'file' => 'file_2.jpg', + 'media_type' => 'image', + 'position' => '0', + 'url' => 'url_to_the_image/image_2.jpg', + 'size' => 879394 + ], + [ + 'value_id' => '1', + 'file' => 'file_1.jpg', + 'media_type' => 'image', + 'position' => '1', + 'url' => 'url_to_the_image/image_1.jpg', + 'size' => 399659 + ] + ]; + + $images = [ + 'images' => [ + [ + 'value_id' => '1', + 'file' => 'file_1.jpg', + 'media_type' => 'image', + 'position' => '1' + ] , + [ + 'value_id' => '2', + 'file' => 'file_2.jpg', + 'media_type' => 'image', + 'position' => '0' + ] + ] + ]; + + $this->content->setElement($this->galleryMock); + $this->galleryMock->expects($this->once())->method('getImages')->willReturn($images); + $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->willReturn($this->readMock); + + $this->mediaConfigMock->expects($this->any())->method('getMediaUrl')->willReturnMap($url); + $this->mediaConfigMock->expects($this->any())->method('getMediaPath')->willReturnMap($mediaPath); + $this->readMock->expects($this->any())->method('stat')->willReturnMap($sizeMap); + $this->jsonEncoderMock->expects($this->once())->method('encode')->willReturnCallback('json_encode'); + + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(true)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(false)); + + $this->assertSame(json_encode($imagesResult), $this->content->getImagesJson()); + } + + public function testGetImagesJsonWithoutImages() + { + $this->content->setElement($this->galleryMock); + $this->galleryMock->expects($this->once())->method('getImages')->willReturn(null); + + $this->assertSame('[]', $this->content->getImagesJson()); + } + + public function testGetImagesJsonWithException() + { + $this->imageHelper = $this->getMockBuilder(\Magento\Catalog\Helper\Image::class) + ->disableOriginalConstructor() + ->setMethods(['getDefaultPlaceholderUrl']) + ->getMock(); + + $this->objectManager->setBackwardCompatibleProperty( + $this->content, + 'imageHelper', + $this->imageHelper + ); + + $placeholderUrl = 'url_to_the_placeholder/placeholder.jpg'; + + $imagesResult = [ + [ + 'value_id' => '2', + 'file' => 'file_2.jpg', + 'media_type' => 'image', + 'position' => '0', + 'url' => 'url_to_the_placeholder/placeholder.jpg', + 'size' => 0 + ], + [ + 'value_id' => '1', + 'file' => 'file_1.jpg', + 'media_type' => 'image', + 'position' => '1', + 'url' => 'url_to_the_placeholder/placeholder.jpg', + 'size' => 0 + ] + ]; + + $images = [ + 'images' => [ + [ + 'value_id' => '1', + 'file' => 'file_1.jpg', + 'media_type' => 'image', + 'position' => '1' + ], + [ + 'value_id' => '2', + 'file' => 'file_2.jpg', + 'media_type' => 'image', + 'position' => '0' + ] + ] + ]; + + $this->content->setElement($this->galleryMock); + $this->galleryMock->expects($this->once())->method('getImages')->willReturn($images); + $this->fileSystemMock->expects($this->any())->method('getDirectoryRead')->willReturn($this->readMock); + $this->mediaConfigMock->expects($this->any())->method('getMediaUrl'); + $this->mediaConfigMock->expects($this->any())->method('getMediaPath'); + + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(true)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(false)); + + $this->readMock->expects($this->any())->method('stat')->willReturnOnConsecutiveCalls( + $this->throwException( + new \Magento\Framework\Exception\FileSystemException(new Phrase('test')) + ), + $this->throwException( + new \Magento\Framework\Exception\FileSystemException(new Phrase('test')) + ) + ); + $this->imageHelper->expects($this->any())->method('getDefaultPlaceholderUrl')->willReturn($placeholderUrl); + $this->jsonEncoderMock->expects($this->once())->method('encode')->willReturnCallback('json_encode'); + + $this->assertSame(json_encode($imagesResult), $this->content->getImagesJson()); + } + + /** + * Test GetImageTypes() will return value for given attribute from data persistor. + * + * @return void + */ + public function testGetImageTypesFromDataPersistor() + { + $attributeCode = 'thumbnail'; + $value = 'testImageValue'; + $scopeLabel = 'testScopeLabel'; + $label = 'testLabel'; + $name = 'testName'; + $expectedTypes = [ + $attributeCode => [ + 'code' => $attributeCode, + 'value' => $value, + 'label' => $label, + 'name' => $name, + ], + ]; + $product = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + $product->expects($this->once()) + ->method('getData') + ->with($this->identicalTo($attributeCode)) + ->willReturn(null); + $mediaAttribute = $this->getMediaAttribute($label, $attributeCode); + $product->expects($this->once()) + ->method('getMediaAttributes') + ->willReturn([$mediaAttribute]); + $this->galleryMock->expects($this->exactly(2)) + ->method('getDataObject') + ->willReturn($product); + $this->galleryMock->expects($this->once()) + ->method('getImageValue') + ->with($this->identicalTo($attributeCode)) + ->willReturn($value); + $this->galleryMock->expects($this->once()) + ->method('getScopeLabel') + ->with($this->identicalTo($mediaAttribute)) + ->willReturn($scopeLabel); + $this->galleryMock->expects($this->once()) + ->method('getAttributeFieldName') + ->with($this->identicalTo($mediaAttribute)) + ->willReturn($name); + $this->getImageTypesAssertions($attributeCode, $scopeLabel, $expectedTypes); + } + + /** + * Test GetImageTypes() will return value for given attribute from product. + * + * @return void + */ + public function testGetImageTypesFromProduct() + { + $attributeCode = 'thumbnail'; + $value = 'testImageValue'; + $scopeLabel = 'testScopeLabel'; + $label = 'testLabel'; + $name = 'testName'; + $expectedTypes = [ + $attributeCode => [ + 'code' => $attributeCode, + 'value' => $value, + 'label' => $label, + 'name' => $name, + ], + ]; + $product = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + $product->expects($this->once()) + ->method('getData') + ->with($this->identicalTo($attributeCode)) + ->willReturn($value); + $mediaAttribute = $this->getMediaAttribute($label, $attributeCode); + $product->expects($this->once()) + ->method('getMediaAttributes') + ->willReturn([$mediaAttribute]); + $this->galleryMock->expects($this->exactly(2)) + ->method('getDataObject') + ->willReturn($product); + $this->galleryMock->expects($this->never()) + ->method('getImageValue'); + $this->galleryMock->expects($this->once()) + ->method('getScopeLabel') + ->with($this->identicalTo($mediaAttribute)) + ->willReturn($scopeLabel); + $this->galleryMock->expects($this->once()) + ->method('getAttributeFieldName') + ->with($this->identicalTo($mediaAttribute)) + ->willReturn($name); + $this->getImageTypesAssertions($attributeCode, $scopeLabel, $expectedTypes); + } + + /** + * Perform assertions. + * + * @param string $attributeCode + * @param string $scopeLabel + * @param array $expectedTypes + * @return void + */ + private function getImageTypesAssertions(string $attributeCode, string $scopeLabel, array $expectedTypes) + { + $this->content->setElement($this->galleryMock); + $result = $this->content->getImageTypes(); + $scope = $result[$attributeCode]['scope']; + $this->assertSame($scopeLabel, $scope->getText()); + unset($result[$attributeCode]['scope']); + $this->assertSame($expectedTypes, $result); + } + + /** + * Get media attribute mock. + * + * @param string $label + * @param string $attributeCode + * @return \PHPUnit_Framework_MockObject_MockObject + */ + private function getMediaAttribute(string $label, string $attributeCode) + { + $frontend = $this->getMockBuilder(Product\Attribute\Frontend\Image::class) + ->disableOriginalConstructor() + ->getMock(); + $frontend->expects($this->once()) + ->method('getLabel') + ->willReturn($label); + $mediaAttribute = $this->getMockBuilder(Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $mediaAttribute->expects($this->any()) + ->method('getAttributeCode') + ->willReturn($attributeCode); + $mediaAttribute->expects($this->once()) + ->method('getFrontend') + ->willReturn($frontend); + + return $mediaAttribute; + } + + /** + * Test GetImagesJson() calls MediaStorage functions to obtain image from DB prior to stat call + * + * @return void + */ + public function testGetImagesJsonMediaStorageMode() + { + $images = [ + 'images' => [ + [ + 'value_id' => '0', + 'file' => 'file_1.jpg', + 'media_type' => 'image', + 'position' => '0' + ] + ] + ]; + + $mediaPath = [ + ['file_1.jpg', 'catalog/product/image_1.jpg'] + ]; + + $this->content->setElement($this->galleryMock); + + $this->galleryMock->expects($this->once()) + ->method('getImages') + ->willReturn($images); + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->willReturn($this->readMock); + $this->mediaConfigMock->expects($this->any()) + ->method('getMediaPath') + ->willReturnMap($mediaPath); + + $this->readMock->expects($this->any()) + ->method('isFile') + ->will($this->returnValue(false)); + $this->databaseMock->expects($this->any()) + ->method('checkDbUsage') + ->will($this->returnValue(true)); + + $this->databaseMock->expects($this->once()) + ->method('saveFileToFilesystem') + ->with('catalog/product/image_1.jpg'); + + $this->content->getImagesJson(); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php new file mode 100644 index 0000000000000..6552e85440008 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php @@ -0,0 +1,154 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model; + +class ImageUploaderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Catalog\Model\ImageUploader + */ + private $imageUploader; + + /** + * Core file storage database + * + * @var \Magento\MediaStorage\Helper\File\Storage\Database|\PHPUnit_Framework_MockObject_MockObject + */ + private $coreFileStorageDatabaseMock; + + /** + * Media directory object (writable). + * + * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $mediaDirectoryMock; + + /** + * Media directory object (writable). + * + * @var \Magento\Framework\Filesystem\Directory\WriteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $mediaWriteDirectoryMock; + + /** + * Uploader factory + * + * @var \Magento\MediaStorage\Model\File\UploaderFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $uploaderFactoryMock; + + /** + * Store manager + * + * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + + /** + * Base tmp path + * + * @var string + */ + private $baseTmpPath; + + /** + * Base path + * + * @var string + */ + private $basePath; + + /** + * Allowed extensions + * + * @var array + */ + private $allowedExtensions; + + /** + * Allowed mime types + * + * @var array + */ + private $allowedMimeTypes; + + protected function setUp() + { + $this->coreFileStorageDatabaseMock = $this->createMock( + \Magento\MediaStorage\Helper\File\Storage\Database::class + ); + $this->mediaDirectoryMock = $this->createMock( + \Magento\Framework\Filesystem::class + ); + $this->mediaWriteDirectoryMock = $this->createMock( + \Magento\Framework\Filesystem\Directory\WriteInterface::class + ); + $this->mediaDirectoryMock->expects($this->any())->method('getDirectoryWrite')->willReturn( + $this->mediaWriteDirectoryMock + ); + $this->uploaderFactoryMock = $this->createMock( + \Magento\MediaStorage\Model\File\UploaderFactory::class + ); + $this->storeManagerMock = $this->createMock( + \Magento\Store\Model\StoreManagerInterface::class + ); + $this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class); + $this->baseTmpPath = 'base/tmp/'; + $this->basePath = 'base/real/'; + $this->allowedExtensions = ['.jpg']; + $this->allowedMimeTypes = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png']; + + $this->imageUploader = + new \Magento\Catalog\Model\ImageUploader( + $this->coreFileStorageDatabaseMock, + $this->mediaDirectoryMock, + $this->uploaderFactoryMock, + $this->storeManagerMock, + $this->loggerMock, + $this->baseTmpPath, + $this->basePath, + $this->allowedExtensions, + $this->allowedMimeTypes + ); + } + + public function testSaveFileToTmpDir() + { + $fileId = 'file.jpg'; + $allowedMimeTypes = [ + 'image/jpg', + 'image/jpeg', + 'image/gif', + 'image/png', + ]; + /** @var \Magento\MediaStorage\Model\File\Uploader|\PHPUnit_Framework_MockObject_MockObject $uploader */ + $uploader = $this->createMock(\Magento\MediaStorage\Model\File\Uploader::class); + $this->uploaderFactoryMock->expects($this->once())->method('create')->willReturn($uploader); + $uploader->expects($this->once())->method('setAllowedExtensions')->with($this->allowedExtensions); + $uploader->expects($this->once())->method('setAllowRenameFiles')->with(true); + $this->mediaWriteDirectoryMock->expects($this->once())->method('getAbsolutePath')->with($this->baseTmpPath) + ->willReturn($this->basePath); + $uploader->expects($this->once())->method('save')->with($this->basePath) + ->willReturn(['tmp_name' => $this->baseTmpPath, 'file' => $fileId, 'path' => $this->basePath]); + $uploader->expects($this->atLeastOnce())->method('checkMimeType')->with($allowedMimeTypes)->willReturn(true); + $storeMock = $this->createPartialMock( + \Magento\Store\Model\Store::class, + ['getBaseUrl'] + ); + $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); + $storeMock->expects($this->once())->method('getBaseUrl'); + $this->coreFileStorageDatabaseMock->expects($this->once())->method('saveFile'); + + $result = $this->imageUploader->saveFileToTmpDir($fileId); + + $this->assertArrayNotHasKey('path', $result); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/Image/ContextTest.php b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/Image/ContextTest.php new file mode 100644 index 0000000000000..e73a2f30e2b10 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/Image/ContextTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\View\Asset\Image; + +use Magento\Catalog\Model\Product\Media\ConfigInterface; +use Magento\Catalog\Model\View\Asset\Image\Context; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; + +/** + * Class ContextTest + */ +class ContextTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Context + */ + protected $model; + + /** + * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $mediaDirectory; + + /** + * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $mediaConfig; + + /** + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filesystem; + + protected function setUp() + { + $this->mediaConfig = $this->getMockBuilder(ConfigInterface::class)->getMockForAbstractClass(); + $this->mediaConfig->expects($this->any())->method('getBaseMediaPath')->willReturn('catalog/product'); + $this->mediaDirectory = $this->getMockBuilder(WriteInterface::class)->getMockForAbstractClass(); + $this->mediaDirectory->expects($this->once())->method('create')->with('catalog/product'); + $this->filesystem = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem->expects($this->once()) + ->method('getDirectoryWrite') + ->with(DirectoryList::MEDIA) + ->willReturn($this->mediaDirectory); + $this->model = new Context( + $this->mediaConfig, + $this->filesystem + ); + } + + public function testGetPath() + { + $path = '/var/www/html/magento2ce/pub/media/catalog/product'; + $this->mediaDirectory->expects($this->once()) + ->method('getAbsolutePath') + ->with('catalog/product') + ->willReturn($path); + + $this->assertEquals($path, $this->model->getPath()); + } + + public function testGetUrl() + { + $baseUrl = 'http://localhost/pub/media/catalog/product'; + $this->mediaConfig->expects($this->once())->method('getBaseMediaUrl')->willReturn($baseUrl); + + $this->assertEquals($baseUrl, $this->model->getBaseUrl()); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php new file mode 100644 index 0000000000000..6832d5b3399d7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/View/Asset/ImageTest.php @@ -0,0 +1,213 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\View\Asset; + +use Magento\Catalog\Model\Product\Media\ConfigInterface; +use Magento\Catalog\Model\View\Asset\Image; +use Magento\Framework\Encryption\Encryptor; +use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Asset\ContextInterface; +use Magento\Framework\View\Asset\Repository; + +/** + * Class ImageTest + */ +class ImageTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Catalog\Model\View\Asset\Image + */ + protected $model; + + /** + * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $mediaConfig; + + /** + * @var EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $encryptor; + + /** + * @var ContextInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $context; + + /** + * @var Repository|\PHPUnit_Framework_MockObject_MockObject + */ + private $assetRepo; + + private $objectManager; + + protected function setUp() + { + $this->mediaConfig = $this->createMock(ConfigInterface::class); + $this->encryptor = $this->createMock(EncryptorInterface::class); + $this->context = $this->createMock(ContextInterface::class); + $this->assetRepo = $this->createMock(Repository::class); + $this->objectManager = new ObjectManager($this); + $this->model = $this->objectManager->getObject( + Image::class, + [ + 'mediaConfig' => $this->mediaConfig, + 'imageContext' => $this->context, + 'encryptor' => $this->encryptor, + 'filePath' => '/somefile.png', + 'assetRepo' => $this->assetRepo, + 'miscParams' => [ + 'image_width' => 100, + 'image_height' => 50, + 'constrain_only' => false, + 'keep_aspect_ratio' => false, + 'keep_frame' => true, + 'keep_transparency' => false, + 'background' => '255,255,255', + 'image_type' => 'image', //thumbnail,small_image,image,swatch_image,swatch_thumb + 'quality' => 80, + 'angle' => null + ] + ] + ); + } + + public function testModuleAndContentAndContentType() + { + $contentType = 'image'; + $this->assertEquals($contentType, $this->model->getContentType()); + $this->assertEquals($contentType, $this->model->getSourceContentType()); + $this->assertNull($this->model->getContent()); + $this->assertEquals('cache', $this->model->getModule()); + } + + public function testGetFilePath() + { + $this->assertEquals('/somefile.png', $this->model->getFilePath()); + } + + public function testGetSoureFile() + { + $this->mediaConfig->expects($this->once())->method('getBaseMediaPath')->willReturn('catalog/product'); + $this->assertEquals('catalog/product/somefile.png', $this->model->getSourceFile()); + } + + public function testGetContext() + { + $this->assertInstanceOf(ContextInterface::class, $this->model->getContext()); + } + + /** + * @param string $filePath + * @param array $miscParams + * @param string $readableParams + * @dataProvider getPathDataProvider + */ + public function testGetPath($filePath, $miscParams, $readableParams) + { + $imageModel = $this->objectManager->getObject( + Image::class, + [ + 'mediaConfig' => $this->mediaConfig, + 'context' => $this->context, + 'encryptor' => $this->encryptor, + 'filePath' => $filePath, + 'assetRepo' => $this->assetRepo, + 'miscParams' => $miscParams + ] + ); + $absolutePath = '/var/www/html/magento2ce/pub/media/catalog/product'; + $hashPath = 'somehash'; + $this->context->method('getPath')->willReturn($absolutePath); + $this->encryptor->expects(static::once()) + ->method('hash') + ->with($readableParams, $this->anything()) + ->willReturn($hashPath); + static::assertEquals( + $absolutePath . '/cache/'. $hashPath . $filePath, + $imageModel->getPath() + ); + } + + /** + * @param string $filePath + * @param array $miscParams + * @param string $readableParams + * @dataProvider getPathDataProvider + */ + public function testGetUrl($filePath, $miscParams, $readableParams) + { + $imageModel = $this->objectManager->getObject( + Image::class, + [ + 'mediaConfig' => $this->mediaConfig, + 'context' => $this->context, + 'encryptor' => $this->encryptor, + 'filePath' => $filePath, + 'assetRepo' => $this->assetRepo, + 'miscParams' => $miscParams + ] + ); + $absolutePath = 'http://localhost/pub/media/catalog/product'; + $hashPath = 'somehash'; + $this->context->expects(static::once())->method('getBaseUrl')->willReturn($absolutePath); + $this->encryptor->expects(static::once()) + ->method('hash') + ->with($readableParams, $this->anything()) + ->willReturn($hashPath); + static::assertEquals( + $absolutePath . '/cache/' . $hashPath . $filePath, + $imageModel->getUrl() + ); + } + + /** + * @return array + */ + public function getPathDataProvider() + { + return [ + [ + '/some_file.png', + [], //default value for miscParams, + 'h:empty_w:empty_q:empty_r:empty_nonproportional_noframe_notransparency_notconstrainonly_nobackground', + ], + [ + '/some_file_2.png', + [ + 'image_type' => 'thumbnail', + 'image_height' => 75, + 'image_width' => 75, + 'keep_aspect_ratio' => true, + 'keep_frame' => true, + 'keep_transparency' => true, + 'constrain_only' => true, + 'background' => [233,1,0], + 'angle' => null, + 'quality' => 80, + ], + 'h:75_w:75_proportional_frame_transparency_doconstrainonly_rgb233,1,0_r:empty_q:80', + ], + [ + '/some_file_3.png', + [ + 'image_type' => 'thumbnail', + 'image_height' => 75, + 'image_width' => 75, + 'keep_aspect_ratio' => false, + 'keep_frame' => false, + 'keep_transparency' => false, + 'constrain_only' => false, + 'background' => [233,1,0], + 'angle' => 90, + 'quality' => 80, + ], + 'h:75_w:75_nonproportional_noframe_notransparency_notconstrainonly_rgb233,1,0_r:90_q:80', + ], + ]; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php index bd08a39fb2bed..009cd690d4cd4 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php @@ -99,6 +99,9 @@ public function testGet() ->method('create') ->willReturn($image); + $imageHelper->expects($this->once()) + ->method('getResizedImageInfo') + ->willReturn([11, 11]); $this->state->expects($this->once()) ->method('emulateAreaCode') ->with( @@ -108,14 +111,12 @@ public function testGet() ) ->willReturn($imageHelper); - $width = 5; - $height = 10; $imageHelper->expects($this->once()) ->method('getHeight') - ->willReturn($height); + ->willReturn(10); $imageHelper->expects($this->once()) ->method('getWidth') - ->willReturn($width); + ->willReturn(10); $imageHelper->expects($this->once()) ->method('getLabel') ->willReturn('Label'); @@ -131,10 +132,10 @@ public function testGet() ->with(); $image->expects($this->once()) ->method('setResizedHeight') - ->with($height); + ->with(11); $image->expects($this->once()) ->method('setResizedWidth') - ->with($width); + ->with(11); $productRenderInfoDto->expects($this->once()) ->method('setImages') diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php index 52773b4580256..09c9782fc0e32 100644 --- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php +++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php @@ -9,7 +9,7 @@ use Magento\Framework\View\Element\UiComponent\ContextInterface; /** - * Column with thumbnail images + * Class Thumbnail * * @api * @since 100.0.2 @@ -20,16 +20,6 @@ class Thumbnail extends \Magento\Ui\Component\Listing\Columns\Column const ALT_FIELD = 'name'; - /** - * @var \Magento\Catalog\Helper\Image - */ - private $imageHelper; - - /** - * @var \Magento\Framework\UrlInterface - */ - private $urlBuilder; - /** * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php index b822a5e3ef88a..b4acb93dcd14f 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Related.php @@ -25,7 +25,7 @@ use Magento\Catalog\Model\Product\Attribute\Source\Status; /** - * Related products modifier + * Class Related * * @api * @@ -143,7 +143,7 @@ public function __construct( } /** - * @inheritdoc + * {@inheritdoc} * @since 101.0.0 */ public function modifyMeta(array $meta) @@ -182,7 +182,7 @@ public function modifyMeta(array $meta) } /** - * @inheritdoc + * {@inheritdoc} * @since 101.0.0 */ public function modifyData(array $data) diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php index 45383ed51f6fc..d8f76c40e8fad 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php @@ -118,14 +118,18 @@ public function collect(ProductInterface $product, ProductRenderInterface $produ [$product, $imageCode, (int) $productRender->getStoreId(), $image] ); + try { + $resizedInfo = $helper->getResizedImageInfo(); + } catch (NotLoadInfoImageException $exception) { + $resizedInfo = [$helper->getWidth(), $helper->getHeight()]; + } + $image->setCode($imageCode); - $height = $helper->getHeight(); - $image->setHeight($height); - $width = $helper->getWidth(); - $image->setWidth($width); + $image->setHeight($helper->getHeight()); + $image->setWidth($helper->getWidth()); $image->setLabel($helper->getLabel()); - $image->setResizedHeight($height); - $image->setResizedWidth($width); + $image->setResizedHeight($resizedInfo[1]); + $image->setResizedWidth($resizedInfo[0]); $images[] = $image; } diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index f59990cdcea96..80b323cfdb250 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -208,13 +208,6 @@ <source_model>Magento\Catalog\Model\Config\Source\LayoutList</source_model> </field> </group> - <group id="url"> - <field id="catalog_media_url_format" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> - <label>Catalog media URL format</label> - <source_model>Magento\Catalog\Model\Config\Source\Web\CatalogMediaUrlFormat</source_model> - <comment><![CDATA[Images should be optimized based on query parameters by your CDN or web server. Use the legacy mode for backward compatibility. <a href="https://docs.magento.com/m2/ee/user_guide/configuration/general/web.html#url-options">Learn more</a> about catalog URL formats.<br/><br/><strong style="color:red">Warning!</strong> If you switch back to legacy mode, you must <a href="https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/themes/theme-images.html#resize-catalog-images">use the CLI to regenerate images</a>.]]></comment> - </field> - </group> </section> <section id="system" translate="label" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1"> <class>separator-top</class> diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index 68289904db0cf..59fc4b6d947d9 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -80,11 +80,6 @@ <thumbnail_position>stretch</thumbnail_position> </watermark> </design> - <web> - <url> - <catalog_media_url_format>hash</catalog_media_url_format> - </url> - </web> <general> <validator_data> <input_types> diff --git a/app/code/Magento/Checkout/CustomerData/DefaultItem.php b/app/code/Magento/Checkout/CustomerData/DefaultItem.php index 23d5827dc1916..21580d1275d0c 100644 --- a/app/code/Magento/Checkout/CustomerData/DefaultItem.php +++ b/app/code/Magento/Checkout/CustomerData/DefaultItem.php @@ -10,7 +10,7 @@ use Magento\Catalog\Model\Product\Configuration\Item\ItemResolverInterface; /** - * Default item in checkout customer data + * Default item */ class DefaultItem extends AbstractItem { @@ -78,7 +78,7 @@ public function __construct( } /** - * @inheritdoc + * {@inheritdoc} */ protected function doGetItemData() { @@ -121,8 +121,6 @@ protected function getOptionList() } /** - * Get product for thumbnail - * * @return \Magento\Catalog\Model\Product * @codeCoverageIgnore */ @@ -132,8 +130,6 @@ protected function getProductForThumbnail() } /** - * Get product - * * @return \Magento\Catalog\Model\Product * @codeCoverageIgnore */ diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php index 87585e4bf327f..fdf49d6765a29 100644 --- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php +++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php @@ -31,7 +31,7 @@ use Magento\Ui\Component\Form\Element\Multiline; /** - * Default Config Provider for checkout + * Default Config Provider * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) diff --git a/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php b/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php index d592a004e111a..4ed84829c2ad0 100644 --- a/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php +++ b/app/code/Magento/MediaStorage/Console/Command/ImagesResizeCommand.php @@ -84,11 +84,7 @@ public function __construct( protected function configure() { $this->setName('catalog:images:resize') - ->setDescription( - 'Creates resized product images ' . - '(Not relevant when image resizing is offloaded from Magento. ' . - 'See https://docs.magento.com/m2/ee/user_guide/configuration/general/web.html#url-options )' - ) + ->setDescription('Creates resized product images') ->setDefinition($this->getOptionsList()); } diff --git a/app/code/Magento/Wishlist/CustomerData/Wishlist.php b/app/code/Magento/Wishlist/CustomerData/Wishlist.php index 2f6b57a8650c4..ae54289d4b1c9 100644 --- a/app/code/Magento/Wishlist/CustomerData/Wishlist.php +++ b/app/code/Magento/Wishlist/CustomerData/Wishlist.php @@ -68,7 +68,7 @@ public function __construct( } /** - * @inheritdoc + * {@inheritdoc} */ public function getSectionData() { @@ -80,8 +80,6 @@ public function getSectionData() } /** - * Get counter - * * @return string */ protected function getCounter() @@ -158,6 +156,7 @@ protected function getItemData(\Magento\Wishlist\Model\Item $wishlistItem) * * @param \Magento\Catalog\Model\Product $product * @return array + * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function getImageData($product) { @@ -165,11 +164,27 @@ protected function getImageData($product) $helper = $this->imageHelperFactory->create() ->init($product, 'wishlist_sidebar_block'); + $template = 'Magento_Catalog/product/image_with_borders'; + + try { + $imagesize = $helper->getResizedImageInfo(); + } catch (NotLoadInfoImageException $exception) { + $imagesize = [$helper->getWidth(), $helper->getHeight()]; + } + + $width = $helper->getFrame() + ? $helper->getWidth() + : $imagesize[0]; + + $height = $helper->getFrame() + ? $helper->getHeight() + : $imagesize[1]; + return [ - 'template' => 'Magento_Catalog/product/image_with_borders', + 'template' => $template, 'src' => $helper->getUrl(), - 'width' => $helper->getWidth(), - 'height' => $helper->getHeight(), + 'width' => $width, + 'height' => $height, 'alt' => $helper->getLabel(), ]; } diff --git a/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php index 3ef2833ded21f..325922f0bc4e3 100644 --- a/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/CustomerData/WishlistTest.php @@ -193,6 +193,9 @@ public function testGetSectionData() $this->catalogImageHelperMock->expects($this->any()) ->method('getFrame') ->willReturn(true); + $this->catalogImageHelperMock->expects($this->once()) + ->method('getResizedImageInfo') + ->willReturn([]); $this->wishlistHelperMock->expects($this->once()) ->method('getProductUrl') @@ -391,6 +394,9 @@ public function testGetSectionDataWithTwoItems() $this->catalogImageHelperMock->expects($this->any()) ->method('getFrame') ->willReturn(true); + $this->catalogImageHelperMock->expects($this->exactly(2)) + ->method('getResizedImageInfo') + ->willReturn([]); $this->wishlistHelperMock->expects($this->exactly(2)) ->method('getProductUrl') diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/GalleryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/GalleryTest.php index a3545e4a39e80..9bcdb00eebe7c 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/GalleryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Product/View/GalleryTest.php @@ -120,23 +120,9 @@ public function testGetGalleryImagesJsonWithoutImages(): void $this->assertImages(reset($result), $this->placeholderExpectation); } - /** - * @magentoDataFixture Magento/Catalog/_files/product_simple.php - * @magentoConfigFixture default/web/url/catalog_media_url_format image_optimization_parameters - * @magentoDbIsolation enabled - * @return void - */ - public function testGetGalleryImagesJsonWithoutImagesWithImageOptimizationParametersInUrl(): void - { - $this->block->setData('product', $this->getProduct()); - $result = $this->serializer->unserialize($this->block->getGalleryImagesJson()); - $this->assertImages(reset($result), $this->placeholderExpectation); - } - /** * @dataProvider galleryDisabledImagesDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php - * @magentoConfigFixture default/web/url/catalog_media_url_format hash * @magentoDbIsolation enabled * @param array $images * @param array $expectation @@ -155,7 +141,6 @@ public function testGetGalleryImagesJsonWithDisabledImage(array $images, array $ * @dataProvider galleryDisabledImagesDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php * @magentoDataFixture Magento/Store/_files/second_store.php - * @magentoConfigFixture default/web/url/catalog_media_url_format hash * @magentoDbIsolation disabled * @param array $images * @param array $expectation @@ -188,8 +173,6 @@ public function galleryDisabledImagesDataProvider(): array } /** - * Test default image generation format. - * * @dataProvider galleryImagesDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php * @magentoDbIsolation enabled @@ -247,95 +230,10 @@ public function galleryImagesDataProvider(): array ]; } - /** - * @dataProvider galleryImagesWithImageOptimizationParametersInUrlDataProvider - * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php - * @magentoConfigFixture default/web/url/catalog_media_url_format image_optimization_parameters - * @magentoDbIsolation enabled - * @param array $images - * @param array $expectation - * @return void - */ - public function testGetGalleryImagesJsonWithImageOptimizationParametersInUrl( - array $images, - array $expectation - ): void { - $product = $this->getProduct(); - $this->setGalleryImages($product, $images); - $this->block->setData('product', $this->getProduct()); - [$firstImage, $secondImage] = $this->serializer->unserialize($this->block->getGalleryImagesJson()); - [$firstExpectedImage, $secondExpectedImage] = $expectation; - $this->assertImages($firstImage, $firstExpectedImage); - $this->assertImages($secondImage, $secondExpectedImage); - } - - /** - * @return array - */ - public function galleryImagesWithImageOptimizationParametersInUrlDataProvider(): array - { - - $imageExpectation = [ - 'thumb' => '/m/a/magento_image.jpg?width=88&height=110&store=default&image-type=thumbnail', - 'img' => '/m/a/magento_image.jpg?width=700&height=700&store=default&image-type=image', - 'full' => '/m/a/magento_image.jpg?store=default&image-type=image', - 'caption' => 'Image Alt Text', - 'position' => '1', - 'isMain' => false, - 'type' => 'image', - 'videoUrl' => null, - ]; - - $thumbnailExpectation = [ - 'thumb' => '/m/a/magento_thumbnail.jpg?width=88&height=110&store=default&image-type=thumbnail', - 'img' => '/m/a/magento_thumbnail.jpg?width=700&height=700&store=default&image-type=image', - 'full' => '/m/a/magento_thumbnail.jpg?store=default&image-type=image', - 'caption' => 'Thumbnail Image', - 'position' => '2', - 'isMain' => false, - 'type' => 'image', - 'videoUrl' => null, - ]; - - return [ - 'with_main_image' => [ - 'images' => [ - '/m/a/magento_image.jpg' => [], - '/m/a/magento_thumbnail.jpg' => ['main' => true], - ], - 'expectation' => [ - $imageExpectation, - array_merge($thumbnailExpectation, ['isMain' => true]), - ], - ], - 'without_main_image' => [ - 'images' => [ - '/m/a/magento_image.jpg' => [], - '/m/a/magento_thumbnail.jpg' => [], - ], - 'expectation' => [ - array_merge($imageExpectation, ['isMain' => true]), - $thumbnailExpectation, - ], - ], - 'with_changed_position' => [ - 'images' => [ - '/m/a/magento_image.jpg' => ['position' => '2'], - '/m/a/magento_thumbnail.jpg' => ['position' => '1'], - ], - 'expectation' => [ - array_merge($thumbnailExpectation, ['position' => '1']), - array_merge($imageExpectation, ['position' => '2', 'isMain' => true]), - ], - ], - ]; - } - /** * @dataProvider galleryImagesOnStoreViewDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_multiple_images.php * @magentoDataFixture Magento/Store/_files/second_store.php - * @magentoConfigFixture default/web/url/catalog_media_url_format hash * @magentoDbIsolation disabled * @param array $images * @param array $expectation diff --git a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php index 7d5e919880d3b..d6388b188a5fd 100644 --- a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php @@ -52,7 +52,6 @@ public function testGetCollectionNone() * 3) Check thumbnails when no thumbnail selected * * @magentoConfigFixture default_store sitemap/product/image_include all - * @magentoConfigFixture default/web/url/catalog_media_url_format hash */ public function testGetCollectionAll() { @@ -121,7 +120,6 @@ public function testGetCollectionAll() * 3) Check thumbnails when no thumbnail selected * * @magentoConfigFixture default_store sitemap/product/image_include base - * @magentoConfigFixture default/web/url/catalog_media_url_format hash */ public function testGetCollectionBase() { diff --git a/lib/internal/Magento/Framework/Image.php b/lib/internal/Magento/Framework/Image.php index 64cd009a84a3c..b3867c0197b79 100644 --- a/lib/internal/Magento/Framework/Image.php +++ b/lib/internal/Magento/Framework/Image.php @@ -49,7 +49,7 @@ public function open() $this->_adapter->checkDependencies(); if (!file_exists($this->_fileName)) { - throw new \RuntimeException("File '{$this->_fileName}' does not exist."); + throw new \Exception("File '{$this->_fileName}' does not exist."); } $this->_adapter->open($this->_fileName); @@ -85,7 +85,6 @@ public function save($destination = null, $newFileName = null) * @param int $angle * @access public * @return void - * @deprecated unused */ public function rotate($angle) { @@ -95,7 +94,7 @@ public function rotate($angle) /** * Crop an image. * - * @param int $top Default value is 0 + * @param int $top Default value is 0 * @param int $left Default value is 0 * @param int $right Default value is 0 * @param int $bottom Default value is 0 @@ -195,7 +194,7 @@ public function quality($value) * @param int $watermarkImageOpacity Watermark image opacity. * @param bool $repeat Enable or disable watermark brick. * @access public - * @throws \RuntimeException + * @throws \Exception * @return void */ public function watermark( @@ -206,7 +205,7 @@ public function watermark( $repeat = false ) { if (!file_exists($watermarkImage)) { - throw new \RuntimeException("Required file '{$watermarkImage}' does not exists."); + throw new \Exception("Required file '{$watermarkImage}' does not exists."); } $this->_adapter->watermark($watermarkImage, $positionX, $positionY, $watermarkImageOpacity, $repeat); } @@ -233,19 +232,16 @@ public function getImageType() return $this->_adapter->getImageType(); } - // phpcs:disable Magento2.CodeAnalysis.EmptyBlock /** * Process * - * @access public, + * @access public * @return void */ public function process() { } - // phpcs:enable Magento2.CodeAnalysis.EmptyBlock - // phpcs:disable Magento2.CodeAnalysis.EmptyBlock /** * Instruction * @@ -255,7 +251,6 @@ public function process() public function instruction() { } - // phpcs:enable Magento2.CodeAnalysis.EmptyBlock /** * Set image background color diff --git a/lib/internal/Magento/Framework/Image/Adapter/AbstractAdapter.php b/lib/internal/Magento/Framework/Image/Adapter/AbstractAdapter.php index 88dbd69405471..b06f2f9e62397 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/AbstractAdapter.php +++ b/lib/internal/Magento/Framework/Image/Adapter/AbstractAdapter.php @@ -41,6 +41,9 @@ abstract class AbstractAdapter implements AdapterInterface const POSITION_CENTER = 'center'; + /** + * Default font size + */ const DEFAULT_FONT_SIZE = 15; /** @@ -201,7 +204,6 @@ abstract public function resize($width = null, $height = null); * * @param int $angle * @return void - * @deprecated unused */ abstract public function rotate($angle); diff --git a/lib/internal/Magento/Framework/Image/Adapter/AdapterInterface.php b/lib/internal/Magento/Framework/Image/Adapter/AdapterInterface.php index 736686968b374..b31ed5c773495 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/AdapterInterface.php +++ b/lib/internal/Magento/Framework/Image/Adapter/AdapterInterface.php @@ -28,8 +28,6 @@ interface AdapterInterface public function getColorAt($x, $y); /** - * Render image and return its binary contents - * * @see \Magento\Framework\Image\Adapter\AbstractAdapter::getImage * @return string */ @@ -101,7 +99,6 @@ public function crop($top = 0, $left = 0, $right = 0, $bottom = 0); /** * Save image to specific path. - * * If some folders of path does not exist they will be created * * @param null|string $destination @@ -116,7 +113,6 @@ public function save($destination = null, $newName = null); * * @param int $angle * @return void - * @deprecated unused */ public function rotate($angle); } diff --git a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php index df236faf8173b..6a7a11846334d 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/Gd2.php +++ b/lib/internal/Magento/Framework/Image/Adapter/Gd2.php @@ -401,7 +401,6 @@ public function resize($frameWidth = null, $frameHeight = null) * * @param int $angle * @return void - * @deprecated unused */ public function rotate($angle) { diff --git a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php index a08d83d33b0ef..cd49f283d33a7 100644 --- a/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php +++ b/lib/internal/Magento/Framework/Image/Adapter/ImageMagick.php @@ -5,11 +5,6 @@ */ namespace Magento\Framework\Image\Adapter; -/** - * Wrapper for Imagick image processing PHP Extension. - * - * @link https://www.php.net/manual/en/book.imagick.php - */ class ImageMagick extends \Magento\Framework\Image\Adapter\AbstractAdapter { /** @@ -82,11 +77,7 @@ public function open($filename) try { $this->_imageHandler = new \Imagick($this->_fileName); } catch (\ImagickException $e) { - throw new \RuntimeException( - sprintf('Unsupported image format. File: %s', $this->_fileName), - $e->getCode(), - $e - ); + throw new \Exception(sprintf('Unsupported image format. File: %s', $this->_fileName), $e->getCode(), $e); } $this->backgroundColor(); @@ -95,7 +86,6 @@ public function open($filename) /** * Save image to specific path. - * * If some folders of path does not exist they will be created * * @param null|string $destination @@ -134,8 +124,6 @@ protected function _applyOptions() } /** - * Render image binary content and return it. - * * @see \Magento\Framework\Image\Adapter\AbstractAdapter::getImage * @return string */ @@ -207,7 +195,6 @@ public function resize($frameWidth = null, $frameHeight = null) * * @param int $angle * @return void - * @deprecated unused */ public function rotate($angle) { @@ -346,7 +333,7 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = ); } } catch (\ImagickException $e) { - throw new \RuntimeException('Unable to create watermark.', $e->getCode(), $e); + throw new \Exception('Unable to create watermark.', $e->getCode(), $e); } // merge layers @@ -359,12 +346,12 @@ public function watermark($imagePath, $positionX = 0, $positionY = 0, $opacity = * Checks required dependencies * * @return void - * @throws \RuntimeException If some of dependencies are missing + * @throws \Exception If some of dependencies are missing */ public function checkDependencies() { if (!class_exists('\Imagick', false)) { - throw new \RuntimeException("Required PHP extension 'Imagick' was not loaded."); + throw new \Exception("Required PHP extension 'Imagick' was not loaded."); } } diff --git a/nginx.conf.sample b/nginx.conf.sample index f045edb46a1c2..9219400f6aacd 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -26,9 +26,6 @@ ## ## In production mode, you should uncomment the 'expires' directive in the /static/ location block -# Modules can be loaded only at the very beginning of the Nginx config file, please move the line below to the main config file -# load_module /etc/nginx/modules/ngx_http_image_filter_module.so; - root $MAGE_ROOT/pub; index index.php; @@ -137,28 +134,6 @@ location /static/ { } location /media/ { - -## The following section allows to offload image resizing from Magento instance to the Nginx. -## Catalog image URL format should be set accordingly. -## See https://docs.magento.com/m2/ee/user_guide/configuration/general/web.html#url-options -# location ~* ^/media/catalog/.* { -# -# # Replace placeholders and uncomment the line below to serve product images from public S3 -# # See examples of S3 authentication at https://github.com/anomalizer/ngx_aws_auth -# # proxy_pass https://<bucket-name>.<region-name>.amazonaws.com; -# -# set $width "-"; -# set $height "-"; -# if ($arg_width != '') { -# set $width $arg_width; -# } -# if ($arg_height != '') { -# set $height $arg_height; -# } -# image_filter resize $width $height; -# image_filter_jpeg_quality 90; -# } - try_files $uri $uri/ /get.php$is_args$args; location ~ ^/media/theme_customization/.*\.xml { From 4229fa21fb02fd3df2ae5eae93bb14774ae2f042 Mon Sep 17 00:00:00 2001 From: Piotr Markiewicz <piotr.markiewicz@vaimo.com> Date: Tue, 10 Mar 2020 16:18:06 +0100 Subject: [PATCH 243/369] Unit Tests code review changes --- .../Adminhtml/Export/File/DeleteTest.php | 129 +++++++++-------- .../Adminhtml/Export/File/DownloadTest.php | 135 ++++++++++-------- 2 files changed, 145 insertions(+), 119 deletions(-) diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php index eceaab3d1d1f3..c6c472c977c07 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php @@ -3,119 +3,132 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ImportExport\Controller\Adminhtml\Export\File; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Framework\App\Request\Http; +use Magento\Framework\Controller\Result\Raw; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Filesystem\DriverInterface; +use Magento\Framework\Message\ManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class DeleteTest extends \PHPUnit\Framework\TestCase +class DeleteTest extends TestCase { /** - * @var \Magento\Backend\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|MockObject */ - protected $context; + private $contextMock; /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * @var ObjectManagerHelper */ - protected $objectManagerHelper; + private $objectManagerHelper; /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject + * @var Http|MockObject */ - protected $request; + private $requestMock; /** - * @var \Magento\Framework\Controller\Result\Raw|\PHPUnit_Framework_MockObject_MockObject + * @var Raw|MockObject */ - protected $redirect; + private $redirectMock; /** - * @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + * @var RedirectFactory|MockObject */ - protected $resultRedirectFactory; + private $resultRedirectFactoryMock; /** - * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + * @var Filesystem|MockObject */ - protected $fileSystem; + private $fileSystemMock; /** - * @var \Magento\Framework\Filesystem\DriverInterface|\PHPUnit_Framework_MockObject_MockObject + * @var DriverInterface|MockObject */ - protected $file; + private $fileMock; /** - * @var \Magento\ImportExport\Controller\Adminhtml\Export\File\Delete|\PHPUnit_Framework_MockObject_MockObject + * @var Delete|MockObject */ - protected $deleteController; + private $deleteControllerMock; /** - * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ManagerInterface|MockObject */ - protected $messageManager; + private $messageManagerMock; /** - * @var \Magento\Framework\Filesystem\Directory\ReadInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ReadInterface|MockObject */ - protected $directory; + private $directoryMock; /** * Set up */ protected function setUp() { - $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + $this->requestMock = $this->getMockBuilder(Http::class) ->disableOriginalConstructor() ->getMock(); - $this->fileSystem = $this->getMockBuilder(\Magento\Framework\Filesystem::class) + $this->fileSystemMock = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() ->getMock(); - $this->directory = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\ReadInterface::class) + $this->directoryMock = $this->getMockBuilder(ReadInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->file = $this->getMockBuilder(\Magento\Framework\Filesystem\DriverInterface::class) + $this->fileMock = $this->getMockBuilder(DriverInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->context = $this->createPartialMock( - \Magento\Backend\App\Action\Context::class, + $this->contextMock = $this->createPartialMock( + Context::class, ['getRequest', 'getResultRedirectFactory', 'getMessageManager'] ); - $this->redirect = $this->createPartialMock(\Magento\Backend\Model\View\Result\Redirect::class, ['setPath']); + $this->redirectMock = $this->createPartialMock(Redirect::class, ['setPath']); - $this->resultRedirectFactory = $this->createPartialMock( - \Magento\Framework\Controller\Result\RedirectFactory::class, + $this->resultRedirectFactoryMock = $this->createPartialMock( + RedirectFactory::class, ['create'] ); - $this->resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->redirect); - $this->context->expects($this->any())->method('getRequest')->willReturn($this->request); - $this->context->expects($this->any()) + $this->resultRedirectFactoryMock->expects($this->any())->method('create')->willReturn($this->redirectMock); + $this->contextMock->expects($this->any())->method('getRequest')->willReturn($this->requestMock); + $this->contextMock->expects($this->any()) ->method('getResultRedirectFactory') - ->willReturn($this->resultRedirectFactory); + ->willReturn($this->resultRedirectFactoryMock); - $this->context->expects($this->any()) + $this->contextMock->expects($this->any()) ->method('getMessageManager') - ->willReturn($this->messageManager); + ->willReturn($this->messageManagerMock); $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->deleteController = $this->objectManagerHelper->getObject( + $this->deleteControllerMock = $this->objectManagerHelper->getObject( Delete::class, [ - 'context' => $this->context, - 'filesystem' => $this->fileSystem, - 'file' => $this->file + 'context' => $this->contextMock, + 'filesystem' => $this->fileSystemMock, + 'file' => $this->fileMock ] ); } @@ -125,52 +138,52 @@ protected function setUp() */ public function testExecuteSuccess() { - $this->request->method('getParam') + $this->requestMock->method('getParam') ->with('filename') ->willReturn('sampleFile'); - $this->fileSystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); - $this->directory->expects($this->once())->method('isFile')->willReturn(true); - $this->file->expects($this->once())->method('deleteFile')->willReturn(true); - $this->messageManager->expects($this->once())->method('addSuccessMessage'); + $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directoryMock)); + $this->directoryMock->expects($this->once())->method('isFile')->willReturn(true); + $this->fileMock->expects($this->once())->method('deleteFile')->willReturn(true); + $this->messageManagerMock->expects($this->once())->method('addSuccessMessage'); - $this->deleteController->execute(); + $this->deleteControllerMock->execute(); } /** * Tests download controller with different file names in request. - */ public function testExecuteFileDoesntExists() { - $this->request->method('getParam') + $this->requestMock->method('getParam') ->with('filename') ->willReturn('sampleFile'); - $this->fileSystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); - $this->directory->expects($this->once())->method('isFile')->willReturn(false); - $this->messageManager->expects($this->once())->method('addErrorMessage'); + $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directoryMock)); + $this->directoryMock->expects($this->once())->method('isFile')->willReturn(false); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); - $this->deleteController->execute(); + $this->deleteControllerMock->execute(); } /** * Test execute() with invalid file name * @param string $requestFilename - * @dataProvider executeDataProvider + * @dataProvider invalidFileDataProvider */ public function testExecuteInvalidFileName($requestFilename) { - $this->request->method('getParam')->with('filename')->willReturn($requestFilename); - $this->messageManager->expects($this->once())->method('addErrorMessage'); + $this->requestMock->method('getParam')->with('filename')->willReturn($requestFilename); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); - $this->deleteController->execute(); + $this->deleteControllerMock->execute(); } /** + * Data provider to test possible invalid filenames * @return array */ - public function executeDataProvider() + public function invalidFileDataProvider() { return [ 'Relative file name' => ['../.htaccess'], diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php index e2b5395ac2231..71f6940ad7a86 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php @@ -3,127 +3,140 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ImportExport\Controller\Adminhtml\Export\File; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\View\Result\Redirect; +use Magento\Framework\App\Request\Http; +use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Framework\Controller\Result\Raw; +use Magento\Framework\Controller\Result\RedirectFactory; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Message\ManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class DownloadTest extends \PHPUnit\Framework\TestCase +class DownloadTest extends TestCase { /** - * @var \Magento\Backend\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject + * @var Context|MockObject */ - protected $context; + private $contextMock; /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * @var ObjectManagerHelper */ - protected $objectManagerHelper; + private $objectManagerHelper; /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject + * @var Http|MockObject */ - protected $request; + private $requestMock; /** - * @var \Magento\Framework\Controller\Result\Raw|\PHPUnit_Framework_MockObject_MockObject + * @var Raw|MockObject */ - protected $redirect; + private $redirectMock; /** - * @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject + * @var RedirectFactory|MockObject */ - protected $resultRedirectFactory; + private $resultRedirectFactoryMock; /** - * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject + * @var Filesystem|MockObject */ - protected $fileSystem; + private $fileSystemMock; /** - * @var \Magento\Framework\App\Response\Http\FileFactory|\PHPUnit_Framework_MockObject_MockObject + * @var FileFactory|MockObject */ - protected $fileFactory; + private $fileFactoryMock; /** - * @var \Magento\ImportExport\Controller\Adminhtml\Export\File\Download|\PHPUnit_Framework_MockObject_MockObject + * @var Download|MockObject */ - protected $downloadController; + private $downloadControllerMock; /** - * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ManagerInterface|MockObject */ - protected $messageManager; + private $messageManagerMock; /** - * @var \Magento\Framework\Filesystem\Directory\ReadInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ReadInterface|MockObject */ - protected $directory; + private $directoryMock; /** * Set up */ protected function setUp() { - $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + $this->requestMock = $this->getMockBuilder(Http::class) ->disableOriginalConstructor() ->getMock(); - $this->fileSystem = $this->getMockBuilder(\Magento\Framework\Filesystem::class) + $this->fileSystemMock = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() ->getMock(); - $this->directory = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\ReadInterface::class) + $this->directoryMock = $this->getMockBuilder(ReadInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->fileFactory = $this->getMockBuilder(\Magento\Framework\App\Response\Http\FileFactory::class) + $this->fileFactoryMock = $this->getMockBuilder(FileFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->messageManager = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) + $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->context = $this->createPartialMock( - \Magento\Backend\App\Action\Context::class, + $this->contextMock = $this->createPartialMock( + Context::class, ['getRequest', 'getResultRedirectFactory', 'getMessageManager'] ); - $this->redirect = $this->createPartialMock( - \Magento\Backend\Model\View\Result\Redirect::class, + $this->redirectMock = $this->createPartialMock( + Redirect::class, ['setPath'] ); - $this->resultRedirectFactory = $this->createPartialMock( - \Magento\Framework\Controller\Result\RedirectFactory::class, + $this->resultRedirectFactoryMock = $this->createPartialMock( + RedirectFactory::class, ['create'] ); - $this->resultRedirectFactory->expects($this->any()) + $this->resultRedirectFactoryMock->expects($this->any()) ->method('create') - ->willReturn($this->redirect); + ->willReturn($this->redirectMock); - $this->context->expects($this->any()) + $this->contextMock->expects($this->any()) ->method('getRequest') - ->willReturn($this->request); + ->willReturn($this->requestMock); - $this->context->expects($this->any()) + $this->contextMock->expects($this->any()) ->method('getResultRedirectFactory') - ->willReturn($this->resultRedirectFactory); + ->willReturn($this->resultRedirectFactoryMock); - $this->context->expects($this->any()) + $this->contextMock->expects($this->any()) ->method('getMessageManager') - ->willReturn($this->messageManager); + ->willReturn($this->messageManagerMock); $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->downloadController = $this->objectManagerHelper->getObject( + $this->downloadControllerMock = $this->objectManagerHelper->getObject( Download::class, [ - 'context' => $this->context, - 'filesystem' => $this->fileSystem, - 'fileFactory' => $this->fileFactory + 'context' => $this->contextMock, + 'filesystem' => $this->fileSystemMock, + 'fileFactory' => $this->fileFactoryMock ] ); } @@ -133,51 +146,51 @@ protected function setUp() */ public function testExecuteSuccess() { - $this->request->method('getParam') + $this->requestMock->method('getParam') ->with('filename') ->willReturn('sampleFile.csv'); - $this->fileSystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); - $this->directory->expects($this->once())->method('isFile')->willReturn(true); - $this->fileFactory->expects($this->once())->method('create'); + $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directoryMock)); + $this->directoryMock->expects($this->once())->method('isFile')->willReturn(true); + $this->fileFactoryMock->expects($this->once())->method('create'); - $this->downloadController->execute(); + $this->downloadControllerMock->execute(); } /** * Tests download controller with file that doesn't exist - */ public function testExecuteFileDoesntExists() { - $this->request->method('getParam') + $this->requestMock->method('getParam') ->with('filename') ->willReturn('sampleFile'); - $this->fileSystem->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directory)); - $this->directory->expects($this->once())->method('isFile')->willReturn(false); - $this->messageManager->expects($this->once())->method('addErrorMessage'); + $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directoryMock)); + $this->directoryMock->expects($this->once())->method('isFile')->willReturn(false); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); - $this->downloadController->execute(); + $this->downloadControllerMock->execute(); } /** * Test execute() with invalid file name - * @param string $requestFilename - * @dataProvider executeDataProvider + * @param ?string $requestFilename + * @dataProvider invalidFileDataProvider */ public function testExecuteInvalidFileName($requestFilename) { - $this->request->method('getParam')->with('filename')->willReturn($requestFilename); - $this->messageManager->expects($this->once())->method('addErrorMessage'); + $this->requestMock->method('getParam')->with('filename')->willReturn($requestFilename); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); - $this->downloadController->execute(); + $this->downloadControllerMock->execute(); } /** + * Data provider to test possible invalid filenames * @return array */ - public function executeDataProvider() + public function invalidFileDataProvider() { return [ 'Relative file name' => ['../.htaccess'], From 66fb07c196e94729b6592ba9fb4c4ee14d55c6ff Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 10 Mar 2020 12:21:50 -0500 Subject: [PATCH 244/369] MC-29423: GraphQL Send Friend is still sending emails when disabled --- .../Model/Resolver/SendEmailToFriend.php | 8 +- .../Resolver/SendFriendConfiguration.php | 46 ++++++++ .../SendFriendGraphQl/etc/schema.graphqls | 9 ++ .../GraphQl/SendFriend/SendFriendTest.php | 41 ++++--- .../GraphQl/SendFriend/StoreConfigTest.php | 106 ++++++++++++++++++ 5 files changed, 193 insertions(+), 17 deletions(-) create mode 100644 app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php diff --git a/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendEmailToFriend.php b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendEmailToFriend.php index 0a4fe1e3e5616..ebc1981ca965c 100644 --- a/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendEmailToFriend.php +++ b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendEmailToFriend.php @@ -48,8 +48,14 @@ public function __construct( */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { + $storeId = $context->getExtensionAttributes()->getStore()->getId(); + + if (!$this->sendFriendHelper->isEnabled($storeId)) { + throw new GraphQlInputException(__('"Email to a Friend" is not enabled.')); + } + /** @var ContextInterface $context */ - if (!$this->sendFriendHelper->isAllowForGuest() + if (!$this->sendFriendHelper->isAllowForGuest($storeId) && false === $context->getExtensionAttributes()->getIsCustomer() ) { throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); diff --git a/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php new file mode 100644 index 0000000000000..517a3454fac64 --- /dev/null +++ b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SendFriendGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\SendFriend\Helper\Data as SendFriendHelper; + +/** + * Resolve Store Config information for SendFriend + */ +class SendFriendConfiguration implements ResolverInterface +{ + /** + * @var SendFriendHelper + */ + private $sendFriendHelper; + + /** + * @param SendFriendHelper $sendFriendHelper + */ + public function __construct(SendFriendHelper $sendFriendHelper) + { + $this->sendFriendHelper = $sendFriendHelper; + } + + /** + * @inheritDoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + $store = $context->getExtensionAttributes()->getStore(); + $storeId = $store->getId(); + + return [ + 'enabled' => $this->sendFriendHelper->isEnabled($storeId), + 'allow_guest' => $this->sendFriendHelper->isAllowForGuest($storeId) + ]; + } +} diff --git a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls index 1234b65a7b910..9793d85e45c88 100644 --- a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls +++ b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls @@ -37,3 +37,12 @@ type SendEmailToFriendRecipient { name: String! email: String! } + +type StoreConfig { + sendFriend: SendFriendConfiguration @resolver(class: "\\Magento\\SendFriendGraphQl\\Model\\Resolver\\SendFriendConfiguration") @doc(description: "Config for 'Email to a Friend' feature") +} + +type SendFriendConfiguration { + enabled: Boolean! @doc(description: "Indicates whether or not the feature is enabled.") + allow_guest: Boolean! @doc(description: "Indicates whether or not the feature is allowed for guest customers.") +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php index e01e074900519..93001dd396cdc 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php @@ -45,6 +45,7 @@ protected function setUp() /** * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/allow_guest 1 */ public function testSendFriendGuestEnable() @@ -66,6 +67,7 @@ public function testSendFriendGuestEnable() /** * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/allow_guest 0 * @expectedException \Exception * @expectedExceptionMessage The current customer isn't authorized. @@ -90,9 +92,11 @@ public function testSendFriendGuestDisableAsGuest() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php - * @magentoConfigFixture default_store sendfriend/email/allow_guest 0 + * @magentoConfigFixture default_store sendfriend/email/enabled 0 + * @expectedException \Exception + * @expectedExceptionMessage "Email to a Friend" is not enabled. */ - public function testSendFriendGuestDisableAsCustomer() + public function testSendFriendDisableAsCustomer() { $productId = (int)$this->productRepository->get('simple_product')->getId(); $recipients = '{ @@ -111,6 +115,9 @@ public function testSendFriendGuestDisableAsCustomer() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 + * @expectedException \Exception + * @expectedExceptionMessage The product that was requested doesn't exist. Verify the product and try again. */ public function testSendWithoutExistProduct() { @@ -125,15 +132,13 @@ public function testSendWithoutExistProduct() }'; $query = $this->getQuery($productId, $recipients); - $this->expectExceptionMessage( - 'The product that was requested doesn\'t exist. Verify the product and try again.' - ); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 */ public function testMaxSendEmailToFriend() { @@ -176,6 +181,7 @@ public function testMaxSendEmailToFriend() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @dataProvider sendFriendsErrorsDataProvider * @param string $input * @param string $errorMessage @@ -188,7 +194,7 @@ public function testErrors(string $input, string $errorMessage) sendEmailToFriend( input: { $input - } + } ) { sender { name @@ -210,6 +216,7 @@ public function testErrors(string $input, string $errorMessage) /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/max_per_hour 1 * @magentoApiDataFixture Magento/SendFriend/Fixtures/sendfriend_configuration.php */ @@ -238,6 +245,7 @@ public function testLimitMessagesPerHour() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 */ public function testSendProductWithoutSenderEmail() { @@ -256,6 +264,7 @@ public function testSendProductWithoutSenderEmail() /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product_without_visibility.php + * @magentoConfigFixture default_store sendfriend/email/enabled 1 */ public function testSendProductWithoutVisibility() { @@ -282,12 +291,12 @@ public function sendFriendsErrorsDataProvider() { return [ [ - 'product_id: 1 + 'product_id: 1 sender: { name: "Name" email: "e@mail.com" message: "Lorem Ipsum" - } + } recipients: [ { name: "" @@ -300,12 +309,12 @@ public function sendFriendsErrorsDataProvider() ]', 'Please provide Name for all of recipients.' ], [ - 'product_id: 1 + 'product_id: 1 sender: { name: "Name" email: "e@mail.com" message: "Lorem Ipsum" - } + } recipients: [ { name: "Recipient Name 1" @@ -318,12 +327,12 @@ public function sendFriendsErrorsDataProvider() ]', 'Please provide Email for all of recipients.' ], [ - 'product_id: 1 + 'product_id: 1 sender: { name: "" email: "e@mail.com" message: "Lorem Ipsum" - } + } recipients: [ { name: "Recipient Name 1" @@ -336,12 +345,12 @@ public function sendFriendsErrorsDataProvider() ]', 'Please provide Name of sender.' ], [ - 'product_id: 1 + 'product_id: 1 sender: { name: "Name" email: "e@mail.com" message: "" - } + } recipients: [ { name: "Recipient Name 1" @@ -403,9 +412,9 @@ private function getQuery(int $productId, string $recipients): string name: "Name" email: "e@mail.com" message: "Lorem Ipsum" - } + } recipients: [{$recipients}] - } + } ) { sender { name diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php new file mode 100644 index 0000000000000..3c64ee6e73fb1 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\SendFriend; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test SendFriend configuration resolves correctly in StoreConfig + */ +class StoreConfigTest extends GraphQlAbstract +{ + public function testSendFriendFieldsAreReturnedWithoutError() + { + $query = $this->getQuery(); + + $response = $this->graphQlQuery($query); + $this->assertArrayNotHasKey('errors', $response); + $this->assertArrayHasKey('sendFriend', $response['storeConfig']); + $this->assertArrayHasKey('enabled', $response['storeConfig']['sendFriend']); + $this->assertArrayHasKey('allow_guest', $response['storeConfig']['sendFriend']); + $this->assertNotNull($response['storeConfig']['sendFriend']['enabled']); + $this->assertNotNull($response['storeConfig']['sendFriend']['allow_guest']); + } + + /** + * @magentoConfigFixture default_store sendfriend/email/enabled 0 + */ + public function testSendFriendDisabled() + { + $response = $this->graphQlQuery($this->getQuery()); + + $this->assertResponse( + ['enabled' => false, 'allow_guest' => false], + $response + ); + } + + /** + * @magentoConfigFixture default_store sendfriend/email/enabled 1 + * @magentoConfigFixture default_store sendfriend/email/allow_guest 0 + */ + public function testSendFriendEnabledGuestDisabled() + { + $response = $this->graphQlQuery($this->getQuery()); + + $this->assertResponse( + ['enabled' => true, 'allow_guest' => false], + $response + ); + } + + /** + * @magentoConfigFixture default_store sendfriend/email/enabled 1 + * @magentoConfigFixture default_store sendfriend/email/allow_guest 1 + */ + public function testSendFriendEnabledGuestEnabled() + { + $response = $this->graphQlQuery($this->getQuery()); + + $this->assertResponse( + ['enabled' => true, 'allow_guest' => true], + $response + ); + } + + /** + * Assert response matches expected output + * + * @param array $expectedValues + * @param array $response + */ + private function assertResponse(array $expectedValues, array $response) + { + $this->assertArrayNotHasKey('errors', $response); + $this->assertArrayHasKey('sendFriend', $response['storeConfig']); + $this->assertArrayHasKey('enabled', $response['storeConfig']['sendFriend']); + $this->assertArrayHasKey('allow_guest', $response['storeConfig']['sendFriend']); + $this->assertEquals($expectedValues['enabled'], $response['storeConfig']['sendFriend']['enabled']); + $this->assertEquals($expectedValues['allow_guest'], $response['storeConfig']['sendFriend']['allow_guest']); + } + + /** + * Return simple storeConfig query to get sendFriend configuration + * + * @return string + */ + private function getQuery() + { + return <<<QUERY +{ + storeConfig{ + id + sendFriend { + enabled + allow_guest + } + } +} +QUERY; + } +} From 647080652c61b7adbb760284dc78e1f6e9fdced8 Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 10 Mar 2020 13:39:39 -0500 Subject: [PATCH 245/369] MC-31987: Varnish graphql cache has to skip authenticated requests - Modified vcl to not cache authorized requests for graphql --- app/code/Magento/PageCache/etc/varnish4.vcl | 7 ++++++- app/code/Magento/PageCache/etc/varnish5.vcl | 7 ++++++- app/code/Magento/PageCache/etc/varnish6.vcl | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 6de6b4e917044..6723cd988417a 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -108,6 +108,11 @@ sub vcl_recv { #unset req.http.Cookie; } + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=" && req.http.Authorization ~ "^Bearer") { + # Authentificated customers should not be cached by default + return (pass); + } + return (hash); } @@ -123,7 +128,7 @@ sub vcl_hash { hash_data(server.ip); } - if (req.url ~ "/graphql") { + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { call process_graphql_headers; } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 4505e74629714..3cc75adb8cfe9 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -109,6 +109,11 @@ sub vcl_recv { #unset req.http.Cookie; } + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=" && req.http.Authorization ~ "^Bearer") { + # Authentificated customers should not be cached by default + return (pass); + } + return (hash); } @@ -130,7 +135,7 @@ sub vcl_hash { } /* {{ design_exceptions_code }} */ - if (req.url ~ "/graphql") { + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=" ) { call process_graphql_headers; } } diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index b43c8a77bca74..fbe7f4d62f1c7 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -109,6 +109,11 @@ sub vcl_recv { #unset req.http.Cookie; } + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=" && req.http.Authorization ~ "^Bearer") { + # Authentificated customers should not be cached by default + return (pass); + } + return (hash); } @@ -130,7 +135,7 @@ sub vcl_hash { } /* {{ design_exceptions_code }} */ - if (req.url ~ "/graphql") { + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { call process_graphql_headers; } } From 19b9bfb88b11b75a7b4da46f08a92e6e68e43472 Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 10 Mar 2020 13:46:50 -0500 Subject: [PATCH 246/369] MC-31987: Varnish graphql cache has to skip authenticated requests - (Forward Port) MC-31679: FPC doesn't work with Varnish enabled to 2.4 --- .../HTTP/PhpEnvironment/Response.php | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php index dc3e63fcc7df8..f12de83c52474 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php @@ -183,27 +183,4 @@ public function __sleep() { return ['content', 'isRedirect', 'statusCode']; } - - /** - * Sending provided headers. - * - * Had to be overridden because the original did not work correctly with multi-headers. - */ - public function sendHeaders() - { - if ($this->headersSent()) { - return $this; - } - - $status = $this->renderStatusLine(); - header($status); - - /** @var \Zend\Http\Header\HeaderInterface $header */ - foreach ($this->getHeaders() as $header) { - header($header->toString(), false); - } - - $this->headersSent = true; - return $this; - } } From dee14ee7278a75191aa97d4f4c0bf7f0fda863b8 Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 10 Mar 2020 14:31:38 -0500 Subject: [PATCH 247/369] MC-31987: Varnish graphql cache has to skip authenticated requests - review fix --- app/code/Magento/PageCache/etc/varnish4.vcl | 8 +++++--- app/code/Magento/PageCache/etc/varnish5.vcl | 8 +++++--- app/code/Magento/PageCache/etc/varnish6.vcl | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 6723cd988417a..7226d07e5f48c 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -108,9 +108,11 @@ sub vcl_recv { #unset req.http.Cookie; } - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=" && req.http.Authorization ~ "^Bearer") { - # Authentificated customers should not be cached by default - return (pass); + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { + # Authentificated customers should not be cached by default + if (req.http.Authorization ~ "^Bearer") { + return (pass); + } } return (hash); diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 3cc75adb8cfe9..267ca5633f66d 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -109,9 +109,11 @@ sub vcl_recv { #unset req.http.Cookie; } - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=" && req.http.Authorization ~ "^Bearer") { - # Authentificated customers should not be cached by default - return (pass); + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { + # Authentificated customers should not be cached by default + if (req.http.Authorization ~ "^Bearer") { + return (pass); + } } return (hash); diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index fbe7f4d62f1c7..bf56d00749b2e 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -109,9 +109,11 @@ sub vcl_recv { #unset req.http.Cookie; } - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=" && req.http.Authorization ~ "^Bearer") { + if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { # Authentificated customers should not be cached by default - return (pass); + if (req.http.Authorization ~ "^Bearer") { + return (pass); + } } return (hash); From 187ac76e853915ce59666aceb1a56267b001bf8b Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 10 Mar 2020 14:32:26 -0500 Subject: [PATCH 248/369] MC-31987: Varnish graphql cache has to skip authenticated requests - typo --- app/code/Magento/PageCache/etc/varnish4.vcl | 2 +- app/code/Magento/PageCache/etc/varnish5.vcl | 2 +- app/code/Magento/PageCache/etc/varnish6.vcl | 2 +- vendor/.htaccess | 7 ------- 4 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 vendor/.htaccess diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 7226d07e5f48c..235ce4cfc0bb4 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -109,7 +109,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authentificated customers should not be cached by default + # Authenticated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 267ca5633f66d..740019eebdb08 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -110,7 +110,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authentificated customers should not be cached by default + # Authenticated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index bf56d00749b2e..d0764e799f013 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -110,7 +110,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authentificated customers should not be cached by default + # Authenticated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/vendor/.htaccess b/vendor/.htaccess deleted file mode 100644 index b97408bad3f2e..0000000000000 --- a/vendor/.htaccess +++ /dev/null @@ -1,7 +0,0 @@ -<IfVersion < 2.4> - order allow,deny - deny from all -</IfVersion> -<IfVersion >= 2.4> - Require all denied -</IfVersion> From f59b93fca4f286617a2e20be4f0fb328cd2e1b3c Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 10 Mar 2020 14:34:54 -0500 Subject: [PATCH 249/369] Revert "MC-31987: Varnish graphql cache has to skip authenticated requests" This reverts commit 187ac76e853915ce59666aceb1a56267b001bf8b. --- app/code/Magento/PageCache/etc/varnish4.vcl | 2 +- app/code/Magento/PageCache/etc/varnish5.vcl | 2 +- app/code/Magento/PageCache/etc/varnish6.vcl | 2 +- vendor/.htaccess | 7 +++++++ 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 vendor/.htaccess diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 235ce4cfc0bb4..7226d07e5f48c 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -109,7 +109,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authenticated customers should not be cached by default + # Authentificated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 740019eebdb08..267ca5633f66d 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -110,7 +110,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authenticated customers should not be cached by default + # Authentificated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index d0764e799f013..bf56d00749b2e 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -110,7 +110,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authenticated customers should not be cached by default + # Authentificated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/vendor/.htaccess b/vendor/.htaccess new file mode 100644 index 0000000000000..b97408bad3f2e --- /dev/null +++ b/vendor/.htaccess @@ -0,0 +1,7 @@ +<IfVersion < 2.4> + order allow,deny + deny from all +</IfVersion> +<IfVersion >= 2.4> + Require all denied +</IfVersion> From dc96b5759934a5ca1eea06ce478eedaf52c186db Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 10 Mar 2020 14:35:42 -0500 Subject: [PATCH 250/369] MC-31987: Varnish graphql cache has to skip authenticated requests - typo fix --- app/code/Magento/PageCache/etc/varnish4.vcl | 2 +- app/code/Magento/PageCache/etc/varnish5.vcl | 2 +- app/code/Magento/PageCache/etc/varnish6.vcl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 7226d07e5f48c..235ce4cfc0bb4 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -109,7 +109,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authentificated customers should not be cached by default + # Authenticated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 267ca5633f66d..740019eebdb08 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -110,7 +110,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authentificated customers should not be cached by default + # Authenticated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index bf56d00749b2e..d0764e799f013 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -110,7 +110,7 @@ sub vcl_recv { } if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authentificated customers should not be cached by default + # Authenticated customers should not be cached by default if (req.http.Authorization ~ "^Bearer") { return (pass); } From de81e6201e14459cc24286fce39a3c86980b8836 Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 10 Mar 2020 16:05:12 -0500 Subject: [PATCH 251/369] MC-31987: Varnish graphql cache has to skip authenticated requests - review fix --- app/code/Magento/PageCache/etc/varnish4.vcl | 10 ++++------ app/code/Magento/PageCache/etc/varnish5.vcl | 10 ++++------ app/code/Magento/PageCache/etc/varnish6.vcl | 10 ++++------ 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 235ce4cfc0bb4..9f3ecdc56083f 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -108,11 +108,9 @@ sub vcl_recv { #unset req.http.Cookie; } - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authenticated customers should not be cached by default - if (req.http.Authorization ~ "^Bearer") { - return (pass); - } + # Authenticated graphQL customers should not be cached by default + if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { + return (pass); } return (hash); @@ -130,7 +128,7 @@ sub vcl_hash { hash_data(server.ip); } - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { + if (req.url ~ "/graphql") { call process_graphql_headers; } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 740019eebdb08..2b6a8b69e9792 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -109,11 +109,9 @@ sub vcl_recv { #unset req.http.Cookie; } - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authenticated customers should not be cached by default - if (req.http.Authorization ~ "^Bearer") { - return (pass); - } + # Authenticated graphQL customers should not be cached by default + if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { + return (pass); } return (hash); @@ -137,7 +135,7 @@ sub vcl_hash { } /* {{ design_exceptions_code }} */ - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=" ) { + if (req.url ~ "/graphql") { call process_graphql_headers; } } diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index d0764e799f013..be2895d526373 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -109,11 +109,9 @@ sub vcl_recv { #unset req.http.Cookie; } - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { - # Authenticated customers should not be cached by default - if (req.http.Authorization ~ "^Bearer") { - return (pass); - } + # Authenticated graphQL customers should not be cached by default + if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { + return (pass); } return (hash); @@ -137,7 +135,7 @@ sub vcl_hash { } /* {{ design_exceptions_code }} */ - if (req.method == "GET" && req.url ~ "/graphql" && req.url ~ "query=") { + if (req.url ~ "/graphql") { call process_graphql_headers; } } From ce1f70291b2f3d2b3a34a9bff936b6c090339927 Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 10 Mar 2020 16:14:23 -0500 Subject: [PATCH 252/369] MC-31987: Varnish graphql cache has to skip authenticated requests - review fix --- app/code/Magento/PageCache/etc/varnish4.vcl | 2 +- app/code/Magento/PageCache/etc/varnish5.vcl | 2 +- app/code/Magento/PageCache/etc/varnish6.vcl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl index 9f3ecdc56083f..f5e25ce36e973 100644 --- a/app/code/Magento/PageCache/etc/varnish4.vcl +++ b/app/code/Magento/PageCache/etc/varnish4.vcl @@ -108,7 +108,7 @@ sub vcl_recv { #unset req.http.Cookie; } - # Authenticated graphQL customers should not be cached by default + # Authenticated GraphQL requests should not be cached by default if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl index 2b6a8b69e9792..92bb3394486fc 100644 --- a/app/code/Magento/PageCache/etc/varnish5.vcl +++ b/app/code/Magento/PageCache/etc/varnish5.vcl @@ -109,7 +109,7 @@ sub vcl_recv { #unset req.http.Cookie; } - # Authenticated graphQL customers should not be cached by default + # Authenticated GraphQL requests should not be cached by default if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { return (pass); } diff --git a/app/code/Magento/PageCache/etc/varnish6.vcl b/app/code/Magento/PageCache/etc/varnish6.vcl index be2895d526373..eef5e99862538 100644 --- a/app/code/Magento/PageCache/etc/varnish6.vcl +++ b/app/code/Magento/PageCache/etc/varnish6.vcl @@ -109,7 +109,7 @@ sub vcl_recv { #unset req.http.Cookie; } - # Authenticated graphQL customers should not be cached by default + # Authenticated GraphQL requests should not be cached by default if (req.url ~ "/graphql" && req.http.Authorization ~ "^Bearer") { return (pass); } From fcda68c7cec7c4e24e92a0338ce65c7758dd3709 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 10 Mar 2020 16:21:33 -0500 Subject: [PATCH 253/369] MC-29423: GraphQL Send Friend is still sending emails when disabled - rename storeConfig fields --- .../Resolver/SendFriendConfiguration.php | 4 +-- .../SendFriendGraphQl/etc/schema.graphqls | 6 ++-- .../GraphQl/SendFriend/StoreConfigTest.php | 36 +++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php index 517a3454fac64..7149dccdec834 100644 --- a/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php +++ b/app/code/Magento/SendFriendGraphQl/Model/Resolver/SendFriendConfiguration.php @@ -39,8 +39,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $storeId = $store->getId(); return [ - 'enabled' => $this->sendFriendHelper->isEnabled($storeId), - 'allow_guest' => $this->sendFriendHelper->isAllowForGuest($storeId) + 'enabled_for_customers' => $this->sendFriendHelper->isEnabled($storeId), + 'enabled_for_guests' => $this->sendFriendHelper->isAllowForGuest($storeId) ]; } } diff --git a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls index 9793d85e45c88..7dc28c54cf6ef 100644 --- a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls +++ b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls @@ -39,10 +39,10 @@ type SendEmailToFriendRecipient { } type StoreConfig { - sendFriend: SendFriendConfiguration @resolver(class: "\\Magento\\SendFriendGraphQl\\Model\\Resolver\\SendFriendConfiguration") @doc(description: "Config for 'Email to a Friend' feature") + sendFriend: SendFriendConfiguration @resolver(class: "\\Magento\\SendFriendGraphQl\\Model\\Resolver\\SendFriendConfiguration") @doc(description: "Email to a Friend configuration.") } type SendFriendConfiguration { - enabled: Boolean! @doc(description: "Indicates whether or not the feature is enabled.") - allow_guest: Boolean! @doc(description: "Indicates whether or not the feature is allowed for guest customers.") + enabled_for_customers: Boolean! @doc(description: "Indicates whether the Email to a Friend feature is enabled.") + enabled_for_guests: Boolean! @doc(description: "Indicates whether the Email to a Friend feature is enabled for guests.") } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php index 3c64ee6e73fb1..73855f63a0945 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php @@ -16,15 +16,15 @@ class StoreConfigTest extends GraphQlAbstract { public function testSendFriendFieldsAreReturnedWithoutError() { - $query = $this->getQuery(); + $query = $this->getStoreConfigQuery(); $response = $this->graphQlQuery($query); $this->assertArrayNotHasKey('errors', $response); $this->assertArrayHasKey('sendFriend', $response['storeConfig']); - $this->assertArrayHasKey('enabled', $response['storeConfig']['sendFriend']); - $this->assertArrayHasKey('allow_guest', $response['storeConfig']['sendFriend']); - $this->assertNotNull($response['storeConfig']['sendFriend']['enabled']); - $this->assertNotNull($response['storeConfig']['sendFriend']['allow_guest']); + $this->assertArrayHasKey('enabled_for_customers', $response['storeConfig']['sendFriend']); + $this->assertArrayHasKey('enabled_for_guests', $response['storeConfig']['sendFriend']); + $this->assertNotNull($response['storeConfig']['sendFriend']['enabled_for_customers']); + $this->assertNotNull($response['storeConfig']['sendFriend']['enabled_for_guests']); } /** @@ -32,10 +32,10 @@ public function testSendFriendFieldsAreReturnedWithoutError() */ public function testSendFriendDisabled() { - $response = $this->graphQlQuery($this->getQuery()); + $response = $this->graphQlQuery($this->getStoreConfigQuery()); $this->assertResponse( - ['enabled' => false, 'allow_guest' => false], + ['enabled_for_customers' => false, 'enabled_for_guests' => false], $response ); } @@ -46,10 +46,10 @@ public function testSendFriendDisabled() */ public function testSendFriendEnabledGuestDisabled() { - $response = $this->graphQlQuery($this->getQuery()); + $response = $this->graphQlQuery($this->getStoreConfigQuery()); $this->assertResponse( - ['enabled' => true, 'allow_guest' => false], + ['enabled_for_customers' => true, 'enabled_for_guests' => false], $response ); } @@ -60,10 +60,10 @@ public function testSendFriendEnabledGuestDisabled() */ public function testSendFriendEnabledGuestEnabled() { - $response = $this->graphQlQuery($this->getQuery()); + $response = $this->graphQlQuery($this->getStoreConfigQuery()); $this->assertResponse( - ['enabled' => true, 'allow_guest' => true], + ['enabled_for_customers' => true, 'enabled_for_guests' => true], $response ); } @@ -78,10 +78,10 @@ private function assertResponse(array $expectedValues, array $response) { $this->assertArrayNotHasKey('errors', $response); $this->assertArrayHasKey('sendFriend', $response['storeConfig']); - $this->assertArrayHasKey('enabled', $response['storeConfig']['sendFriend']); - $this->assertArrayHasKey('allow_guest', $response['storeConfig']['sendFriend']); - $this->assertEquals($expectedValues['enabled'], $response['storeConfig']['sendFriend']['enabled']); - $this->assertEquals($expectedValues['allow_guest'], $response['storeConfig']['sendFriend']['allow_guest']); + $this->assertArrayHasKey('enabled_for_customers', $response['storeConfig']['sendFriend']); + $this->assertArrayHasKey('enabled_for_guests', $response['storeConfig']['sendFriend']); + $this->assertEquals($expectedValues['enabled_for_customers'], $response['storeConfig']['sendFriend']['enabled_for_customers']); + $this->assertEquals($expectedValues['enabled_for_guests'], $response['storeConfig']['sendFriend']['enabled_for_guests']); } /** @@ -89,15 +89,15 @@ private function assertResponse(array $expectedValues, array $response) * * @return string */ - private function getQuery() + private function getStoreConfigQuery() { return <<<QUERY { storeConfig{ id sendFriend { - enabled - allow_guest + enabled_for_customers + enabled_for_guests } } } From f91aff175c6a3e4701e69601a68d3333da702e0e Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Wed, 11 Mar 2020 10:06:20 +0200 Subject: [PATCH 254/369] 26749: Saving CMS Page Title from REST web API makes content empty --- app/code/Magento/Cms/Model/PageRepository.php | 24 ++- app/code/Magento/Cms/etc/di.xml | 1 + .../Magento/Cms/Api/PageRepositoryTest.php | 195 ++++++++++++------ 3 files changed, 157 insertions(+), 63 deletions(-) diff --git a/app/code/Magento/Cms/Model/PageRepository.php b/app/code/Magento/Cms/Model/PageRepository.php index 72f07771f59d4..2de44b6691274 100644 --- a/app/code/Magento/Cms/Model/PageRepository.php +++ b/app/code/Magento/Cms/Model/PageRepository.php @@ -9,19 +9,21 @@ use Magento\Cms\Api\Data; use Magento\Cms\Api\PageRepositoryInterface; use Magento\Cms\Model\Page\IdentityMap; +use Magento\Cms\Model\ResourceModel\Page as ResourcePage; +use Magento\Cms\Model\ResourceModel\Page\CollectionFactory as PageCollectionFactory; use Magento\Framework\Api\DataObjectHelper; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\HydratorInterface; use Magento\Framework\Exception\CouldNotDeleteException; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Reflection\DataObjectProcessor; -use Magento\Cms\Model\ResourceModel\Page as ResourcePage; -use Magento\Cms\Model\ResourceModel\Page\CollectionFactory as PageCollectionFactory; use Magento\Store\Model\StoreManagerInterface; /** - * Class PageRepository + * @inheritdoc + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class PageRepository implements PageRepositoryInterface @@ -76,6 +78,11 @@ class PageRepository implements PageRepositoryInterface */ private $identityMap; + /** + * @var HydratorInterface + */ + private $hydrator; + /** * @param ResourcePage $resource * @param PageFactory $pageFactory @@ -87,6 +94,7 @@ class PageRepository implements PageRepositoryInterface * @param StoreManagerInterface $storeManager * @param CollectionProcessorInterface $collectionProcessor * @param IdentityMap|null $identityMap + * @param HydratorInterface|null $hydrator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -99,7 +107,8 @@ public function __construct( DataObjectProcessor $dataObjectProcessor, StoreManagerInterface $storeManager, CollectionProcessorInterface $collectionProcessor = null, - ?IdentityMap $identityMap = null + ?IdentityMap $identityMap = null, + ?HydratorInterface $hydrator = null ) { $this->resource = $resource; $this->pageFactory = $pageFactory; @@ -111,6 +120,7 @@ public function __construct( $this->storeManager = $storeManager; $this->collectionProcessor = $collectionProcessor ?: $this->getCollectionProcessor(); $this->identityMap = $identityMap ?? ObjectManager::getInstance()->get(IdentityMap::class); + $this->hydrator = $hydrator ?: ObjectManager::getInstance()->get(HydratorInterface::class); } /** @@ -150,8 +160,13 @@ public function save(\Magento\Cms\Api\Data\PageInterface $page) $storeId = $this->storeManager->getStore()->getId(); $page->setStoreId($storeId); } + $pageId = $page->getId(); + try { $this->validateLayoutUpdate($page); + if ($pageId) { + $page = $this->hydrator->hydrate($this->getById($pageId), $this->hydrator->extract($page)); + } $this->resource->save($page); $this->identityMap->add($page); } catch (\Exception $exception) { @@ -248,6 +263,7 @@ private function getCollectionProcessor() { if (!$this->collectionProcessor) { $this->collectionProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get( + // phpstan:ignore "Class Magento\Cms\Model\Api\SearchCriteria\PageCollectionProcessor not found." \Magento\Cms\Model\Api\SearchCriteria\PageCollectionProcessor::class ); } diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 869bd22e20548..7fc8268eea5e0 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -193,6 +193,7 @@ <type name="Magento\Cms\Model\PageRepository"> <arguments> <argument name="collectionProcessor" xsi:type="object">Magento\Cms\Model\Api\SearchCriteria\PageCollectionProcessor</argument> + <argument name="hydrator" xsi:type="object">Magento\Framework\EntityManager\AbstractModelHydrator</argument> </arguments> </type> <virtualType name="Magento\Cms\Model\Api\SearchCriteria\CollectionProcessor\BlockFilterProcessor" type="Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor"> diff --git a/dev/tests/api-functional/testsuite/Magento/Cms/Api/PageRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Cms/Api/PageRepositoryTest.php index 015eb067e4c8e..bff2ba3ce7aac 100644 --- a/dev/tests/api-functional/testsuite/Magento/Cms/Api/PageRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Cms/Api/PageRepositoryTest.php @@ -6,14 +6,18 @@ namespace Magento\Cms\Api; use Magento\Authorization\Model\Role; -use Magento\Authorization\Model\Rules; use Magento\Authorization\Model\RoleFactory; +use Magento\Authorization\Model\Rules; use Magento\Authorization\Model\RulesFactory; use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Api\Data\PageInterfaceFactory; +use Magento\Framework\Api\DataObjectHelper; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\Api\SortOrder; use Magento\Framework\Api\SortOrderBuilder; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Framework\Webapi\Rest\Request; use Magento\Integration\Api\AdminTokenServiceInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\WebapiAbstract; @@ -25,34 +29,39 @@ */ class PageRepositoryTest extends WebapiAbstract { - const SERVICE_NAME = 'cmsPageRepositoryV1'; - const SERVICE_VERSION = 'V1'; - const RESOURCE_PATH = '/V1/cmsPage'; + private const PAGE_TITLE = 'Page title'; + private const PAGE_TITLE_NEW = 'Page title new'; + private const PAGE_CONTENT = '<h1>Some content</h1>'; + private const PAGE_IDENTIFIER_PREFIX = 'page-'; + + private const SERVICE_NAME = 'cmsPageRepositoryV1'; + private const SERVICE_VERSION = 'V1'; + private const RESOURCE_PATH = '/V1/cmsPage'; /** - * @var \Magento\Cms\Api\Data\PageInterfaceFactory + * @var PageInterfaceFactory */ - protected $pageFactory; + private $pageFactory; /** - * @var \Magento\Cms\Api\PageRepositoryInterface + * @var PageRepositoryInterface */ - protected $pageRepository; + private $pageRepository; /** - * @var \Magento\Framework\Api\DataObjectHelper + * @var DataObjectHelper */ - protected $dataObjectHelper; + private $dataObjectHelper; /** - * @var \Magento\Framework\Reflection\DataObjectProcessor + * @var DataObjectProcessor */ - protected $dataObjectProcessor; + private $dataObjectProcessor; /** - * @var \Magento\Cms\Api\Data\PageInterface|null + * @var PageInterface|null */ - protected $currentPage; + private $currentPage; /** * @var RoleFactory @@ -70,39 +79,49 @@ class PageRepositoryTest extends WebapiAbstract private $adminTokens; /** - * Execute per test initialization. + * @var array + */ + private $createdPages = []; + + /** + * @inheritdoc */ public function setUp() { - $this->pageFactory = Bootstrap::getObjectManager()->create(\Magento\Cms\Api\Data\PageInterfaceFactory::class); - $this->pageRepository = Bootstrap::getObjectManager()->create(\Magento\Cms\Api\PageRepositoryInterface::class); - $this->dataObjectHelper = Bootstrap::getObjectManager()->create(\Magento\Framework\Api\DataObjectHelper::class); - $this->dataObjectProcessor = Bootstrap::getObjectManager() - ->create(\Magento\Framework\Reflection\DataObjectProcessor::class); + $this->pageFactory = Bootstrap::getObjectManager()->create(PageInterfaceFactory::class); + $this->pageRepository = Bootstrap::getObjectManager()->create(PageRepositoryInterface::class); + $this->dataObjectHelper = Bootstrap::getObjectManager()->create(DataObjectHelper::class); + $this->dataObjectProcessor = Bootstrap::getObjectManager()->create(DataObjectProcessor::class); $this->roleFactory = Bootstrap::getObjectManager()->get(RoleFactory::class); $this->rulesFactory = Bootstrap::getObjectManager()->get(RulesFactory::class); $this->adminTokens = Bootstrap::getObjectManager()->get(AdminTokenServiceInterface::class); } /** - * Clear temporary data + * @inheritdoc */ - public function tearDown() + protected function tearDown(): void { if ($this->currentPage) { $this->pageRepository->delete($this->currentPage); $this->currentPage = null; } + + foreach ($this->createdPages as $page) { + $this->pageRepository->delete($page); + } } /** - * Test get \Magento\Cms\Api\Data\PageInterface + * Test get page + * + * @return void */ - public function testGet() + public function testGet(): void { - $pageTitle = 'Page title'; - $pageIdentifier = 'page-title' . uniqid(); - /** @var \Magento\Cms\Api\Data\PageInterface $pageDataObject */ + $pageTitle = self::PAGE_TITLE; + $pageIdentifier = self::PAGE_IDENTIFIER_PREFIX . uniqid(); + /** @var PageInterface $pageDataObject */ $pageDataObject = $this->pageFactory->create(); $pageDataObject->setTitle($pageTitle) ->setIdentifier($pageIdentifier); @@ -111,7 +130,7 @@ public function testGet() $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH . '/' . $this->currentPage->getId(), - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + 'httpMethod' => Request::HTTP_METHOD_GET, ], 'soap' => [ 'service' => self::SERVICE_NAME, @@ -129,13 +148,15 @@ public function testGet() } /** - * Test create \Magento\Cms\Api\Data\PageInterface + * Test create page + * + * @return void */ - public function testCreate() + public function testCreate(): void { - $pageTitle = 'Page title'; - $pageIdentifier = 'page-title' . uniqid(); - /** @var \Magento\Cms\Api\Data\PageInterface $pageDataObject */ + $pageTitle = self::PAGE_TITLE; + $pageIdentifier = self::PAGE_IDENTIFIER_PREFIX . uniqid(); + /** @var PageInterface $pageDataObject */ $pageDataObject = $this->pageFactory->create(); $pageDataObject->setTitle($pageTitle) ->setIdentifier($pageIdentifier); @@ -143,7 +164,7 @@ public function testCreate() $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + 'httpMethod' => Request::HTTP_METHOD_POST, ], 'soap' => [ 'service' => self::SERVICE_NAME, @@ -152,7 +173,8 @@ public function testCreate() ], ]; - $requestData = ['page' => [ + $requestData = [ + 'page' => [ PageInterface::IDENTIFIER => $pageDataObject->getIdentifier(), PageInterface::TITLE => $pageDataObject->getTitle(), ], @@ -170,10 +192,10 @@ public function testCreate() */ public function testUpdate() { - $pageTitle = 'Page title'; - $newPageTitle = 'New Page title'; - $pageIdentifier = 'page-title' . uniqid(); - /** @var \Magento\Cms\Api\Data\PageInterface $pageDataObject */ + $pageTitle = self::PAGE_TITLE; + $newPageTitle = self::PAGE_TITLE_NEW; + $pageIdentifier = self::PAGE_IDENTIFIER_PREFIX . uniqid(); + /** @var PageInterface $pageDataObject */ $pageDataObject = $this->pageFactory->create(); $pageDataObject->setTitle($pageTitle) ->setIdentifier($pageIdentifier); @@ -181,17 +203,17 @@ public function testUpdate() $this->dataObjectHelper->populateWithArray( $this->currentPage, [PageInterface::TITLE => $newPageTitle], - \Magento\Cms\Api\Data\PageInterface::class + PageInterface::class ); $pageData = $this->dataObjectProcessor->buildOutputDataArray( $this->currentPage, - \Magento\Cms\Api\Data\PageInterface::class + PageInterface::class ); $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + 'httpMethod' => Request::HTTP_METHOD_POST, ], 'soap' => [ 'service' => self::SERVICE_NAME, @@ -207,15 +229,69 @@ public function testUpdate() $this->assertEquals($pageData->getTitle(), $newPageTitle); } + /** + * Test update page one field + * + * @return void + */ + public function testUpdateOneField(): void + { + $pageTitle = self::PAGE_TITLE; + $content = self::PAGE_CONTENT; + $newPageTitle = self::PAGE_TITLE_NEW; + $pageIdentifier = self::PAGE_IDENTIFIER_PREFIX . uniqid(); + + /** @var PageInterface $pageDataObject */ + $pageDataObject = $this->pageFactory->create(); + $pageDataObject->setTitle($pageTitle) + ->setIdentifier($pageIdentifier) + ->setContent($content); + $this->currentPage = $this->pageRepository->save($pageDataObject); + $pageId = $this->currentPage->getId(); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '/' . $pageId, + 'httpMethod' => Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $data = [ + 'page' => [ + 'title' => $newPageTitle, + ], + ]; + + if (TESTS_WEB_API_ADAPTER === self::ADAPTER_SOAP) { + $data['page'] += [ + 'id' => $pageId, + 'identifier' => $pageIdentifier, + ]; + } + + $page = $this->_webApiCall($serviceInfo, $data); + + $this->assertArrayHasKey('title', $page); + $this->assertEquals($page['title'], $newPageTitle); + + $this->assertArrayHasKey('content', $page); + $this->assertEquals($page['content'], $content); + } + /** * Test delete \Magento\Cms\Api\Data\PageInterface * @expectedException \Magento\Framework\Exception\NoSuchEntityException */ public function testDelete() { - $pageTitle = 'Page title'; - $pageIdentifier = 'page-title' . uniqid(); - /** @var \Magento\Cms\Api\Data\PageInterface $pageDataObject */ + $pageTitle = self::PAGE_TITLE; + $pageIdentifier = self::PAGE_IDENTIFIER_PREFIX . uniqid(); + /** @var PageInterface $pageDataObject */ $pageDataObject = $this->pageFactory->create(); $pageDataObject->setTitle($pageTitle) ->setIdentifier($pageIdentifier); @@ -224,7 +300,7 @@ public function testDelete() $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH . '/' . $this->currentPage->getId(), - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE, + 'httpMethod' => Request::HTTP_METHOD_DELETE, ], 'soap' => [ 'service' => self::SERVICE_NAME, @@ -291,7 +367,7 @@ public function testSearch() $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH . "/search" . '?' . http_build_query($requestData), - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + 'httpMethod' => Request::HTTP_METHOD_GET, ], 'soap' => [ 'service' => self::SERVICE_NAME, @@ -314,11 +390,12 @@ public function testSearch() */ public function testCreateSamePage() { - $pageIdentifier = 'page-' . uniqid(); + $pageIdentifier = self::PAGE_IDENTIFIER_PREFIX . uniqid(); $pageId = $this->createPageWithIdentifier($pageIdentifier); $this->deletePageByIdentifier($pageId); - $this->createPageWithIdentifier($pageIdentifier); + $id = $this->createPageWithIdentifier($pageIdentifier); + $this->currentPage = $this->pageRepository->getById($id); } /** @@ -341,14 +418,14 @@ private function prepareCmsPages() $pagesData['third'][PageInterface::IS_ACTIVE] = true; foreach ($pagesData as $key => $pageData) { - /** @var \Magento\Cms\Api\Data\PageInterface $pageDataObject */ + /** @var PageInterface $pageDataObject */ $pageDataObject = $this->pageFactory->create(); $this->dataObjectHelper->populateWithArray( $pageDataObject, $pageData, - \Magento\Cms\Api\Data\PageInterface::class + PageInterface::class ); - $result[$key] = $this->pageRepository->save($pageDataObject); + $this->createdPages[] = $result[$key] = $this->pageRepository->save($pageDataObject); } return $result; @@ -364,7 +441,7 @@ private function createPageWithIdentifier($identifier) $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + 'httpMethod' => Request::HTTP_METHOD_POST, ], 'soap' => [ 'service' => self::SERVICE_NAME, @@ -372,10 +449,10 @@ private function createPageWithIdentifier($identifier) 'operation' => self::SERVICE_NAME . 'Save', ], ]; - $requestData = ['page' => - [ + $requestData = [ + 'page' => [ PageInterface::IDENTIFIER => $identifier, - PageInterface::TITLE => 'Page title', + PageInterface::TITLE => self::PAGE_TITLE, ], ]; @@ -393,7 +470,7 @@ private function deletePageByIdentifier($pageId) $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH . '/' . $pageId, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE, + 'httpMethod' => Request::HTTP_METHOD_DELETE, ], 'soap' => [ 'service' => self::SERVICE_NAME, @@ -433,7 +510,7 @@ public function testSaveDesign(): void $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH, - 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + 'httpMethod' => Request::HTTP_METHOD_POST, 'token' => $token, ], 'soap' => [ @@ -446,7 +523,7 @@ public function testSaveDesign(): void $requestData = [ 'page' => [ PageInterface::IDENTIFIER => $id, - PageInterface::TITLE => 'Page title', + PageInterface::TITLE => self::PAGE_TITLE, PageInterface::CUSTOM_THEME => 1 ], ]; From 19c7b22583829b1c362cb89eebc16c8d97095749 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Wed, 11 Mar 2020 11:51:55 +0200 Subject: [PATCH 255/369] MC-32330: Shopping Cart QTY text box functionality slow --- .../view/frontend/web/js/action/update-shopping-cart.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js index a8e70b65019ce..f9def2fd58b23 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js @@ -122,6 +122,9 @@ define([ submitForm: function () { this.element .off('submit', this.onSubmit) + .on('submit', function () { + $(document.body).trigger('processStart'); + }) .submit(); } }); From 85d6433f5734752d496a3b7423bbc3461fd9b5cc Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Wed, 11 Mar 2020 13:37:35 +0200 Subject: [PATCH 256/369] MC-32332: MFTF test for Review Grid Filters not working --- .../AdminAddProductReviewActionGroup.xml | 29 +++++++++ .../AdminFilterProductReviewActionGroup.xml | 21 ++++++ .../Test/Mftf/Data/ProductReviewData.xml | 3 + .../Test/Mftf/Page/AdminProductReviewPage.xml | 14 ++++ .../Section/AdminCreateNewReviewSection.xml | 26 ++++++++ .../Test/AdminReviewsByProductsReportTest.xml | 64 +++++++++++++++++++ 6 files changed, 157 insertions(+) create mode 100644 app/code/Magento/Review/Test/Mftf/ActionGroup/AdminAddProductReviewActionGroup.xml create mode 100644 app/code/Magento/Review/Test/Mftf/ActionGroup/AdminFilterProductReviewActionGroup.xml create mode 100644 app/code/Magento/Review/Test/Mftf/Page/AdminProductReviewPage.xml create mode 100644 app/code/Magento/Review/Test/Mftf/Section/AdminCreateNewReviewSection.xml create mode 100644 app/code/Magento/Review/Test/Mftf/Test/AdminReviewsByProductsReportTest.xml diff --git a/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminAddProductReviewActionGroup.xml b/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminAddProductReviewActionGroup.xml new file mode 100644 index 0000000000000..9316194dcc78b --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminAddProductReviewActionGroup.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAddProductReviewActionGroup"> + <arguments> + <argument name="review" type="entity" defaultValue="simpleProductReview"/> + <argument name="sku" type="string"/> + </arguments> + <!--Click on Add New Review --> + <click selector="{{AdminCreateNewReviewSection.addNewReviewButton}}" stepKey="clickOnNewReview"/> + <waitForElementVisible selector="{{AdminCreateNewReviewSection.addNewReviewBySKU(sku)}}" stepKey="waitForVisibleReviewButton"/> + <!--Select Product by SKU and Create Review --> + <click selector="{{AdminCreateNewReviewSection.addNewReviewBySKU(sku)}}" stepKey="addNewReviewBySKU"/> + <waitForElementVisible selector="{{AdminCreateNewReviewSection.select_stores}}" stepKey="waitForVisibleReviewDetails"/> + <selectOption selector="{{AdminCreateNewReviewSection.select_stores}}" userInput="{{review.select_stores[0]}}" stepKey="visibilityField"/> + <fillField selector="{{AdminCreateNewReviewSection.nickname}}" userInput="{{review.nickname}}" stepKey="fillNicknameField"/> + <fillField selector="{{AdminCreateNewReviewSection.title}}" userInput="{{review.title}}" stepKey="fillSummaryField"/> + <fillField selector="{{AdminCreateNewReviewSection.detail}}" userInput="{{review.detail}}" stepKey="fillReviewField"/> + <click selector="{{AdminCreateNewReviewSection.submitReview}}" stepKey="clickSubmitReview"/> + <waitForElementVisible selector="{{AdminCreateNewReviewSection.SuccessMessage}}" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminCreateNewReviewSection.SuccessMessage}}" userInput="You saved the review." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminFilterProductReviewActionGroup.xml b/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminFilterProductReviewActionGroup.xml new file mode 100644 index 0000000000000..daab520025477 --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/ActionGroup/AdminFilterProductReviewActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminFilterProductReviewActionGroup"> + <arguments> + <argument name="reviewCount" type="string"/> + </arguments> + <!--Sort Review Column in Grid --> + <waitForPageLoad stepKey="waitForGridToAppear"/> + <fillField userInput="{{reviewCount}}" selector="{{AdminCreateNewReviewSection.gridProducts_filter_review_cnt}}" stepKey="searchReview"/> + <click selector="{{AdminCreateNewReviewSection.searchButton}}" stepKey="startSearch"/> + <waitForPageLoad stepKey="waitForResults"/> + <see userInput="{{reviewCount}}" selector="{{AdminCreateNewReviewSection.gridReviewColumn}}" stepKey="assertReviewColumn"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Review/Test/Mftf/Data/ProductReviewData.xml b/app/code/Magento/Review/Test/Mftf/Data/ProductReviewData.xml index 89758328efd54..f66decd1b7bd0 100644 --- a/app/code/Magento/Review/Test/Mftf/Data/ProductReviewData.xml +++ b/app/code/Magento/Review/Test/Mftf/Data/ProductReviewData.xml @@ -12,5 +12,8 @@ <data key="nickname" unique="suffix">user</data> <data key="title">Review title</data> <data key="detail">Simple product review</data> + <array key="select_stores"> + <item>Default Store View</item> + </array> </entity> </entities> diff --git a/app/code/Magento/Review/Test/Mftf/Page/AdminProductReviewPage.xml b/app/code/Magento/Review/Test/Mftf/Page/AdminProductReviewPage.xml new file mode 100644 index 0000000000000..131fc5e82d944 --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Page/AdminProductReviewPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminProductReviewPage" url="review/product/new/{{productId}}/" area="admin" module="Review" parameterized="true"> + <section name="AdminCreateNewReviewSection"/> + </page> +</pages> diff --git a/app/code/Magento/Review/Test/Mftf/Section/AdminCreateNewReviewSection.xml b/app/code/Magento/Review/Test/Mftf/Section/AdminCreateNewReviewSection.xml new file mode 100644 index 0000000000000..3b17b20e9da1b --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Section/AdminCreateNewReviewSection.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCreateNewReviewSection"> + <element name="addNewReviewButton" type="button" selector="#add" timeout="30"/> + <element name="addNewReviewBySKU" type="text" selector="//td[contains(@class,'col-sku')][contains(text(),'{{sku}}')]" parameterized="true"/> + <element name="select_stores" type="text" selector="#select_stores"/> + <element name="nickname" type="text" selector="#nickname"/> + <element name="title" type="text" selector="#title"/> + <element name="detail" type="textarea" selector="#detail"/> + <element name="submitReview" type="button" selector="#save_button"/> + <element name="SuccessMessage" type="button" selector="div.message-success"/> + <element name="gridProducts_filter_review_cnt" type="button" selector="#gridProducts_filter_review_cnt"/> + <element name="searchButton" type="button" selector="//*[@id='gridProducts']//button[contains(@title, 'Search')]"/> + <element name="gridReviewColumn" type="text" selector="//tbody//td[@data-column='review_cnt']"/> + <element name="gridCustomer_filter_review_cnt" type="button" selector="#customers_grid_filter_review_cnt"/> + <element name="CustomerSearchButton" type="button" selector="//*[@id='customers_grid']//button[contains(@title, 'Search')]"/> + </section> +</sections> diff --git a/app/code/Magento/Review/Test/Mftf/Test/AdminReviewsByProductsReportTest.xml b/app/code/Magento/Review/Test/Mftf/Test/AdminReviewsByProductsReportTest.xml new file mode 100644 index 0000000000000..050f30d6ca65f --- /dev/null +++ b/app/code/Magento/Review/Test/Mftf/Test/AdminReviewsByProductsReportTest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminReviewsByProductsReportTest"> + <annotations> + <features value="Review"/> + <stories value="Review By Products"/> + <title value="Admin Reports Review by Products"/> + <description value="Review By Products Grid Filters"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-32083"/> + </annotations> + <before> + <!--Login--> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <!--Create product and Category--> + <createData stepKey="category" entity="SimpleSubCategory"/> + <createData stepKey="createProduct1" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <createData stepKey="createProduct2" entity="SimpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + </before> + <after> + <!-- Delete reviews --> + <actionGroup ref="AdminOpenReviewsPageActionGroup" stepKey="openAllReviewsPage"/> + <actionGroup ref="AdminDeleteReviewsByUserNicknameActionGroup" stepKey="deleteCustomerReview"/> + <!--delete Category and Products --> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <!--Logout--> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Navigate to Marketing > User Content> All Review --> + <amOnPage url="{{AdminReviewsPage.url}}" stepKey="openReviewsPage"/> + <waitForPageLoad time="30" stepKey="waitForPageLoadCreatedReviewOne"/> + <actionGroup ref="AdminAddProductReviewActionGroup" stepKey="addFirstReview"> + <argument name="sku" value="$$createProduct1.sku$$"/> + </actionGroup> + <waitForPageLoad time="30" stepKey="waitForPageLoadCreatedReviewTwo"/> + <actionGroup ref="AdminAddProductReviewActionGroup" stepKey="addSecondReview"> + <argument name="sku" value="$$createProduct2.sku$$"/> + </actionGroup> + <!-- Navigate to Reports > Reviews >By Products --> + <actionGroup ref="AdminNavigateMenuActionGroup" stepKey="navigateToReportsByProductsPage"> + <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> + <argument name="submenuUiId" value="{{AdminMenuReportsReviewsByProducts.dataUiId}}"/> + </actionGroup> + <!--Sort Review Column --> + <grabTextFrom selector="{{AdminCreateNewReviewSection.gridReviewColumn}}" stepKey="grabReviewQuantity"/> + <actionGroup ref="AdminFilterProductReviewActionGroup" stepKey="navigateToReportsReview"> + <argument name="reviewCount" value="$grabReviewQuantity"/> + </actionGroup> + </test> +</tests> From e699d3feb9514ee73c8c0afc3fdb8b5c03ed18d5 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Wed, 11 Mar 2020 15:09:26 +0200 Subject: [PATCH 257/369] MC-32229: Category and other trees not working in Cart Price Rule --- .../Adminhtml/Promo/Quote/NewActionHtml.php | 29 ++++++ .../Promo/Quote/NewConditionHtml.php | 33 ++++++- .../Promo/Quote/NewActionHtmlTest.php | 7 ++ .../Promo/Quote/NewConditionHtmlTest.php | 88 +++++++++++++++++++ 4 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtmlTest.php diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php index 56c08864c90c4..af28547456a9d 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php @@ -47,6 +47,7 @@ public function execute() if ($model instanceof AbstractCondition) { $model->setJsFormObject($formName); $model->setFormName($formName); + $this->setJsFormObject($model); $html = $model->asHtmlRecursive(); } else { $html = ''; @@ -54,4 +55,32 @@ public function execute() $this->getResponse() ->setBody($html); } + + /** + * Set jsFormObject for the model object + * + * @return void + * @param AbstractCondition $model + */ + private function setJsFormObject(AbstractCondition $model): void + { + $requestJsFormName = $this->getRequest()->getParam('form'); + $actualJsFormName = $this->getJsFormObjectName($model->getFormName()); + if ($requestJsFormName === $actualJsFormName) { //new + $model->setJsFormObject($actualJsFormName); + } else { //edit + $model->setJsFormObject($requestJsFormName); + } + } + + /** + * Get jsFormObject name + * + * @param string $formName + * @return string + */ + private function getJsFormObjectName(string $formName): string + { + return $formName . 'rule_actions_fieldset_'; + } } diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php index 50545fd864866..3646f9592c497 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php @@ -6,11 +6,13 @@ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Rule\Model\Condition\AbstractCondition; +use Magento\SalesRule\Controller\Adminhtml\Promo\Quote; /** * Controller class NewConditionHtml. Returns condition html */ -class NewConditionHtml extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpPostActionInterface +class NewConditionHtml extends Quote implements HttpPostActionInterface { /** * New condition html action @@ -39,13 +41,40 @@ public function execute() $model->setAttribute($typeArr[1]); } - if ($model instanceof \Magento\Rule\Model\Condition\AbstractCondition) { + if ($model instanceof AbstractCondition) { $model->setJsFormObject($this->getRequest()->getParam('form')); $model->setFormName($formName); + $this->setJsFormObject($model); $html = $model->asHtmlRecursive(); } else { $html = ''; } $this->getResponse()->setBody($html); } + + /** + * Set jsFormObject for the model object + * + * @return void + * @param AbstractCondition $model + */ + private function setJsFormObject(AbstractCondition $model): void + { + $requestJsFormName = $this->getRequest()->getParam('form'); + $actualJsFormName = $this->getJsFormObjectName($model->getFormName()); + if ($requestJsFormName === $actualJsFormName) { //new + $model->setJsFormObject($actualJsFormName); + } + } + + /** + * Get jsFormObject name + * + * @param string $formName + * @return string + */ + private function getJsFormObjectName(string $formName): string + { + return $formName . 'rule_conditions_fieldset_'; + } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php index 82f1c53d8f161..b2fc8365c90ea 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php @@ -12,6 +12,7 @@ /** * New action html test * + * Verify the request object contains the proper form object for action * @magentoAppArea adminhtml */ class NewActionHtmlTest extends AbstractBackendController @@ -31,6 +32,11 @@ class NewActionHtmlTest extends AbstractBackendController */ private $formName = 'test_form'; + /** + * @var string + */ + private $requestFormName = 'rule_actions_fieldset_'; + /** * Test verifies that execute method has the proper data-form-part value in html response * @@ -73,6 +79,7 @@ private function prepareRequest(): void $this->getRequest()->setParams( [ 'id' => 1, + 'form' => $this->requestFormName, 'form_namespace' => $this->formName, 'type' => 'Magento\SalesRule\Model\Rule\Condition\Product|quote_item_price', ] diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtmlTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtmlTest.php new file mode 100644 index 0000000000000..f15befedfbca7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtmlTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; + +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * New condition html test + * + * Verify the request object contains the proper form object for condition + * @magentoAppArea adminhtml + */ +class NewConditionHtmlTest extends AbstractBackendController +{ + /** + * @var string + */ + protected $resource = 'Magento_SalesRule::quote'; + + /** + * @var string + */ + protected $uri = 'backend/sales_rule/promo_quote/newConditionHtml'; + + /** + * @var string + */ + private $formName = 'test_form'; + + /** + * @var string + */ + private $requestFormName = 'rule_conditions_fieldset_'; + + /** + * Test verifies that execute method has the proper data-form-part value in html response + * + * @return void + */ + public function testExecute(): void + { + $this->prepareRequest(); + $this->dispatch($this->uri); + $html = $this->getResponse() + ->getBody(); + $this->assertContains($this->formName, $html); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(); + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(); + parent::testAclNoAccess(); + } + + /** + * Prepare request + * + * @return void + */ + private function prepareRequest(): void + { + $this->getRequest()->setParams( + [ + 'id' => 1, + 'form' => $this->requestFormName, + 'form_namespace' => $this->formName, + 'type' => 'Magento\SalesRule\Model\Rule\Condition\Product|category_ids', + ] + )->setMethod('POST'); + } +} From e3c2951cb7fe8c25afc3ad7ee8a8cda8c64422d0 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Wed, 11 Mar 2020 15:32:56 +0200 Subject: [PATCH 258/369] Fix mftf test --- .../Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml | 1 + .../Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml | 2 ++ .../Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml | 1 + 3 files changed, 4 insertions(+) diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml index 629599eba84fe..ab4502f3e9cfb 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Section/AdminManageSwatchSection.xml @@ -21,6 +21,7 @@ <element name="chooserBlock" type="block" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatches-visual-col .swatch_sub-menu_container" parameterized="true"/> <!-- Selector for Admin Description input where the index is zero-based --> <element name="swatchAdminDescriptionByIndex" type="input" selector="input[name='optiontext[value][option_{{index}}][0]']" parameterized="true"/> + <element name="swatchWindow" type="button" selector="#swatch_window_option_option_{{var}}" parameterized="true"/> <element name="nthChooseColor" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatch_row_name.colorpicker_handler" parameterized="true"/> <element name="nthUploadFile" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) .swatch_row_name.btn_choose_file_upload" parameterized="true"/> <element name="nthDelete" type="button" selector="#swatch-visual-options-panel table tbody tr:nth-of-type({{var}}) button.delete-option" parameterized="true"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml index 0e24d63728d9d..5a78efcde33b7 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminCreateImageSwatchTest.xml @@ -53,6 +53,7 @@ <click selector="{{AdminManageSwatchSection.nthUploadFile('1')}}" stepKey="clickUploadFile1"/> <attachFile selector="input[name='datafile']" userInput="adobe-thumb.jpg" stepKey="attachFile1"/> <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="adobe-thumb" stepKey="fillAdmin1"/> + <click selector="{{AdminManageSwatchSection.swatchWindow('0')}}" stepKey="clicksWatchWindow1"/> <!-- Set swatch image #2 --> <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch2"/> @@ -62,6 +63,7 @@ <click selector="{{AdminManageSwatchSection.nthUploadFile('2')}}" stepKey="clickUploadFile2"/> <attachFile selector="input[name='datafile']" userInput="adobe-small.jpg" stepKey="attachFile2"/> <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('1')}}" userInput="adobe-small" stepKey="fillAdmin2"/> + <click selector="{{AdminManageSwatchSection.swatchWindow('1')}}" stepKey="clicksWatchWindow2"/> <!-- Set swatch image #3 --> <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch3"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml index 427797bdb09e2..39b3ca51327ba 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml @@ -55,6 +55,7 @@ <click selector="{{AdminManageSwatchSection.nthUploadFile('1')}}" stepKey="clickUploadFile1"/> <attachFile selector="input[name='datafile']" userInput="adobe-thumb.jpg" stepKey="attachFile1"/> <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="adobe-thumb" stepKey="fillAdmin1"/> + <click selector="{{AdminManageSwatchSection.swatchWindow('0')}}" stepKey="clicksWatchWindow1"/> <!-- Set swatch #2 image using the file upload --> <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch2"/> From bff7a3990245a5677c6aabfeddddf47440895824 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Wed, 11 Mar 2020 15:41:30 +0200 Subject: [PATCH 259/369] MC-31878: [Magento Cloud] - Order bulk update using rest api --- .../Model/OperationRepositoryInterface.php | 4 ++-- .../Model/ResourceModel/Operation/OperationRepository.php | 2 +- app/code/Magento/WebapiAsync/Model/OperationRepository.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php b/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php index 945692fed7c99..601ab44af5023 100644 --- a/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php +++ b/app/code/Magento/AsynchronousOperations/Model/OperationRepositoryInterface.php @@ -24,8 +24,8 @@ interface OperationRepositoryInterface * '<arg2-name>' => '<arg2-value>', * ) * @param string $groupId - * @param int|null $operationId + * @param int $operationId * @return OperationInterface */ - public function create($topicName, $entityParams, $groupId, $operationId = null): OperationInterface; + public function create($topicName, $entityParams, $groupId, $operationId): OperationInterface; } diff --git a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php index 40f776ad81099..5e42d0a2310b9 100644 --- a/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php +++ b/app/code/Magento/AsynchronousOperations/Model/ResourceModel/Operation/OperationRepository.php @@ -104,7 +104,7 @@ public function createByTopic($topicName, $entityParams, $groupId) /** * @inheritDoc */ - public function create($topicName, $entityParams, $groupId, $operationId = null): OperationInterface + public function create($topicName, $entityParams, $groupId, $operationId): OperationInterface { return $this->createByTopic($topicName, $entityParams, $groupId); } diff --git a/app/code/Magento/WebapiAsync/Model/OperationRepository.php b/app/code/Magento/WebapiAsync/Model/OperationRepository.php index 05dab58b945c0..695cab2ae4402 100644 --- a/app/code/Magento/WebapiAsync/Model/OperationRepository.php +++ b/app/code/Magento/WebapiAsync/Model/OperationRepository.php @@ -70,7 +70,7 @@ public function __construct( /** * @inheritDoc */ - public function create($topicName, $entityParams, $groupId, $operationId = null): OperationInterface + public function create($topicName, $entityParams, $groupId, $operationId): OperationInterface { $this->messageValidator->validate($topicName, $entityParams); $requestData = $this->inputParamsResolver->getInputData(); From 41562d0cc1c6a9e5a6d2a8159a37de1f6e744384 Mon Sep 17 00:00:00 2001 From: Piotr Markiewicz <piotr.markiewicz@vaimo.com> Date: Wed, 11 Mar 2020 15:35:56 +0100 Subject: [PATCH 260/369] Fixed namespace for Test classes --- .../Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php | 3 ++- .../Unit/Controller/Adminhtml/Export/File/DownloadTest.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php index c6c472c977c07..eb29907f8830d 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\ImportExport\Controller\Adminhtml\Export\File; +namespace Magento\ImportExport\Test\Unit\Controller\Adminhtml\Export\File; use Magento\Backend\App\Action\Context; use Magento\Backend\Model\View\Result\Redirect; @@ -17,6 +17,7 @@ use Magento\Framework\Filesystem\DriverInterface; use Magento\Framework\Message\ManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\ImportExport\Controller\Adminhtml\Export\File\Delete; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php index 71f6940ad7a86..4312520cfefd1 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\ImportExport\Controller\Adminhtml\Export\File; +namespace Magento\ImportExport\Test\Unit\Controller\Adminhtml\Export\File; use Magento\Backend\App\Action\Context; use Magento\Backend\Model\View\Result\Redirect; @@ -17,6 +17,7 @@ use Magento\Framework\Filesystem\Directory\ReadInterface; use Magento\Framework\Message\ManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\ImportExport\Controller\Adminhtml\Export\File\Download; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; From e4255ece861bed0db15ca80a0dd89860bafc027a Mon Sep 17 00:00:00 2001 From: Piotr Markiewicz <piotr.markiewicz@vaimo.com> Date: Wed, 11 Mar 2020 16:26:05 +0100 Subject: [PATCH 261/369] Fixed for static tests --- .../Unit/Controller/Adminhtml/Export/File/DeleteTest.php | 9 ++++++--- .../Controller/Adminhtml/Export/File/DownloadTest.php | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php index eb29907f8830d..ef40651f95be9 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DeleteTest.php @@ -122,7 +122,6 @@ protected function setUp() ->method('getMessageManager') ->willReturn($this->messageManagerMock); - $this->objectManagerHelper = new ObjectManagerHelper($this); $this->deleteControllerMock = $this->objectManagerHelper->getObject( Delete::class, @@ -143,7 +142,9 @@ public function testExecuteSuccess() ->with('filename') ->willReturn('sampleFile'); - $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directoryMock)); + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->returnValue($this->directoryMock)); $this->directoryMock->expects($this->once())->method('isFile')->willReturn(true); $this->fileMock->expects($this->once())->method('deleteFile')->willReturn(true); $this->messageManagerMock->expects($this->once())->method('addSuccessMessage'); @@ -160,7 +161,9 @@ public function testExecuteFileDoesntExists() ->with('filename') ->willReturn('sampleFile'); - $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directoryMock)); + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->returnValue($this->directoryMock)); $this->directoryMock->expects($this->once())->method('isFile')->willReturn(false); $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php index 4312520cfefd1..4512aa6365ca5 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Export/File/DownloadTest.php @@ -151,7 +151,9 @@ public function testExecuteSuccess() ->with('filename') ->willReturn('sampleFile.csv'); - $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directoryMock)); + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->returnValue($this->directoryMock)); $this->directoryMock->expects($this->once())->method('isFile')->willReturn(true); $this->fileFactoryMock->expects($this->once())->method('create'); @@ -167,7 +169,9 @@ public function testExecuteFileDoesntExists() ->with('filename') ->willReturn('sampleFile'); - $this->fileSystemMock->expects($this->once())->method('getDirectoryRead')->will($this->returnValue($this->directoryMock)); + $this->fileSystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->returnValue($this->directoryMock)); $this->directoryMock->expects($this->once())->method('isFile')->willReturn(false); $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); From 8a66448d1ac1c3c11073a1e091a714c45250ec3c Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Wed, 11 Mar 2020 12:18:51 -0500 Subject: [PATCH 262/369] MC-31986: Add support for ES 7 to 2.4-develop - fix search engines sorting issue. --- app/code/Magento/Elasticsearch/etc/di.xml | 2 +- app/code/Magento/Elasticsearch6/etc/di.xml | 2 +- app/code/Magento/Elasticsearch7/etc/di.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index fa8686b6262df..5ce0c55fb454a 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -163,7 +163,7 @@ <type name="Magento\Search\Model\Adminhtml\System\Config\Source\Engine"> <arguments> <argument name="engines" xsi:type="array"> - <item name="elasticsearch5" xsi:type="string">Elasticsearch 5.0+ (Deprecated)</item> + <item sortOrder="10" name="elasticsearch5" xsi:type="string">Elasticsearch 5.0+ (Deprecated)</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml index f7b22c05027b1..19d3b709cb2ea 100644 --- a/app/code/Magento/Elasticsearch6/etc/di.xml +++ b/app/code/Magento/Elasticsearch6/etc/di.xml @@ -17,7 +17,7 @@ <type name="Magento\Search\Model\Adminhtml\System\Config\Source\Engine"> <arguments> <argument name="engines" xsi:type="array"> - <item name="elasticsearch6" xsi:type="string">Elasticsearch 6.x</item> + <item sortOrder="20" name="elasticsearch6" xsi:type="string">Elasticsearch 6.x</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Elasticsearch7/etc/di.xml b/app/code/Magento/Elasticsearch7/etc/di.xml index 1e480894bc630..a35fef8c66f0d 100644 --- a/app/code/Magento/Elasticsearch7/etc/di.xml +++ b/app/code/Magento/Elasticsearch7/etc/di.xml @@ -17,7 +17,7 @@ <type name="Magento\Search\Model\Adminhtml\System\Config\Source\Engine"> <arguments> <argument name="engines" xsi:type="array"> - <item name="elasticsearch7" xsi:type="string">Elasticsearch 7.0+</item> + <item sortOrder="30" name="elasticsearch7" xsi:type="string">Elasticsearch 7.0+</item> </argument> </arguments> </type> From 8f2b7351a25b0f6b0edc61a68cb8568ff69feddc Mon Sep 17 00:00:00 2001 From: Navarr Barnier <navarr@mediotype.com> Date: Wed, 11 Mar 2020 13:39:16 -0400 Subject: [PATCH 263/369] Update Frontend Development Workflow type's comment to be clearer An implementor expressed confusion, thinking that the comment applied to the value displayed in the setting as opposed to the setting itself. This commit makes the meaning of the comment clearer - in that, rather than the setting not being available, it has no effect. --- app/code/Magento/Developer/etc/adminhtml/system.xml | 2 +- app/code/Magento/Developer/i18n/en_US.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Developer/etc/adminhtml/system.xml b/app/code/Magento/Developer/etc/adminhtml/system.xml index 10449ab428726..812776ba1da28 100644 --- a/app/code/Magento/Developer/etc/adminhtml/system.xml +++ b/app/code/Magento/Developer/etc/adminhtml/system.xml @@ -11,7 +11,7 @@ <label>Frontend Development Workflow</label> <field id="type" translate="label comment" type="select" sortOrder="1" showInDefault="1" canRestore="1"> <label>Workflow type</label> - <comment>Not available in production mode.</comment> + <comment>Modifying this configuration has no effect in production mode.</comment> <source_model>Magento\Developer\Model\Config\Source\WorkflowType</source_model> <frontend_model>Magento\Developer\Block\Adminhtml\System\Config\WorkflowType</frontend_model> <backend_model>Magento\Developer\Model\Config\Backend\WorkflowType</backend_model> diff --git a/app/code/Magento/Developer/i18n/en_US.csv b/app/code/Magento/Developer/i18n/en_US.csv index 59e67445b2b58..649779cdf853d 100644 --- a/app/code/Magento/Developer/i18n/en_US.csv +++ b/app/code/Magento/Developer/i18n/en_US.csv @@ -9,4 +9,4 @@ "Allowed IPs (comma separated)","Allowed IPs (comma separated)" "Leave empty for access from any location.","Leave empty for access from any location." "Log to File","Log to File" -"Not available in production mode.","Not available in production mode." +"Modifying this configuration has no effect in production mode.","Modifying this configuration has no effect in production mode." From 15dd3ec9747157281694d2ce65c20ed93ea69754 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Wed, 11 Mar 2020 12:45:52 -0500 Subject: [PATCH 264/369] MC-29423: GraphQL Send Friend is still sending emails when disabled - use snake case for field names --- .../SendFriendGraphQl/etc/schema.graphqls | 2 +- .../GraphQl/SendFriend/StoreConfigTest.php | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls index 7dc28c54cf6ef..f4967779f3822 100644 --- a/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls +++ b/app/code/Magento/SendFriendGraphQl/etc/schema.graphqls @@ -39,7 +39,7 @@ type SendEmailToFriendRecipient { } type StoreConfig { - sendFriend: SendFriendConfiguration @resolver(class: "\\Magento\\SendFriendGraphQl\\Model\\Resolver\\SendFriendConfiguration") @doc(description: "Email to a Friend configuration.") + send_friend: SendFriendConfiguration @resolver(class: "\\Magento\\SendFriendGraphQl\\Model\\Resolver\\SendFriendConfiguration") @doc(description: "Email to a Friend configuration.") } type SendFriendConfiguration { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php index 73855f63a0945..e1c475d2ea059 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php @@ -20,11 +20,11 @@ public function testSendFriendFieldsAreReturnedWithoutError() $response = $this->graphQlQuery($query); $this->assertArrayNotHasKey('errors', $response); - $this->assertArrayHasKey('sendFriend', $response['storeConfig']); - $this->assertArrayHasKey('enabled_for_customers', $response['storeConfig']['sendFriend']); - $this->assertArrayHasKey('enabled_for_guests', $response['storeConfig']['sendFriend']); - $this->assertNotNull($response['storeConfig']['sendFriend']['enabled_for_customers']); - $this->assertNotNull($response['storeConfig']['sendFriend']['enabled_for_guests']); + $this->assertArrayHasKey('send_friend', $response['storeConfig']); + $this->assertArrayHasKey('enabled_for_customers', $response['storeConfig']['send_friend']); + $this->assertArrayHasKey('enabled_for_guests', $response['storeConfig']['send_friend']); + $this->assertNotNull($response['storeConfig']['send_friend']['enabled_for_customers']); + $this->assertNotNull($response['storeConfig']['send_friend']['enabled_for_guests']); } /** @@ -77,11 +77,11 @@ public function testSendFriendEnabledGuestEnabled() private function assertResponse(array $expectedValues, array $response) { $this->assertArrayNotHasKey('errors', $response); - $this->assertArrayHasKey('sendFriend', $response['storeConfig']); - $this->assertArrayHasKey('enabled_for_customers', $response['storeConfig']['sendFriend']); - $this->assertArrayHasKey('enabled_for_guests', $response['storeConfig']['sendFriend']); - $this->assertEquals($expectedValues['enabled_for_customers'], $response['storeConfig']['sendFriend']['enabled_for_customers']); - $this->assertEquals($expectedValues['enabled_for_guests'], $response['storeConfig']['sendFriend']['enabled_for_guests']); + $this->assertArrayHasKey('send_friend', $response['storeConfig']); + $this->assertArrayHasKey('enabled_for_customers', $response['storeConfig']['send_friend']); + $this->assertArrayHasKey('enabled_for_guests', $response['storeConfig']['send_friend']); + $this->assertEquals($expectedValues['enabled_for_customers'], $response['storeConfig']['send_friend']['enabled_for_customers']); + $this->assertEquals($expectedValues['enabled_for_guests'], $response['storeConfig']['send_friend']['enabled_for_guests']); } /** @@ -95,7 +95,7 @@ private function getStoreConfigQuery() { storeConfig{ id - sendFriend { + send_friend { enabled_for_customers enabled_for_guests } From 7aa945b656baecaf231953b3e29ffab98e2f4708 Mon Sep 17 00:00:00 2001 From: Vasya Tsviklinskyi <tsviklinskyi@gmail.com> Date: Wed, 11 Mar 2020 19:48:28 +0200 Subject: [PATCH 265/369] MC-32223: JS bug in validate date of birth input on 2.3.4 --- app/code/Magento/Customer/view/frontend/web/js/validation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Customer/view/frontend/web/js/validation.js b/app/code/Magento/Customer/view/frontend/web/js/validation.js index 67a714212026a..573556f0f33a2 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/validation.js +++ b/app/code/Magento/Customer/view/frontend/web/js/validation.js @@ -2,6 +2,7 @@ define([ 'jquery', 'moment', 'jquery/validate', + 'validation', 'mage/translate' ], function ($, moment) { 'use strict'; From 8bbebf38a52474069a672b93bd9d5ecca6cf1852 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Wed, 11 Mar 2020 20:04:12 +0100 Subject: [PATCH 266/369] Avoid executing original `execute` method when no permission to do so --- .../Customer/Controller/Plugin/Account.php | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/app/code/Magento/Customer/Controller/Plugin/Account.php b/app/code/Magento/Customer/Controller/Plugin/Account.php index b7352873269e9..6974ce92daed1 100644 --- a/app/code/Magento/Customer/Controller/Plugin/Account.php +++ b/app/code/Magento/Customer/Controller/Plugin/Account.php @@ -7,8 +7,10 @@ namespace Magento\Customer\Controller\Plugin; +use Closure; use Magento\Customer\Controller\AccountInterface; use Magento\Customer\Model\Session; +use Magento\Framework\App\ActionFlag; use Magento\Framework\App\ActionInterface; use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; @@ -33,53 +35,61 @@ class Account * @var array */ private $allowedActions = []; + /** + * @var ActionFlag + */ + private $actionFlag; /** * @param RequestInterface $request * @param Session $customerSession + * @param ActionFlag $actionFlag * @param array $allowedActions List of actions that are allowed for not authorized users */ public function __construct( RequestInterface $request, Session $customerSession, + ActionFlag $actionFlag, array $allowedActions = [] ) { $this->session = $customerSession; $this->allowedActions = $allowedActions; $this->request = $request; + $this->actionFlag = $actionFlag; } /** - * Dispatch actions allowed for not authorized users + * Executes original method if allowed, otherwise - redirects to log in * - * @param AccountInterface $subject - * @return void + * @param AccountInterface $controllerAction + * @param Closure $proceed + * @return ResultInterface|ResponseInterface|void */ - public function beforeExecute(AccountInterface $subject) + public function aroundExecute(AccountInterface $controllerAction, Closure $proceed) { - $action = strtolower($this->request->getActionName()); - $pattern = '/^(' . implode('|', $this->allowedActions) . ')$/i'; - - if (!preg_match($pattern, $action)) { - if (!$this->session->authenticate()) { - $subject->getActionFlag()->set('', ActionInterface::FLAG_NO_DISPATCH, true); - } - } else { + if ($this->isActionAllowed()) { $this->session->setNoReferer(true); + $response = $proceed(); + $this->session->unsNoReferer(false); + + return $response; + } + + if (!$this->session->authenticate()) { + $this->actionFlag->set('', ActionInterface::FLAG_NO_DISPATCH, true); } } /** - * Remove No-referer flag from customer session + * Validates whether currently requested action is one of the allowed * - * @param AccountInterface $subject - * @param ResponseInterface|ResultInterface $result - * @return ResponseInterface|ResultInterface - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @return bool */ - public function afterExecute(AccountInterface $subject, $result) + private function isActionAllowed(): bool { - $this->session->unsNoReferer(false); - return $result; + $action = strtolower($this->request->getActionName()); + $pattern = '/^(' . implode('|', $this->allowedActions) . ')$/i'; + + return (bool)preg_match($pattern, $action); } } From 501e370f7dcfd98bfd068b6af6fa3344676e27d3 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Wed, 11 Mar 2020 21:27:30 +0100 Subject: [PATCH 267/369] Fix implementation and Unit Tests --- .../Customer/Controller/Plugin/Account.php | 20 ++---- .../Unit/Controller/Plugin/AccountTest.php | 61 ++++++------------- 2 files changed, 24 insertions(+), 57 deletions(-) diff --git a/app/code/Magento/Customer/Controller/Plugin/Account.php b/app/code/Magento/Customer/Controller/Plugin/Account.php index 6974ce92daed1..7d0b954523fa6 100644 --- a/app/code/Magento/Customer/Controller/Plugin/Account.php +++ b/app/code/Magento/Customer/Controller/Plugin/Account.php @@ -35,27 +35,20 @@ class Account * @var array */ private $allowedActions = []; - /** - * @var ActionFlag - */ - private $actionFlag; /** * @param RequestInterface $request * @param Session $customerSession - * @param ActionFlag $actionFlag * @param array $allowedActions List of actions that are allowed for not authorized users */ public function __construct( RequestInterface $request, Session $customerSession, - ActionFlag $actionFlag, array $allowedActions = [] ) { + $this->request = $request; $this->session = $customerSession; $this->allowedActions = $allowedActions; - $this->request = $request; - $this->actionFlag = $actionFlag; } /** @@ -68,16 +61,11 @@ public function __construct( public function aroundExecute(AccountInterface $controllerAction, Closure $proceed) { if ($this->isActionAllowed()) { - $this->session->setNoReferer(true); - $response = $proceed(); - $this->session->unsNoReferer(false); - - return $response; + return $proceed(); } - if (!$this->session->authenticate()) { - $this->actionFlag->set('', ActionInterface::FLAG_NO_DISPATCH, true); - } + /** @FIXME Move Authentication and redirect out of Session model */ + $this->session->authenticate(); } /** diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php index 7dd376d57bdb0..8986675d963a4 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Test\Unit\Controller\Plugin; +use Closure; use Magento\Customer\Controller\AccountInterface; use Magento\Customer\Controller\Plugin\Account; use Magento\Customer\Model\Session; @@ -83,40 +84,38 @@ protected function setUp() /** * @param string $action * @param array $allowedActions - * @param boolean $isActionAllowed + * @param boolean $isAllowed * @param boolean $isAuthenticated * * @dataProvider beforeExecuteDataProvider */ - public function testBeforeExecute($action, $allowedActions, $isActionAllowed, $isAuthenticated) + public function testAroundExecuteInterruptsOriginalCallWhenNotAllowed(string $action, array $allowedActions, bool $isAllowed, bool $isAuthenticated) { + /** @var callable|MockObject $proceedMock */ + $proceedMock = $this->getMockBuilder(\stdClass::class) + ->setMethods(['__invoke']) + ->getMock(); + + $closureMock = Closure::fromCallable($proceedMock); + $this->requestMock->expects($this->once()) ->method('getActionName') ->willReturn($action); - if ($isActionAllowed) { - $this->sessionMock->expects($this->once()) - ->method('setNoReferer') - ->with(true) - ->willReturnSelf(); + if ($isAllowed) { + $proceedMock->expects($this->once())->method('__invoke')->willReturn($this->resultMock); } else { - $this->sessionMock->expects($this->once()) - ->method('authenticate') - ->willReturn($isAuthenticated); - if (!$isAuthenticated) { - $this->actionMock->expects($this->once()) - ->method('getActionFlag') - ->willReturn($this->actionFlagMock); - - $this->actionFlagMock->expects($this->once()) - ->method('set') - ->with('', ActionInterface::FLAG_NO_DISPATCH, true) - ->willReturnSelf(); - } + $proceedMock->expects($this->never())->method('__invoke'); } $plugin = new Account($this->requestMock, $this->sessionMock, $allowedActions); - $plugin->beforeExecute($this->actionMock); + $result = $plugin->aroundExecute($this->actionMock, $closureMock); + + if ($isAllowed) { + $this->assertSame($this->resultMock, $result); + } else { + $this->assertNull($result); + } } /** @@ -157,24 +156,4 @@ public function beforeExecuteDataProvider() ], ]; } - - public function testAfterExecute() - { - $this->sessionMock->expects($this->once()) - ->method('unsNoReferer') - ->with(false) - ->willReturnSelf(); - - $plugin = (new ObjectManager($this))->getObject( - Account::class, - [ - 'session' => $this->sessionMock, - 'allowedActions' => ['testaction'] - ] - ); - $this->assertSame( - $this->resultMock, - $plugin->afterExecute($this->actionMock, $this->resultMock, $this->requestMock) - ); - } } From 79a3a1a1ad97dcd04a96a37625541b19881d1583 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Wed, 11 Mar 2020 21:33:51 +0100 Subject: [PATCH 268/369] Fix logical issue --- app/code/Magento/Customer/Controller/Plugin/Account.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Customer/Controller/Plugin/Account.php b/app/code/Magento/Customer/Controller/Plugin/Account.php index 7d0b954523fa6..ee927525f2315 100644 --- a/app/code/Magento/Customer/Controller/Plugin/Account.php +++ b/app/code/Magento/Customer/Controller/Plugin/Account.php @@ -60,12 +60,10 @@ public function __construct( */ public function aroundExecute(AccountInterface $controllerAction, Closure $proceed) { - if ($this->isActionAllowed()) { + /** @FIXME Move Authentication and redirect out of Session model */ + if ($this->isActionAllowed() || $this->session->authenticate()) { return $proceed(); } - - /** @FIXME Move Authentication and redirect out of Session model */ - $this->session->authenticate(); } /** From 302392701655d6458d8c976b8491a96a16eb97bb Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Wed, 11 Mar 2020 21:50:47 +0100 Subject: [PATCH 269/369] Code Review changes --- app/code/Magento/Customer/Controller/AbstractAccount.php | 2 +- app/code/Magento/Customer/Controller/Plugin/Account.php | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/code/Magento/Customer/Controller/AbstractAccount.php b/app/code/Magento/Customer/Controller/AbstractAccount.php index 36a96521cc6d1..4f2c80711d292 100644 --- a/app/code/Magento/Customer/Controller/AbstractAccount.php +++ b/app/code/Magento/Customer/Controller/AbstractAccount.php @@ -12,7 +12,7 @@ * AbstractAccount class is deprecated, in favour of Composition approach to build Controllers * * @SuppressWarnings(PHPMD.NumberOfChildren) - * @deprecated 2.4.0 + * @deprecated * @see \Magento\Customer\Controller\AccountInterface */ abstract class AbstractAccount extends Action implements AccountInterface diff --git a/app/code/Magento/Customer/Controller/Plugin/Account.php b/app/code/Magento/Customer/Controller/Plugin/Account.php index ee927525f2315..f02b333b70f7a 100644 --- a/app/code/Magento/Customer/Controller/Plugin/Account.php +++ b/app/code/Magento/Customer/Controller/Plugin/Account.php @@ -10,8 +10,6 @@ use Closure; use Magento\Customer\Controller\AccountInterface; use Magento\Customer\Model\Session; -use Magento\Framework\App\ActionFlag; -use Magento\Framework\App\ActionInterface; use Magento\Framework\App\RequestInterface; use Magento\Framework\App\ResponseInterface; use Magento\Framework\Controller\ResultInterface; From b5fda4c69bf1268f35e2758355e764c11a02a807 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Wed, 11 Mar 2020 21:56:52 +0100 Subject: [PATCH 270/369] Code style --- app/code/Magento/Customer/Controller/Plugin/Account.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Controller/Plugin/Account.php b/app/code/Magento/Customer/Controller/Plugin/Account.php index f02b333b70f7a..bbdb58d626108 100644 --- a/app/code/Magento/Customer/Controller/Plugin/Account.php +++ b/app/code/Magento/Customer/Controller/Plugin/Account.php @@ -22,7 +22,7 @@ class Account /** * @var Session */ - protected $session; + private $session; /** * @var RequestInterface @@ -55,6 +55,7 @@ public function __construct( * @param AccountInterface $controllerAction * @param Closure $proceed * @return ResultInterface|ResponseInterface|void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundExecute(AccountInterface $controllerAction, Closure $proceed) { From 1917781506ff523a94514fa63a0a04637ef51f05 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Wed, 11 Mar 2020 22:55:29 +0100 Subject: [PATCH 271/369] Fix code style --- .../Customer/Test/Unit/Controller/Plugin/AccountTest.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php index 8986675d963a4..01ff1ced05ff9 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Plugin/AccountTest.php @@ -85,12 +85,14 @@ protected function setUp() * @param string $action * @param array $allowedActions * @param boolean $isAllowed - * @param boolean $isAuthenticated * * @dataProvider beforeExecuteDataProvider */ - public function testAroundExecuteInterruptsOriginalCallWhenNotAllowed(string $action, array $allowedActions, bool $isAllowed, bool $isAuthenticated) - { + public function testAroundExecuteInterruptsOriginalCallWhenNotAllowed( + string $action, + array $allowedActions, + bool $isAllowed + ) { /** @var callable|MockObject $proceedMock */ $proceedMock = $this->getMockBuilder(\stdClass::class) ->setMethods(['__invoke']) From 53da3670a98b000d893c560ae1cf18b1d795dc44 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Thu, 12 Mar 2020 01:14:39 +0100 Subject: [PATCH 272/369] Deprecate `AdminLoginTest`, introduce `AdminLoginSuccessfulTest` --- .../Test/Mftf/Test/AdminLoginFailedTest.xml | 28 +++++++++++++++++++ .../Mftf/Test/AdminLoginSuccessfulTest.xml | 27 ++++++++++++++++++ .../Backend/Test/Mftf/Test/AdminLoginTest.xml | 2 +- 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Backend/Test/Mftf/Test/AdminLoginFailedTest.xml create mode 100644 app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginFailedTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginFailedTest.xml new file mode 100644 index 0000000000000..1239fc471f59e --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginFailedTest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminLoginFailedTest"> + <annotations> + <features value="Backend"/> + <stories value="Login on the Admin Login page"/> + <title value="Admin should not be able to log into the backend with invalid credentials"/> + <description value="Admin should not be able to log into the backend with invalid credentials"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-71572"/> + <group value="example"/> + <group value="login"/> + </annotations> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"> + <argument name="password" value="INVALID!{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + </actionGroup> + <actionGroup ref="AssertMessageOnAdminLoginActionGroup" stepKey="assertErrorMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml new file mode 100644 index 0000000000000..a8de04f4342de --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginSuccessfulTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminLoginSuccessfulTest"> + <annotations> + <features value="Backend"/> + <stories value="Login on the Admin Login page"/> + <title value="Admin should be able to log into the Magento Admin backend successfully"/> + <description value="Admin should be able to log into the Magento Admin backend successfully"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-71572"/> + <group value="example"/> + <group value="login"/> + </annotations> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AssertAdminSuccessLoginActionGroup" stepKey="assertLoggedIn"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml index 566328e075600..5f916063cfd1d 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminLoginTest"> + <test name="AdminLoginTest" deprecated="Replaced with AdminLoginSuccessfulTest"> <annotations> <features value="Backend"/> <stories value="Login on the Admin Login page"/> From aa6d0d9f0beaded06eb6fad1fe8689a2a076fa25 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Thu, 12 Mar 2020 01:17:48 +0100 Subject: [PATCH 273/369] Replace deprecated reference to Action Group --- app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml index 5f916063cfd1d..09893f5f51e5e 100644 --- a/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminLoginTest.xml @@ -20,7 +20,7 @@ <group value="login"/> </annotations> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> <seeInCurrentUrl url="{{AdminLoginPage.url}}" stepKey="seeAdminLoginUrl"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> From 3503fad3935369bde46e99394a39feb32855c7f7 Mon Sep 17 00:00:00 2001 From: Slava Mankivski <mankivsk@adobe.com> Date: Wed, 11 Mar 2020 19:17:51 -0500 Subject: [PATCH 274/369] MC-32272: Skip video related tests MAGETWO-95254, MC-102, MC-108, MC-109, MC-110, MC-111, MC-114, MC-202, MC-203, MC-204, MC-205, MC-206, MC-207 --- .../Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml | 3 +++ .../Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml | 3 +++ .../Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml | 3 +++ .../Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml | 3 +++ .../Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml | 3 +++ .../Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml | 3 +++ .../Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml | 3 +++ .../Test/AdminRemoveDefaultVideoDownloadableProductTest.xml | 3 +++ .../Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml | 3 +++ .../Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml | 3 +++ .../Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml | 3 +++ .../Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml | 3 +++ .../Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml | 3 +++ .../Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml | 3 +++ 14 files changed, 42 insertions(+) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml index 66443e130ed08..2f2326b465062 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-110"/> <group value="Bundle"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml index 0de9f4ee75a4d..09297a4e1df80 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-205"/> <group value="Bundle"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml index 0b33ef0ac0783..ba91817a8dc6e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-111"/> <group value="Catalog"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml index e89cf6f4242e7..e1c81c8ae0303 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-109"/> <group value="Catalog"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <!-- Replacing steps in base AdminAddDefaultVideoSimpleProductTest --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml index 8c80a2bf9a851..baa952f6bcf45 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-206"/> <group value="Catalog"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <actionGroup ref="EnableAdminAccountSharingActionGroup" stepKey="enableAdminAccountSharing"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml index 8d89e0d9b535b..0104eff068e0b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-204"/> <group value="Catalog"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <!-- Replacing steps in base AdminRemoveDefaultVideoSimpleProductTest --> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml index c78c237935a90..ddc11fa6420ec 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-114"/> <group value="Downloadable"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add static.magento.com" before="enableAdminAccountSharing"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml index 5792fd3cc7eb7..bd2e7615ac252 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminRemoveDefaultVideoDownloadableProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-207"/> <group value="Downloadable"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add static.magento.com" before="enableAdminAccountSharing"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml index f9af2284ae71a..5eee2d77befab 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-108"/> <group value="GroupedProduct"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml index 572ae3a44a953..4486bc66ffb98 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml @@ -17,6 +17,9 @@ <severity value="MAJOR"/> <testCaseId value="MC-203"/> <group value="GroupedProduct"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml index c2b92e6af452a..766d8027bbb89 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml @@ -11,6 +11,9 @@ <test name="AdminAddDefaultVideoSimpleProductTest"> <annotations> <group value="ProductVideo"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <!-- Set product video Youtube api key configuration --> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml index c674f12115334..2f47163cff9f6 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml @@ -11,6 +11,9 @@ <test name="AdminRemoveDefaultVideoSimpleProductTest"> <annotations> <group value="ProductVideo"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <!-- Set product video Youtube api key configuration --> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml index 183bd64d97678..440846e073f1b 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminValidateUrlOnGetVideoInformationTest.xml @@ -15,6 +15,9 @@ <description value="Testing for a required video url when getting video information"/> <severity value="CRITICAL"/> <group value="ProductVideo"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml index ee03e811ae579..862831c09d1d7 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml @@ -17,6 +17,9 @@ <description value="Check Youtube video window on the product page"/> <severity value="MAJOR"/> <group value="ProductVideo"/> + <skip> + <issueId value="MC-32197"/> + </skip> </annotations> <before> From 0e2ae972f77a556f40ee3a2b09e43e2cc5a4392f Mon Sep 17 00:00:00 2001 From: Slava Mankivski <mankivsk@adobe.com> Date: Wed, 11 Mar 2020 22:01:29 -0500 Subject: [PATCH 275/369] MC-32272: Skip video related tests MAGETWO-95254, MC-102, MC-108, MC-109, MC-110, MC-111, MC-114, MC-202, MC-203, MC-204, MC-205, MC-206, MC-207 --- .../ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml | 1 + .../ProductVideo/Test/TestCase/UpdateProductVideoTest.xml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml index 25fe2b75dfe48..f9c1019290a63 100644 --- a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml +++ b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/ConfigurableProductVideoTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\ProductVideo\Test\TestCase\ConfigurableProductVideoTest" summary="Video displaying for simple variation of configurable on product page" ticketId="MAGETWO-69381"> <variation name="ConfigurableProductVideoTestVariation1"> + <data name="tag" xsi:type="string">stable:no</data> <data name="product/dataset" xsi:type="string">configurable_with_video</data> <data name="simpleProductVideo/data/media_gallery/images/0/video_url" xsi:type="string">https://vimeo.com/16342611</data> <data name="variation" xsi:type="string">attribute_key_0:option_key_0 attribute_key_1:option_key_0</data> diff --git a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/UpdateProductVideoTest.xml b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/UpdateProductVideoTest.xml index 9d5dd49eadc6f..a6ad2c6e621f4 100644 --- a/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/UpdateProductVideoTest.xml +++ b/dev/tests/functional/tests/app/Magento/ProductVideo/Test/TestCase/UpdateProductVideoTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\ProductVideo\Test\TestCase\UpdateProductVideoTest" summary="Add Video to PCF"> <variation name="UpdateProductVideoTestVariation1" summary="Edit Youtube URL" ticketId="MAGETWO-43664"> + <data name="tag" xsi:type="string">stable:no</data> <data name="productVideo/dataset" xsi:type="string">product_with_video_youtube</data> <data name="product/data/sku" xsi:type="string">simple_product_with_category_%isolation%</data> <data name="product/data/media_gallery/images/0/video_url" xsi:type="string">https://vimeo.com/16342611</data> @@ -39,6 +40,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductForm" /> </variation> <variation name="GetVideoInfoTestVariation1" summary="Validate Youtube video info" ticketId="MAGETWO-43663"> + <data name="tag" xsi:type="string">stable:no</data> <data name="productVideo/dataset" xsi:type="string">product_with_video_youtube</data> <data name="product/data/sku" xsi:type="string">simple_product_with_category_%isolation%</data> <data name="product/data/media_gallery/images/0/video_url" xsi:type="string">https://youtu.be/WMp2PvU2qi8</data> From 39ade1f9791606927a37386d08a96105e4d18613 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Thu, 12 Mar 2020 09:48:15 +0200 Subject: [PATCH 276/369] added webapi test --- .../Guest/AddSimpleProductToCartTest.php | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php index 01ae565f00bf6..3bdce6ea98b30 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php @@ -10,6 +10,7 @@ use Exception; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException; use Magento\TestFramework\TestCase\GraphQlAbstract; /** @@ -79,6 +80,30 @@ public function testAddSimpleProductToCart() self::assertEquals('USD', $rowTotalIncludingTax['currency']); } + /** + * Add out of stock product to cart + * + * @@magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @return void + */ + public function testAddProductToCartWithError(): void + { + $disabledProductSku = 'simple3'; + $quantity = 2; + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $disabledProductSku, $quantity); + + $this->expectException(ResponseContainsErrorsException::class); + $this->expectExceptionMessage( + 'Could not add the product with SKU simple3 to the shopping cart: ' . + 'Product that you are trying to add is not available.' + ); + + $this->graphQlMutation($query); + } + /** * @expectedException Exception * @expectedExceptionMessage Required parameter "cart_id" is missing @@ -191,7 +216,7 @@ public function testAddSimpleProductToCustomerCart() private function getQuery(string $maskedQuoteId, string $sku, float $quantity): string { return <<<QUERY -mutation { +mutation { addSimpleProductsToCart( input: { cart_id: "{$maskedQuoteId}" From 9b178d0fbb4f3722680cb9f5c98c3a5ce99c878c Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 12 Mar 2020 10:34:20 +0200 Subject: [PATCH 277/369] MC-31945: New customer creation via the admin does not honor default customer group --- .../Model/AttributeMetadataResolver.php | 40 ++++- .../Model/AttributeMetadataResolverTest.php | 166 ++++++++++++++++++ 2 files changed, 198 insertions(+), 8 deletions(-) create mode 100644 app/code/Magento/Customer/Test/Unit/Model/AttributeMetadataResolverTest.php diff --git a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php index c936de1bd0230..27c5f77674577 100644 --- a/app/code/Magento/Customer/Model/AttributeMetadataResolver.php +++ b/app/code/Magento/Customer/Model/AttributeMetadataResolver.php @@ -6,17 +6,17 @@ */ namespace Magento\Customer\Model; +use Magento\Customer\Api\Data\AddressInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Model\Config\Share as ShareConfig; use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; +use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; -use Magento\Customer\Api\Data\AddressInterface; -use Magento\Ui\DataProvider\EavValidationRules; -use Magento\Ui\Component\Form\Field; use Magento\Eav\Model\Entity\Type; -use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Customer\Api\Data\CustomerInterface; -use Magento\Customer\Model\Config\Share as ShareConfig; -use Magento\Customer\Model\FileUploaderDataResolver; +use Magento\Ui\Component\Form\Field; +use Magento\Ui\DataProvider\EavValidationRules; /** * Class to build meta data of the customer or customer address attribute @@ -75,25 +75,33 @@ class AttributeMetadataResolver */ private $shareConfig; + /** + * @var GroupManagement + */ + private $groupManagement; + /** * @param CountryWithWebsites $countryWithWebsiteSource * @param EavValidationRules $eavValidationRules * @param FileUploaderDataResolver $fileUploaderDataResolver * @param ContextInterface $context * @param ShareConfig $shareConfig + * @param GroupManagement|null $groupManagement */ public function __construct( CountryWithWebsites $countryWithWebsiteSource, EavValidationRules $eavValidationRules, FileUploaderDataResolver $fileUploaderDataResolver, ContextInterface $context, - ShareConfig $shareConfig + ShareConfig $shareConfig, + ?GroupManagement $groupManagement = null ) { $this->countryWithWebsiteSource = $countryWithWebsiteSource; $this->eavValidationRules = $eavValidationRules; $this->fileUploaderDataResolver = $fileUploaderDataResolver; $this->context = $context; $this->shareConfig = $shareConfig; + $this->groupManagement = $groupManagement ?? ObjectManager::getInstance()->get(GroupManagement::class); } /** @@ -111,6 +119,7 @@ public function getAttributesMeta( bool $allowToShowHiddenAttributes ): array { $meta = $this->modifyBooleanAttributeMeta($attribute); + $this->modifyGroupAttributeMeta($attribute); // use getDataUsingMethod, since some getters are defined and apply additional processing of returning value foreach (self::$metaProperties as $metaName => $origName) { $value = $attribute->getDataUsingMethod($origName); @@ -196,6 +205,21 @@ private function modifyBooleanAttributeMeta(AttributeInterface $attribute): arra return $meta; } + /** + * Modify group attribute meta data + * + * @param AttributeInterface $attribute + * @return void + */ + private function modifyGroupAttributeMeta(AttributeInterface $attribute): void + { + if ($attribute->getAttributeCode() === 'group_id') { + $defaultGroup = $this->groupManagement->getDefaultGroup(); + $defaultGroupId = !empty($defaultGroup) ? $defaultGroup->getId() : null; + $attribute->setDataUsingMethod(self::$metaProperties['default'], $defaultGroupId); + } + } + /** * Add global scope parameter and filter options to website meta * diff --git a/app/code/Magento/Customer/Test/Unit/Model/AttributeMetadataResolverTest.php b/app/code/Magento/Customer/Test/Unit/Model/AttributeMetadataResolverTest.php new file mode 100644 index 0000000000000..aef9d8ca40e85 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/AttributeMetadataResolverTest.php @@ -0,0 +1,166 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Test\Unit\Model; + +use Magento\Customer\Model\Attribute; +use Magento\Customer\Model\AttributeMetadataResolver; +use Magento\Customer\Model\Config\Share as ShareConfig; +use Magento\Customer\Model\FileUploaderDataResolver; +use Magento\Customer\Model\GroupManagement; +use Magento\Customer\Model\ResourceModel\Address\Attribute\Source\CountryWithWebsites; +use Magento\Eav\Model\Entity\Type; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Ui\DataProvider\EavValidationRules; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Class AttributeMetadataResolverTest + * + * Validate attributeMetadata contains correct values in meta data array + */ +class AttributeMetadataResolverTest extends TestCase +{ + /** + * @var CountryWithWebsites|MockObject + */ + private $countryWithWebsiteSource; + + /** + * @var EavValidationRules|MockObject + */ + private $eavValidationRules; + + /** + * @var FileUploaderDataResolver|MockObject + */ + private $fileUploaderDataResolver; + + /** + * @var ShareConfig|MockObject + */ + private $shareConfig; + + /** + * @var GroupManagement|MockObject + */ + private $groupManagement; + + /** + * @var ContextInterface|MockObject + */ + private $context; + + /** + * @var AttributeMetadataResolver + */ + private $model; + + /** + * @var Attribute|MockObject + */ + private $attribute; + + /** + * @inheritdoc + */ + public function setUp() + { + $this->countryWithWebsiteSource = $this->getMockBuilder(CountryWithWebsites::class) + ->setMethods(['getAllOptions']) + ->disableOriginalConstructor() + ->getMock(); + $this->eavValidationRules = $this->getMockBuilder(EavValidationRules::class) + ->setMethods(['build']) + ->disableOriginalConstructor() + ->getMock(); + $this->fileUploaderDataResolver = $this->getMockBuilder(FileUploaderDataResolver::class) + ->setMethods(['overrideFileUploaderMetadata']) + ->disableOriginalConstructor() + ->getMock(); + $this->context = $this->getMockBuilder(ContextInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->shareConfig = $this->getMockBuilder(ShareConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->groupManagement = $this->getMockBuilder(GroupManagement::class) + ->setMethods(['getId', 'getDefaultGroup']) + ->disableOriginalConstructor() + ->getMock(); + $this->attribute = $this->getMockBuilder(Attribute::class) + ->setMethods([ + 'usesSource', + 'getDataUsingMethod', + 'getAttributeCode', + 'getFrontendInput', + 'getSource', + 'setDataUsingMethod' + ]) + ->disableOriginalConstructor() + ->getMock(); + + $this->model = new AttributeMetadataResolver( + $this->countryWithWebsiteSource, + $this->eavValidationRules, + $this->fileUploaderDataResolver, + $this->context, + $this->shareConfig, + $this->groupManagement + ); + } + + /** + * Test to get meta data of the customer or customer address attribute + * + * @return void + */ + public function testGetAttributesMetaHasDefaultAttributeValue(): void + { + $rules = [ + 'required-entry' => true + ]; + $defaultGroupId = '3'; + $allowToShowHiddenAttributes = false; + $usesSource = false; + $entityType = $this->getMockBuilder(Type::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attribute->expects($this->once()) + ->method('usesSource') + ->willReturn($usesSource); + $this->attribute->expects($this->once()) + ->method('getAttributeCode') + ->willReturn('group_id'); + $this->groupManagement->expects($this->once()) + ->method('getDefaultGroup') + ->willReturnSelf(); + $this->groupManagement->expects($this->once()) + ->method('getId') + ->willReturn($defaultGroupId); + $this->attribute->expects($this->at(9)) + ->method('getDataUsingMethod') + ->with('default_value') + ->willReturn($defaultGroupId); + $this->attribute->expects($this->once()) + ->method('setDataUsingMethod') + ->willReturnSelf(); + $this->eavValidationRules->expects($this->once()) + ->method('build') + ->with($this->attribute) + ->willReturn($rules); + $this->fileUploaderDataResolver->expects($this->once()) + ->method('overrideFileUploaderMetadata') + ->with($entityType, $this->attribute) + ->willReturnSelf(); + + $meta = $this->model->getAttributesMeta($this->attribute, $entityType, $allowToShowHiddenAttributes); + $this->assertArrayHasKey('default', $meta['arguments']['data']['config']); + $this->assertEquals($defaultGroupId, $meta['arguments']['data']['config']['default']); + } +} From d81d1d451bb7f10492d249e2c0c70e9d8d1df905 Mon Sep 17 00:00:00 2001 From: Vaha <vaha@atwix.com> Date: Thu, 12 Mar 2020 11:30:25 +0200 Subject: [PATCH 278/369] added product list price modifier to modify price attributes data to price format --- .../AssertAdminProductGridCellActionGroup.xml | 23 ++++ ...dminProductGridColumnOptionActionGroup.xml | 21 ++++ ...learFiltersAdminProductGridActionGroup.xml | 19 ++++ ...esetAdminProductGridColumnsActionGroup.xml | 18 +++ ...nProductGridColumnsDropdownActionGroup.xml | 18 +++ .../Test/Mftf/Data/CustomAttributeData.xml | 8 ++ .../Catalog/Test/Mftf/Data/ProductData.xml | 4 + .../Mftf/Section/AdminProductGridSection.xml | 2 +- ...minCheckProductListPriceAttributesTest.xml | 64 +++++++++++ .../Product/Modifier/PriceAttributes.php | 105 ++++++++++++++++++ app/code/Magento/Catalog/etc/adminhtml/di.xml | 12 ++ .../Test/Mftf/Data/CustomAttributeData.xml | 14 +++ .../Msrp/Test/Mftf/Data/ProductData.xml | 16 +++ ...minCheckProductListPriceAttributesTest.xml | 29 +++++ app/code/Magento/Msrp/etc/adminhtml/di.xml | 7 ++ 15 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductGridCellActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckAdminProductGridColumnOptionActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/ClearFiltersAdminProductGridActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/ResetAdminProductGridColumnsActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/ToggleAdminProductGridColumnsDropdownActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml create mode 100644 app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php create mode 100644 app/code/Magento/Msrp/Test/Mftf/Data/CustomAttributeData.xml create mode 100644 app/code/Magento/Msrp/Test/Mftf/Data/ProductData.xml create mode 100644 app/code/Magento/Msrp/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductGridCellActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductGridCellActionGroup.xml new file mode 100644 index 0000000000000..724c852e7b0be --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductGridCellActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminProductGridCellActionGroup"> + <annotations> + <description>Checks value for Admin Product Grid cell by provided row and column.</description> + </annotations> + <arguments> + <argument name="row" type="string" defaultValue="1"/> + <argument name="column" type="string" defaultValue="Name"/> + <argument name="value" type="string" defaultValue="1"/> + </arguments> + + <see selector="{{AdminProductGridSection.productGridCell(row,column)}}" userInput="{{value}}" stepKey="seeProductGridCellWithProvidedValue"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckAdminProductGridColumnOptionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckAdminProductGridColumnOptionActionGroup.xml new file mode 100644 index 0000000000000..dc10933150617 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckAdminProductGridColumnOptionActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CheckAdminProductGridColumnOptionActionGroup"> + <annotations> + <description>Checks Admin Product Grid 'Columns' option.</description> + </annotations> + <arguments> + <argument name="optionName" type="string" defaultValue="Name"/> + </arguments> + + <checkOption selector="{{AdminProductGridFilterSection.viewColumnOption(optionName)}}" stepKey="checkColumn"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ClearFiltersAdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ClearFiltersAdminProductGridActionGroup.xml new file mode 100644 index 0000000000000..fb75b65120287 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ClearFiltersAdminProductGridActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ClearFiltersAdminProductGridActionGroup"> + <annotations> + <description>Clicks on 'Clear Filters'.</description> + </annotations> + + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <waitForPageLoad stepKey="waitForGridLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ResetAdminProductGridColumnsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ResetAdminProductGridColumnsActionGroup.xml new file mode 100644 index 0000000000000..8e10da66a83af --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ResetAdminProductGridColumnsActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ResetAdminProductGridColumnsActionGroup"> + <annotations> + <description>Clicks 'reset' for Admin Product Grid 'Columns' dropdown.</description> + </annotations> + + <click selector="{{AdminProductGridFilterSection.resetGridColumns}}" stepKey="resetProductGridColumns"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ToggleAdminProductGridColumnsDropdownActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ToggleAdminProductGridColumnsDropdownActionGroup.xml new file mode 100644 index 0000000000000..783c05797c6e1 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/ToggleAdminProductGridColumnsDropdownActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ToggleAdminProductGridColumnsDropdownActionGroup"> + <annotations> + <description>Toggles Admin Product Grid 'Columns' dropdown.</description> + </annotations> + + <click selector="{{AdminProductGridFilterSection.columnsDropdown}}" stepKey="toggleColumnsDropdown"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml index 1effb4ed0664e..9b35ceba0494c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml @@ -31,6 +31,14 @@ <data key="attribute_code">short_description</data> <data key="value" unique="suffix">API Product Short Description</data> </entity> + <entity name="ApiProductSpecialPrice" type="custom_attribute"> + <data key="attribute_code">special_price</data> + <data key="value">51.51</data> + </entity> + <entity name="ApiProductCost" type="custom_attribute"> + <data key="attribute_code">cost</data> + <data key="value">50.05</data> + </entity> <entity name="ApiProductNewsFromDate" type="custom_attribute"> <data key="attribute_code">news_from_date</data> <data key="value">2018-05-17 00:00:00</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index a44db8010a822..a4439e34fbfca 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -175,6 +175,10 @@ <data key="status">1</data> <data key="quantity">0</data> </entity> + <entity name="SimpleOutOfStockProductWithSpecialPriceAndCost" type="product" extends="SimpleOutOfStockProduct"> + <requiredEntity type="custom_attribute_array">ApiProductSpecialPrice</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductCost</requiredEntity> + </entity> <entity name="SimpleProductInStockQuantityZero" type="product"> <data key="name" unique="suffix">SimpleProductInStockQuantityZero</data> <data key="sku" unique="suffix">testSku</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index 07dd26381fe08..1aff1a5031413 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -21,7 +21,7 @@ <element name="firstProductRowEditButton" type="button" selector="table.data-grid tr.data-row td .action-menu-item:first-of-type"/> <element name="productThumbnail" type="text" selector="table.data-grid tr:nth-child({{row}}) td.data-grid-thumbnail-cell > img" parameterized="true"/> <element name="productThumbnailBySrc" type="text" selector="img.admin__control-thumbnail[src*='{{pattern}}']" parameterized="true"/> - <element name="productGridCell" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> + <element name="productGridCell" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[normalize-space(.)='{{column}}']/preceding-sibling::th) +1 ]" parameterized="true"/> <element name="productGridHeaderCell" type="text" selector="//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]" parameterized="true"/> <element name="multicheckDropdown" type="button" selector="div[data-role='grid-wrapper'] th.data-grid-multicheck-cell button.action-multicheck-toggle"/> <element name="multicheckOption" type="button" selector="//div[@data-role='grid-wrapper']//th[contains(@class, data-grid-multicheck-cell)]//li//span[text() = '{{label}}']" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml new file mode 100644 index 0000000000000..f2d6d9442fb18 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckProductListPriceAttributesTest"> + <annotations> + <stories value="Check price attributes values on Admin Product List"/> + <title value="Check price attributes values on Admin Product List."/> + <description value="Login as admin, create simple product, add cost, special price. Go to Admin + Product List page filter grid by created product, add mentioned columns to grid, check values."/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/> + <createData entity="SimpleOutOfStockProductWithSpecialPriceAndCost" stepKey="createSimpleProduct"/> + + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="adminOpenProductIndexPage"/> + <actionGroup ref="FilterProductGridBySkuActionGroup" stepKey="filterProductGridByCreatedSimpleProductSku"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + </before> + <after> + <actionGroup ref="ClearFiltersAdminProductGridActionGroup" stepKey="clearFiltersAdminProductGrid"/> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="openToResetColumnsDropdown"/> + <actionGroup ref="ResetAdminProductGridColumnsActionGroup" stepKey="resetAdminProductGridColumns"/> + + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + </after> + + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="openColumnsDropdown"/> + <actionGroup ref="CheckAdminProductGridColumnOptionActionGroup" stepKey="checkSpecialPriceOption"> + <argument name="optionName" value="Special Price"/> + </actionGroup> + <actionGroup ref="CheckAdminProductGridColumnOptionActionGroup" stepKey="checkCostOption"> + <argument name="optionName" value="Cost"/> + </actionGroup> + <actionGroup ref="ToggleAdminProductGridColumnsDropdownActionGroup" stepKey="closeColumnsDropdown"/> + + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="seePrice"> + <argument name="row" value="1"/> + <argument name="column" value="Price"/> + <argument name="value" value="${{SimpleOutOfStockProduct.price}}"/> + </actionGroup> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="seeCorrectSpecialPrice"> + <argument name="row" value="1"/> + <argument name="column" value="Special Price"/> + <argument name="value" value="${{ApiProductSpecialPrice.value}}"/> + </actionGroup> + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="seeCorrectCost"> + <argument name="row" value="1"/> + <argument name="column" value="Cost"/> + <argument name="value" value="${{ApiProductCost.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php new file mode 100644 index 0000000000000..7f333441dab34 --- /dev/null +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Modifier/PriceAttributes.php @@ -0,0 +1,105 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Ui\DataProvider\Product\Modifier; + +use Magento\Framework\Currency; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Locale\CurrencyInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Ui\DataProvider\Modifier\ModifierInterface; + +/** + * Modify product listing price attributes + */ +class PriceAttributes implements ModifierInterface +{ + /** + * @var array + */ + private $priceAttributeList; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var CurrencyInterface + */ + private $localeCurrency; + + /** + * PriceAttributes constructor. + * + * @param StoreManagerInterface $storeManager + * @param CurrencyInterface $localeCurrency + * @param array $priceAttributeList + */ + public function __construct( + StoreManagerInterface $storeManager, + CurrencyInterface $localeCurrency, + array $priceAttributeList = [] + ) { + $this->storeManager = $storeManager; + $this->localeCurrency = $localeCurrency; + $this->priceAttributeList = $priceAttributeList; + } + + /** + * @inheritdoc + */ + public function modifyData(array $data): array + { + if (empty($data) || empty($this->priceAttributeList)) { + return $data; + } + + foreach ($data['items'] as &$item) { + foreach ($this->priceAttributeList as $priceAttribute) { + if (isset($item[$priceAttribute])) { + $item[$priceAttribute] = $this->getCurrency()->toCurrency(sprintf("%f", $item[$priceAttribute])); + } + } + } + + return $data; + } + + /** + * @inheritdoc + */ + public function modifyMeta(array $meta): array + { + return $meta; + } + + /** + * Retrieve store + * + * @return StoreInterface + * @throws NoSuchEntityException + */ + private function getStore(): StoreInterface + { + return $this->storeManager->getStore(); + } + + /** + * Retrieve currency + * + * @return Currency + * @throws NoSuchEntityException + */ + private function getCurrency(): Currency + { + $baseCurrencyCode = $this->getStore()->getBaseCurrencyCode(); + + return $this->localeCurrency->getCurrency($baseCurrencyCode); + } +} diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index 4f905cd85f3d1..7d74ab38a8560 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -177,6 +177,10 @@ <item name="class" xsi:type="string">Magento\Catalog\Ui\DataProvider\Product\Modifier\Attributes</item> <item name="sortOrder" xsi:type="number">10</item> </item> + <item name="priceAttributes" xsi:type="array"> + <item name="class" xsi:type="string">Magento\Catalog\Ui\DataProvider\Product\Modifier\PriceAttributes</item> + <item name="sortOrder" xsi:type="number">10</item> + </item> </argument> </arguments> </virtualType> @@ -188,6 +192,14 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Ui\DataProvider\Product\Modifier\PriceAttributes"> + <arguments> + <argument name="priceAttributeList" xsi:type="array"> + <item name="cost" xsi:type="string">cost</item> + <item name="special_price" xsi:type="string">special_price</item> + </argument> + </arguments> + </type> <type name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\CustomOptions"> <arguments> <argument name="scopeName" xsi:type="string">product_form.product_form</argument> diff --git a/app/code/Magento/Msrp/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Msrp/Test/Mftf/Data/CustomAttributeData.xml new file mode 100644 index 0000000000000..6f620c8f9aa7f --- /dev/null +++ b/app/code/Magento/Msrp/Test/Mftf/Data/CustomAttributeData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ApiProductMsrp" type="custom_attribute"> + <data key="attribute_code">msrp</data> + <data key="value">111.11</data> + </entity> +</entities> diff --git a/app/code/Magento/Msrp/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Msrp/Test/Mftf/Data/ProductData.xml new file mode 100644 index 0000000000000..aa79b6032df4b --- /dev/null +++ b/app/code/Magento/Msrp/Test/Mftf/Data/ProductData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SimpleOutOfStockProductWithSpecialPriceCostAndMsrp" type="product" extends="SimpleOutOfStockProduct"> + <requiredEntity type="custom_attribute_array">ApiProductSpecialPrice</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductCost</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductMsrp</requiredEntity> + </entity> +</entities> diff --git a/app/code/Magento/Msrp/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml b/app/code/Magento/Msrp/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml new file mode 100644 index 0000000000000..874edf0dff9e3 --- /dev/null +++ b/app/code/Magento/Msrp/Test/Mftf/Test/AdminCheckProductListPriceAttributesTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckProductListPriceAttributesTest"> + <annotations> + <group value="msrp"/> + </annotations> + <before> + <createData entity="SimpleOutOfStockProductWithSpecialPriceCostAndMsrp" stepKey="createSimpleProduct"/> + </before> + + <actionGroup ref="CheckAdminProductGridColumnOptionActionGroup" stepKey="checkMsrpOption" after="checkCostOption"> + <argument name="optionName" value="Minimum Advertised Price"/> + </actionGroup> + + <actionGroup ref="AssertAdminProductGridCellActionGroup" stepKey="seeCorrectMsrp" after="seeCorrectCost"> + <argument name="row" value="1"/> + <argument name="column" value="Minimum Advertised Price"/> + <argument name="value" value="${{ApiProductMsrp.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Msrp/etc/adminhtml/di.xml b/app/code/Magento/Msrp/etc/adminhtml/di.xml index 6126e9132cd7a..d517bd0aa4e7f 100644 --- a/app/code/Magento/Msrp/etc/adminhtml/di.xml +++ b/app/code/Magento/Msrp/etc/adminhtml/di.xml @@ -19,4 +19,11 @@ </argument> </arguments> </virtualType> + <type name="Magento\Catalog\Ui\DataProvider\Product\Modifier\PriceAttributes"> + <arguments> + <argument name="priceAttributeList" xsi:type="array"> + <item name="msrp" xsi:type="string">msrp</item> + </argument> + </arguments> + </type> </config> From f7819a64af4ce9789fa86a71948870eaccbf8c95 Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Thu, 12 Mar 2020 12:18:45 +0200 Subject: [PATCH 279/369] Added an error logging --- .../Controller/Adminhtml/Order/Create/Reorder.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php index 925e8bfdd05aa..eeaf4bee1b1c2 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Reorder.php @@ -20,6 +20,7 @@ use Magento\Sales\Helper\Reorder as ReorderHelper; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Reorder\UnavailableProductsProvider; +use Psr\Log\LoggerInterface; /** * Controller create order. @@ -41,6 +42,11 @@ class Reorder extends Create implements HttpGetActionInterface */ private $reorderHelper; + /** + * @var LoggerInterface + */ + private $logger; + /** * @param Action\Context $context * @param Product $productHelper @@ -50,6 +56,7 @@ class Reorder extends Create implements HttpGetActionInterface * @param UnavailableProductsProvider $unavailableProductsProvider * @param OrderRepositoryInterface $orderRepository * @param ReorderHelper $reorderHelper + * @param LoggerInterface $logger */ public function __construct( Action\Context $context, @@ -59,11 +66,13 @@ public function __construct( ForwardFactory $resultForwardFactory, UnavailableProductsProvider $unavailableProductsProvider, OrderRepositoryInterface $orderRepository, - ReorderHelper $reorderHelper + ReorderHelper $reorderHelper, + LoggerInterface $logger ) { $this->unavailableProductsProvider = $unavailableProductsProvider; $this->orderRepository = $orderRepository; $this->reorderHelper = $reorderHelper; + $this->logger = $logger; parent::__construct( $context, $productHelper, @@ -110,9 +119,11 @@ public function execute() $this->_getOrderCreateModel()->initFromOrder($order); $resultRedirect->setPath('sales/*'); } catch (\Magento\Framework\Exception\LocalizedException $e) { + $this->logger->critical($e); $this->messageManager->addErrorMessage($e->getMessage()); return $resultRedirect->setPath('sales/*'); } catch (\Exception $e) { + $this->logger->critical($e); $this->messageManager->addException($e, __('Error while processing order.')); return $resultRedirect->setPath('sales/*'); } From 983a2fb56fe89bf62cf0e57ba60b60ff1b1414aa Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Thu, 12 Mar 2020 12:44:29 +0200 Subject: [PATCH 280/369] MC-32152: Backend cart in customer detail view --- .../Block/Adminhtml/Edit/Tab/Cart.php | 130 ++++++++++++++---- .../Controller/Adminhtml/Index/Cart.php | 120 ++++++++++------ .../tab/cart_website_filter_form.phtml | 10 ++ .../Block/Adminhtml/Edit/Tab/CartsTest.php | 4 +- .../Controller/Adminhtml/IndexTest.php | 2 +- 5 files changed, 195 insertions(+), 71 deletions(-) create mode 100644 app/code/Magento/Customer/view/adminhtml/templates/tab/cart_website_filter_form.phtml diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php index ec4bd93ee4ff0..656a78d1165e3 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php @@ -5,37 +5,51 @@ */ namespace Magento\Customer\Block\Adminhtml\Edit\Tab; -use Magento\Catalog\Model\Product; +use Magento\Backend\Block\Template\Context; +use Magento\Backend\Block\Widget\Form; +use Magento\Backend\Block\Widget\Grid\Extended; +use Magento\Backend\Helper\Data; +use Magento\Customer\Block\Adminhtml\Edit\Tab\View\Grid\Renderer\Item; +use Magento\Customer\Block\Adminhtml\Grid\Renderer\Multiaction; use Magento\Customer\Controller\RegistryConstants; -use Magento\Directory\Model\Currency; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Data\CollectionFactory; +use Magento\Framework\Data\FormFactory; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteFactory; +use Magento\Store\Model\System\Store as SystemStore; /** * Adminhtml customer orders grid block * * @api * @since 100.0.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Cart extends \Magento\Backend\Block\Widget\Grid\Extended +class Cart extends Extended { /** * Core registry * - * @var \Magento\Framework\Registry + * @var Registry */ protected $_coreRegistry = null; /** - * @var \Magento\Framework\Data\CollectionFactory + * @var CollectionFactory */ protected $_dataCollectionFactory; /** - * @var \Magento\Quote\Api\CartRepositoryInterface + * @var CartRepositoryInterface */ protected $quoteRepository; /** - * @var \Magento\Quote\Model\Quote + * @var Quote */ protected $quote = null; @@ -45,32 +59,46 @@ class Cart extends \Magento\Backend\Block\Widget\Grid\Extended protected $_parentTemplate; /** - * @var \Magento\Quote\Model\QuoteFactory + * @var QuoteFactory */ protected $quoteFactory; + /** + * @var SystemStore + */ + private $systemStore; + /** + * @var FormFactory + */ + private $formFactory; /** - * @param \Magento\Backend\Block\Template\Context $context - * @param \Magento\Backend\Helper\Data $backendHelper - * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository - * @param \Magento\Framework\Data\CollectionFactory $dataCollectionFactory - * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Quote\Model\QuoteFactory $quoteFactory + * @param Context $context + * @param Data $backendHelper + * @param CartRepositoryInterface $quoteRepository + * @param CollectionFactory $dataCollectionFactory + * @param Registry $coreRegistry + * @param QuoteFactory $quoteFactory * @param array $data + * @param SystemStore|null $systemStore + * @param FormFactory|null $formFactory */ public function __construct( - \Magento\Backend\Block\Template\Context $context, - \Magento\Backend\Helper\Data $backendHelper, - \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, - \Magento\Framework\Data\CollectionFactory $dataCollectionFactory, - \Magento\Framework\Registry $coreRegistry, - \Magento\Quote\Model\QuoteFactory $quoteFactory, - array $data = [] + Context $context, + Data $backendHelper, + CartRepositoryInterface $quoteRepository, + CollectionFactory $dataCollectionFactory, + Registry $coreRegistry, + QuoteFactory $quoteFactory, + array $data = [], + ?SystemStore $systemStore = null, + ?FormFactory $formFactory = null ) { $this->_dataCollectionFactory = $dataCollectionFactory; $this->_coreRegistry = $coreRegistry; $this->quoteRepository = $quoteRepository; $this->quoteFactory = $quoteFactory; + $this->systemStore = $systemStore ?? ObjectManager::getInstance()->get(SystemStore::class); + $this->formFactory = $formFactory ?? ObjectManager::getInstance()->get(FormFactory::class); parent::__construct($context, $backendHelper, $data); } @@ -92,8 +120,11 @@ protected function _construct() */ protected function _prepareGrid() { - $this->setId('customer_cart_grid' . $this->getWebsiteId()); + $this->setId('customer_cart_grid'); parent::_prepareGrid(); + if (!$this->_storeManager->isSingleStoreMode()) { + $this->prepareWebsiteFilter(); + } } /** @@ -129,7 +160,7 @@ protected function _prepareColumns() [ 'header' => __('Product'), 'index' => 'name', - 'renderer' => \Magento\Customer\Block\Adminhtml\Edit\Tab\View\Grid\Renderer\Item::class + 'renderer' => Item::class ] ); @@ -167,7 +198,7 @@ protected function _prepareColumns() [ 'header' => __('Action'), 'index' => 'quote_item_id', - 'renderer' => \Magento\Customer\Block\Adminhtml\Grid\Renderer\Multiaction::class, + 'renderer' => Multiaction::class, 'filter' => false, 'sortable' => false, 'actions' => [ @@ -245,10 +276,59 @@ protected function getQuote() try { $this->quote = $this->quoteRepository->getForCustomer($customerId, $storeIds); - } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + } catch (NoSuchEntityException $e) { $this->quote = $this->quoteFactory->create()->setSharedStoreIds($storeIds); } } return $this->quote; } + + /** + * Add website filter block to the layout + * + * @return void + */ + private function prepareWebsiteFilter(): void + { + $form = $this->formFactory->create(); + $form->addField( + 'website_filter', + 'select', + [ + 'name' => 'website_id', + 'values' => $this->systemStore->getWebsiteOptionHash(), + 'value' => $this->getWebsiteId() ?? $this->_storeManager->getWebsite()->getId(), + 'no_span' => true, + 'onchange' => "{$this->getJsObjectName()}.loadByElement(this);", + ] + ); + /** + * @var Form $formWidget + */ + $formWidget = $this->getLayout()->createBlock(Form::class); + $formWidget->setForm($form); + $formWidget->setTemplate('Magento_Customer::tab/cart_website_filter_form.phtml'); + $this->setChild( + 'website_filter_block', + $formWidget + ); + } + + /** + * @inheritDoc + */ + public function getMainButtonsHtml() + { + return $this->getWebsiteFilterHtml() . parent::getMainButtonsHtml(); + } + + /** + * Generate website filter + * + * @return string + */ + private function getWebsiteFilterHtml(): string + { + return $this->getChildHtml('website_filter_block'); + } } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Cart.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Cart.php index 1e4c1fb001ea3..6528ac4c1f211 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Cart.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Cart.php @@ -5,84 +5,116 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\View\Result\ForwardFactory; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\AddressRepositoryInterface; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\AddressInterfaceFactory; use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Customer\Controller\Adminhtml\Index as BaseAction; +use Magento\Customer\Helper\View; use Magento\Customer\Model\Address\Mapper; -use Magento\Framework\DataObjectFactory as ObjectFactory; +use Magento\Customer\Model\AddressFactory; +use Magento\Customer\Model\CustomerFactory; +use Magento\Customer\Model\Metadata\FormFactory; use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\DataObjectFactory as ObjectFactory; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Math\Random; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Framework\Registry; +use Magento\Framework\View\Result\Layout; +use Magento\Framework\View\Result\LayoutFactory; +use Magento\Framework\View\Result\PageFactory; +use Magento\Newsletter\Model\SubscriberFactory; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteFactory; +use Magento\Store\Model\StoreManagerInterface; /** + * Admin customer shopping cart controller + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @deprecated 100.2.0 */ -class Cart extends \Magento\Customer\Controller\Adminhtml\Index +class Cart extends BaseAction implements HttpGetActionInterface, HttpPostActionInterface { /** - * @var \Magento\Quote\Model\QuoteFactory + * @var QuoteFactory */ private $quoteFactory; + /** + * @var StoreManagerInterface + */ + private $storeManager; /** * Constructor * - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory - * @param \Magento\Customer\Model\CustomerFactory $customerFactory - * @param \Magento\Customer\Model\AddressFactory $addressFactory - * @param \Magento\Customer\Model\Metadata\FormFactory $formFactory - * @param \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory - * @param \Magento\Customer\Helper\View $viewHelper - * @param \Magento\Framework\Math\Random $random + * @param Context $context + * @param Registry $coreRegistry + * @param FileFactory $fileFactory + * @param CustomerFactory $customerFactory + * @param AddressFactory $addressFactory + * @param FormFactory $formFactory + * @param SubscriberFactory $subscriberFactory + * @param View $viewHelper + * @param Random $random * @param CustomerRepositoryInterface $customerRepository - * @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter + * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter * @param Mapper $addressMapper * @param AccountManagementInterface $customerAccountManagement * @param AddressRepositoryInterface $addressRepository * @param CustomerInterfaceFactory $customerDataFactory * @param AddressInterfaceFactory $addressDataFactory * @param \Magento\Customer\Model\Customer\Mapper $customerMapper - * @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor + * @param DataObjectProcessor $dataObjectProcessor * @param DataObjectHelper $dataObjectHelper * @param ObjectFactory $objectFactory * @param \Magento\Framework\View\LayoutFactory $layoutFactory - * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory - * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory - * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory - * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory - * @param \Magento\Quote\Model\QuoteFactory|null $quoteFactory + * @param LayoutFactory $resultLayoutFactory + * @param PageFactory $resultPageFactory + * @param ForwardFactory $resultForwardFactory + * @param JsonFactory $resultJsonFactory + * @param QuoteFactory|null $quoteFactory + * @param StoreManagerInterface|null $storeManager * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\Framework\Registry $coreRegistry, - \Magento\Framework\App\Response\Http\FileFactory $fileFactory, - \Magento\Customer\Model\CustomerFactory $customerFactory, - \Magento\Customer\Model\AddressFactory $addressFactory, - \Magento\Customer\Model\Metadata\FormFactory $formFactory, - \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory, - \Magento\Customer\Helper\View $viewHelper, - \Magento\Framework\Math\Random $random, + Context $context, + Registry $coreRegistry, + FileFactory $fileFactory, + CustomerFactory $customerFactory, + AddressFactory $addressFactory, + FormFactory $formFactory, + SubscriberFactory $subscriberFactory, + View $viewHelper, + Random $random, CustomerRepositoryInterface $customerRepository, - \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter, + ExtensibleDataObjectConverter $extensibleDataObjectConverter, Mapper $addressMapper, AccountManagementInterface $customerAccountManagement, AddressRepositoryInterface $addressRepository, CustomerInterfaceFactory $customerDataFactory, AddressInterfaceFactory $addressDataFactory, \Magento\Customer\Model\Customer\Mapper $customerMapper, - \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor, + DataObjectProcessor $dataObjectProcessor, DataObjectHelper $dataObjectHelper, ObjectFactory $objectFactory, \Magento\Framework\View\LayoutFactory $layoutFactory, - \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory, - \Magento\Framework\View\Result\PageFactory $resultPageFactory, - \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory, - \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, - \Magento\Quote\Model\QuoteFactory $quoteFactory = null + LayoutFactory $resultLayoutFactory, + PageFactory $resultPageFactory, + ForwardFactory $resultForwardFactory, + JsonFactory $resultJsonFactory, + QuoteFactory $quoteFactory = null, + ?StoreManagerInterface $storeManager = null ) { parent::__construct( $context, @@ -111,13 +143,14 @@ public function __construct( $resultForwardFactory, $resultJsonFactory ); - $this->quoteFactory = $quoteFactory ?: $this->_objectManager->get(\Magento\Quote\Model\QuoteFactory::class); + $this->quoteFactory = $quoteFactory ?: $this->_objectManager->get(QuoteFactory::class); + $this->storeManager = $storeManager ?? $this->_objectManager->get(StoreManagerInterface::class); } /** * Handle and then get cart grid contents * - * @return \Magento\Framework\View\Result\Layout + * @return Layout */ public function execute() { @@ -127,16 +160,17 @@ public function execute() // delete an item from cart $deleteItemId = $this->getRequest()->getPost('delete'); if ($deleteItemId) { - /** @var \Magento\Quote\Api\CartRepositoryInterface $quoteRepository */ - $quoteRepository = $this->_objectManager->create(\Magento\Quote\Api\CartRepositoryInterface::class); - /** @var \Magento\Quote\Model\Quote $quote */ + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->_objectManager->create(CartRepositoryInterface::class); + /** @var Quote $quote */ try { - $quote = $quoteRepository->getForCustomer($customerId); - } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $storeIds = $this->storeManager->getWebsite($websiteId)->getStoreIds(); + $quote = $quoteRepository->getForCustomer($customerId, $storeIds); + } catch (NoSuchEntityException $e) { $quote = $this->quoteFactory->create(); } $quote->setWebsite( - $this->_objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->getWebsite($websiteId) + $this->storeManager->getWebsite($websiteId) ); $item = $quote->getItemById($deleteItemId); if ($item && $item->getId()) { diff --git a/app/code/Magento/Customer/view/adminhtml/templates/tab/cart_website_filter_form.phtml b/app/code/Magento/Customer/view/adminhtml/templates/tab/cart_website_filter_form.phtml new file mode 100644 index 0000000000000..ec903fa978fce --- /dev/null +++ b/app/code/Magento/Customer/view/adminhtml/templates/tab/cart_website_filter_form.phtml @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $block \Magento\Backend\Block\Widget\Form */ +?> +<?= $block->getFormHtml() ?> +<?= $block->getChildHtml('form_after') ?> diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php index fa8577c6c6a40..604f7d8fcb2a1 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/CartsTest.php @@ -55,12 +55,12 @@ public function testGetHtml() ); $html = $this->_block->toHtml(); - $this->assertContains("<div id=\"customer_cart_grid1\"", $html); + $this->assertContains("<div id=\"customer_cart_grid\"", $html); $this->assertRegExp( '/<div class=".*admin__data-grid-toolbar"/', $html ); - $this->assertContains("customer_cart_grid1JsObject = new varienGrid(\"customer_cart_grid1\",", $html); + $this->assertContains("customer_cart_gridJsObject = new varienGrid(\"customer_cart_grid\",", $html); $this->assertContains( 'backend\u002Fcustomer\u002Fcart_product_composite_cart\u002Fconfigure\u002Fwebsite_id\u002F1', $html diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 4a7cc7591f7aa..1442449f6aedd 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -434,7 +434,7 @@ public function testCartAction() $this->getRequest()->setParam('id', 1)->setParam('website_id', 1)->setPostValue('delete', 1); $this->dispatch('backend/customer/index/cart'); $body = $this->getResponse()->getBody(); - $this->assertContains('<div id="customer_cart_grid1"', $body); + $this->assertContains('<div id="customer_cart_grid"', $body); } /** From f0a412f8a046b81ca1cb520d579113ec451f6554 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Thu, 12 Mar 2020 15:39:58 +0200 Subject: [PATCH 281/369] MC-32175: [Page builder] Category page returns 500 error --- .../Magento/Catalog/Block/Product/View.php | 4 +- .../Magento/Review/Block/Product/View.php | 9 +++- .../Review/Block/Product/View/ListView.php | 5 +- .../Test/Unit/Block/Product/ListViewTest.php | 49 +++++++++++++++++++ 4 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 app/code/Magento/Review/Test/Unit/Block/Product/ListViewTest.php diff --git a/app/code/Magento/Catalog/Block/Product/View.php b/app/code/Magento/Catalog/Block/Product/View.php index ed6278c2b585d..437171bcb4bc6 100644 --- a/app/code/Magento/Catalog/Block/Product/View.php +++ b/app/code/Magento/Catalog/Block/Product/View.php @@ -323,9 +323,9 @@ public function getQuantityValidators() */ public function getIdentities() { - $identities = $this->getProduct()->getIdentities(); + $product = $this->getProduct(); - return $identities; + return $product ? $product->getIdentities() : []; } /** diff --git a/app/code/Magento/Review/Block/Product/View.php b/app/code/Magento/Review/Block/Product/View.php index c7b813ea8eed9..c66e3e50b919b 100644 --- a/app/code/Magento/Review/Block/Product/View.php +++ b/app/code/Magento/Review/Block/Product/View.php @@ -82,13 +82,20 @@ public function __construct( */ protected function _toHtml() { - $this->getProduct()->setShortDescription(null); + $product = $this->getProduct(); + + if (!$product) { + return ''; + } + + $product->setShortDescription(null); return parent::_toHtml(); } /** * Replace review summary html with more detailed review summary + * * Reviews collection count will be jerked here * * @param \Magento\Catalog\Model\Product $product diff --git a/app/code/Magento/Review/Block/Product/View/ListView.php b/app/code/Magento/Review/Block/Product/View/ListView.php index 5df8a3698e537..2d3d1f6637f1f 100644 --- a/app/code/Magento/Review/Block/Product/View/ListView.php +++ b/app/code/Magento/Review/Block/Product/View/ListView.php @@ -55,7 +55,10 @@ protected function _prepareLayout() */ protected function _beforeToHtml() { - $this->getReviewsCollection()->load()->addRateVotes(); + if ($this->getProductId()) { + $this->getReviewsCollection()->load()->addRateVotes(); + } + return parent::_beforeToHtml(); } diff --git a/app/code/Magento/Review/Test/Unit/Block/Product/ListViewTest.php b/app/code/Magento/Review/Test/Unit/Block/Product/ListViewTest.php new file mode 100644 index 0000000000000..a1cc7a05dfef5 --- /dev/null +++ b/app/code/Magento/Review/Test/Unit/Block/Product/ListViewTest.php @@ -0,0 +1,49 @@ +<?php +/** + * Test class for \Magento\Review\Block\Product\View\ListView + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Review\Test\Unit\Block\Product; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Review\Block\Product\View\ListView; +use PHPUnit\Framework\TestCase; + +/** + * Class ViewTest + */ +class ListViewTest extends TestCase +{ + /** + * @var ListView + */ + private $listView; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + $this->listView = $this->objectManager->getObject( + ListView::class + ); + } + + /** + * Validate that ListView->toHtml() would not crush if provided product is null + */ + public function testBlockShouldNotFailWithNullProduct() + { + $output = $this->listView->toHtml(); + $this->assertEquals('', $output); + } +} From 2d8721912c321c2098db2e458ec1a0c6c1155658 Mon Sep 17 00:00:00 2001 From: "vadim.malesh" <engcom-vendorworker-charlie@adobe.com> Date: Thu, 12 Mar 2020 18:41:35 +0200 Subject: [PATCH 282/369] changes requested; add out stock product to cart test --- .../Guest/AddSimpleProductToCartTest.php | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php index 3bdce6ea98b30..e5953e7b7ad72 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php @@ -80,24 +80,48 @@ public function testAddSimpleProductToCart() self::assertEquals('USD', $rowTotalIncludingTax['currency']); } + /** + * Add disabled product to cart + * + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @return void + */ + public function testAddDisabledProductToCart(): void + { + $sku = 'simple3'; + $quantity = 2; + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); + + $this->expectException(ResponseContainsErrorsException::class); + $this->expectExceptionMessage( + 'Could not add the product with SKU ' . $sku . ' to the shopping cart: ' . + 'Product that you are trying to add is not available.' + ); + + $this->graphQlMutation($query); + } + /** * Add out of stock product to cart * - * @@magentoApiDataFixture Magento/Catalog/_files/multiple_products.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual_out_of_stock.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @return void */ - public function testAddProductToCartWithError(): void + public function testAddOutOfStockProductToCart(): void { - $disabledProductSku = 'simple3'; + $sku = 'virtual-product-out'; $quantity = 2; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->getQuery($maskedQuoteId, $disabledProductSku, $quantity); + $query = $this->getQuery($maskedQuoteId, $sku, $quantity); $this->expectException(ResponseContainsErrorsException::class); $this->expectExceptionMessage( - 'Could not add the product with SKU simple3 to the shopping cart: ' . + 'Could not add the product with SKU ' . $sku . ' to the shopping cart: ' . 'Product that you are trying to add is not available.' ); From aaf50094ad8d14b652bce64f2ee81c4472b7ab8d Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Thu, 12 Mar 2020 12:48:18 -0500 Subject: [PATCH 283/369] MC-31986: Add support for ES 7 to 2.4-develop -fix after CR --- .../Adapter/DataMapper/ProductDataMapper.php | 451 ------------- .../DataMapper/ProductDataMapperProxy.php | 54 -- .../Adapter/DataMapper/DataMapperResolver.php | 92 --- .../Model/Adapter/Elasticsearch.php | 11 - .../Magento/Elasticsearch/Model/Config.php | 1 - .../Model/Client/ElasticsearchTest.php | 601 ++++++++++++++++++ .../Model/Adapter/Container/AttributeTest.php | 237 ------- .../Unit/Model/Adapter/ElasticsearchTest.php | 8 - app/code/Magento/Elasticsearch/etc/config.xml | 7 - app/code/Magento/Elasticsearch/etc/di.xml | 14 - app/code/Magento/Elasticsearch6/etc/di.xml | 17 - .../FieldName/Resolver/DefaultResolver.php | 8 +- app/code/Magento/Elasticsearch7/etc/di.xml | 16 +- .../Magento/Elasticsearch7/etc/module.xml | 3 - .../Controller/Advanced/ResultTest.php | 3 +- .../Controller/Result/IndexTest.php | 2 +- .../Fulltext/Action/DataProviderTest.php | 3 +- .../Model/QuickSearchTest.php | 4 +- .../SearchAdapter/AdapterTest.php | 5 +- .../Test/Legacy/_files/obsolete_classes.php | 9 +- .../Framework/ObjectManager/etc/config.xsd | 1 + 21 files changed, 625 insertions(+), 922 deletions(-) delete mode 100644 app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php delete mode 100644 app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapperProxy.php delete mode 100644 app/code/Magento/Elasticsearch/Model/Adapter/DataMapper/DataMapperResolver.php create mode 100644 app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php delete mode 100644 app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Container/AttributeTest.php diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php deleted file mode 100644 index 91bde497c612b..0000000000000 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapper.php +++ /dev/null @@ -1,451 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper; - -use Magento\Catalog\Model\ResourceModel\Eav\Attribute; -use Magento\Elasticsearch\Model\Adapter\Container\Attribute as AttributeContainer; -use Magento\Elasticsearch\Model\Adapter\Document\Builder; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Api\Data\GroupInterface; -use Magento\Elasticsearch\Model\ResourceModel\Index; -use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; -use Magento\Elasticsearch\Model\Adapter\DataMapperInterface; -use Magento\Elasticsearch\Model\Adapter\FieldType\Date as DateFieldType; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; - -/** - * Don't use this product data mapper class. - * - * @deprecated 100.2.0 - * @see \Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface - */ -class ProductDataMapper implements DataMapperInterface -{ - /** - * Attribute code for image - */ - const MEDIA_ROLE_IMAGE = 'image'; - - /** - * Attribute code for small image - */ - const MEDIA_ROLE_SMALL_IMAGE = 'small_image'; - - /** - * Attribute code for thumbnail - */ - const MEDIA_ROLE_THUMBNAIL = 'thumbnail'; - - /** - * Attribute code for swatches - */ - const MEDIA_ROLE_SWATCH_IMAGE = 'swatch_image'; - - /** - * @var Builder - */ - private $builder; - - /** - * @var AttributeContainer - */ - private $attributeContainer; - - /** - * @var Index - */ - private $resourceIndex; - - /** - * @var FieldMapperInterface - */ - private $fieldMapper; - - /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * @var DateFieldType - */ - private $dateFieldType; - - /** - * Media gallery roles - * - * @var array - */ - protected $mediaGalleryRoles; - - /** - * @var AttributeProvider - */ - private $attributeAdapterProvider; - - /** - * @var ResolverInterface - */ - private $fieldNameResolver; - - /** - * Construction for DocumentDataMapper - * - * @param Builder $builder - * @param AttributeContainer $attributeContainer - * @param Index $resourceIndex - * @param FieldMapperInterface $fieldMapper - * @param StoreManagerInterface $storeManager - * @param DateFieldType $dateFieldType - * @param AttributeProvider $attributeAdapterProvider - * @param ResolverInterface $fieldNameResolver - */ - public function __construct( - Builder $builder, - AttributeContainer $attributeContainer, - Index $resourceIndex, - FieldMapperInterface $fieldMapper, - StoreManagerInterface $storeManager, - DateFieldType $dateFieldType, - AttributeProvider $attributeAdapterProvider, - ResolverInterface $fieldNameResolver - ) { - $this->builder = $builder; - $this->attributeContainer = $attributeContainer; - $this->resourceIndex = $resourceIndex; - $this->fieldMapper = $fieldMapper; - $this->storeManager = $storeManager; - $this->dateFieldType = $dateFieldType; - $this->attributeAdapterProvider = $attributeAdapterProvider; - $this->fieldNameResolver = $fieldNameResolver; - - $this->mediaGalleryRoles = [ - self::MEDIA_ROLE_IMAGE, - self::MEDIA_ROLE_SMALL_IMAGE, - self::MEDIA_ROLE_THUMBNAIL, - self::MEDIA_ROLE_SWATCH_IMAGE - ]; - } - - /** - * Prepare index data for using in search engine metadata. - * - * @param int $productId - * @param array $indexData - * @param int $storeId - * @param array $context - * @return array|false - */ - public function map($productId, array $indexData, $storeId, $context = []) - { - $this->builder->addField('store_id', $storeId); - if (count($indexData)) { - $productIndexData = $this->resourceIndex->getFullProductIndexData($productId, $indexData); - } - - foreach ($productIndexData as $attributeCode => $value) { - // Prepare processing attribute info - if (strpos($attributeCode, '_value') !== false) { - $this->builder->addField($attributeCode, $value); - continue; - } - $attribute = $this->attributeContainer->getAttribute($attributeCode); - if (!$attribute || - in_array( - $attributeCode, - [ - 'price', - 'media_gallery', - 'tier_price', - 'quantity_and_stock_status', - 'media_gallery', - 'giftcard_amounts' - ] - ) - ) { - continue; - } - $attribute->setStoreId($storeId); - $value = $this->checkValue($value, $attribute, $storeId); - $this->builder->addField( - $this->fieldMapper->getFieldName( - $attributeCode, - $context - ), - $value - ); - } - $this->processAdvancedAttributes($productId, $productIndexData, $storeId); - - return $this->builder->build(); - } - - /** - * Process advanced attribute values - * - * @param int $productId - * @param array $productIndexData - * @param int $storeId - * @return void - */ - protected function processAdvancedAttributes($productId, array $productIndexData, $storeId) - { - $mediaGalleryRoles = array_fill_keys($this->mediaGalleryRoles, ''); - $productPriceIndexData = $this->attributeContainer->getAttribute('price') - ? $this->resourceIndex->getPriceIndexData([$productId], $storeId) - : []; - $productCategoryIndexData = $this->resourceIndex->getFullCategoryProductIndexData( - $storeId, - [$productId => $productId] - ); - foreach ($productIndexData as $attributeCode => $value) { - if (in_array($attributeCode, $this->mediaGalleryRoles)) { - $mediaGalleryRoles[$attributeCode] = $value; - } elseif ($attributeCode == 'tier_price') { - $this->builder->addFields($this->getProductTierPriceData($value)); - } elseif ($attributeCode == 'quantity_and_stock_status') { - $this->builder->addFields($this->getQtyAndStatus($value)); - } elseif ($attributeCode == 'media_gallery') { - $this->builder->addFields( - $this->getProductMediaGalleryData( - $value, - $mediaGalleryRoles - ) - ); - } - } - $this->builder->addFields($this->getProductPriceData($productId, $storeId, $productPriceIndexData)); - $this->builder->addFields($this->getProductCategoryData($productId, $productCategoryIndexData)); - } - - /** - * Check value. - * - * @param mixed $value - * @param Attribute $attribute - * @param string $storeId - * @return array|mixed|null|string - */ - protected function checkValue($value, $attribute, $storeId) - { - if (in_array($attribute->getBackendType(), ['datetime', 'timestamp']) - || $attribute->getFrontendInput() === 'date') { - return $this->dateFieldType->formatDate($storeId, $value); - } elseif ($attribute->getFrontendInput() === 'multiselect') { - return str_replace(',', ' ', $value); - } else { - return $value; - } - } - - /** - * Prepare tier price data for product - * - * @param array $data - * @return array - */ - protected function getProductTierPriceData($data) - { - $result = []; - if (!empty($data)) { - $i = 0; - foreach ($data as $tierPrice) { - $result['tier_price_id_' . $i] = $tierPrice['price_id']; - $result['tier_website_id_' . $i] = $tierPrice['website_id']; - $result['tier_all_groups_' . $i] = $tierPrice['all_groups']; - $result['tier_cust_group_' . $i] = $tierPrice['cust_group'] == GroupInterface::CUST_GROUP_ALL - ? '' : $tierPrice['cust_group']; - $result['tier_price_qty_' . $i] = $tierPrice['price_qty']; - $result['tier_website_price_' . $i] = $tierPrice['website_price']; - $result['tier_price_' . $i] = $tierPrice['price']; - $i++; - } - } - - return $result; - } - - /** - * Prepare media gallery data for product - * - * @param array $media - * @param array $roles - * @return array - */ - protected function getProductMediaGalleryData($media, $roles) - { - $result = []; - - if (!empty($media['images'])) { - $i = 0; - foreach ($media['images'] as $data) { - if ($data['media_type'] === 'image') { - $result['image_file_' . $i] = $data['file']; - $result['image_position_' . $i] = $data['position']; - $result['image_disabled_' . $i] = $data['disabled']; - $result['image_label_' . $i] = $data['label']; - $result['image_title_' . $i] = $data['label']; - $result['image_base_image_' . $i] = $this->getMediaRoleImage($data['file'], $roles); - $result['image_small_image_' . $i] = $this->getMediaRoleSmallImage($data['file'], $roles); - $result['image_thumbnail_' . $i] = $this->getMediaRoleThumbnail($data['file'], $roles); - $result['image_swatch_image_' . $i] = $this->getMediaRoleSwatchImage($data['file'], $roles); - } else { - $result['video_file_' . $i] = $data['file']; - $result['video_position_' . $i] = $data['position']; - $result['video_disabled_' . $i] = $data['disabled']; - $result['video_label_' . $i] = $data['label']; - $result['video_title_' . $i] = $data['video_title']; - $result['video_base_image_' . $i] = $this->getMediaRoleImage($data['file'], $roles); - $result['video_small_image_' . $i] = $this->getMediaRoleSmallImage($data['file'], $roles); - $result['video_thumbnail_' . $i] = $this->getMediaRoleThumbnail($data['file'], $roles); - $result['video_swatch_image_' . $i] = $this->getMediaRoleSwatchImage($data['file'], $roles); - $result['video_url_' . $i] = $data['video_url']; - $result['video_description_' . $i] = $data['video_description']; - $result['video_metadata_' . $i] = $data['video_metadata']; - $result['video_provider_' . $i] = $data['video_provider']; - } - $i++; - } - } - return $result; - } - - /** - * Get media role image. - * - * @param string $file - * @param array $roles - * @return string - */ - protected function getMediaRoleImage($file, $roles) - { - return $file == $roles[self::MEDIA_ROLE_IMAGE] ? '1' : '0'; - } - - /** - * Get media role small image. - * - * @param string $file - * @param array $roles - * @return string - */ - protected function getMediaRoleSmallImage($file, $roles) - { - return $file == $roles[self::MEDIA_ROLE_SMALL_IMAGE] ? '1' : '0'; - } - - /** - * Get media role thumbnail. - * - * @param string $file - * @param array $roles - * @return string - */ - protected function getMediaRoleThumbnail($file, $roles) - { - return $file == $roles[self::MEDIA_ROLE_THUMBNAIL] ? '1' : '0'; - } - - /** - * Get media role swatch image. - * - * @param string $file - * @param array $roles - * @return string - */ - protected function getMediaRoleSwatchImage($file, $roles) - { - return $file == $roles[self::MEDIA_ROLE_SWATCH_IMAGE] ? '1' : '0'; - } - - /** - * Prepare quantity and stock status for product - * - * @param array $data - * @return array - */ - protected function getQtyAndStatus($data) - { - $result = []; - if (!is_array($data)) { - $result['is_in_stock'] = $data ? 1 : 0; - $result['qty'] = $data; - } else { - $result['is_in_stock'] = $data['is_in_stock'] ? 1 : 0; - $result['qty'] = $data['qty']; - } - return $result; - } - - /** - * Prepare price index for product - * - * @param int $productId - * @param int $storeId - * @param array $priceIndexData - * @return array - */ - protected function getProductPriceData($productId, $storeId, array $priceIndexData) - { - $result = []; - if (array_key_exists($productId, $priceIndexData)) { - $productPriceIndexData = $priceIndexData[$productId]; - foreach ($productPriceIndexData as $customerGroupId => $price) { - $fieldName = $this->fieldMapper->getFieldName( - 'price', - ['customerGroupId' => $customerGroupId, 'websiteId' => $storeId] - ); - $result[$fieldName] = sprintf('%F', $price); - } - } - return $result; - } - - /** - * Prepare category index data for product - * - * @param int $productId - * @param array $categoryIndexData - * @return array - */ - protected function getProductCategoryData($productId, array $categoryIndexData) - { - $result = []; - $categoryIds = []; - - if (array_key_exists($productId, $categoryIndexData)) { - $indexData = $categoryIndexData[$productId]; - $result = $indexData; - } - - if (array_key_exists($productId, $categoryIndexData)) { - $indexData = $categoryIndexData[$productId]; - foreach ($indexData as $categoryData) { - $categoryIds[] = (int)$categoryData['id']; - } - if (count($categoryIds)) { - $result = ['category_ids' => implode(' ', $categoryIds)]; - $positionAttribute = $this->attributeAdapterProvider->getByAttributeCode('position'); - $categoryNameAttribute = $this->attributeAdapterProvider->getByAttributeCode('category_name'); - foreach ($indexData as $data) { - $categoryPositionKey = $this->fieldNameResolver->getFieldName( - $positionAttribute, - ['categoryId' => $data['id']] - ); - $categoryNameKey = $this->fieldNameResolver->getFieldName( - $categoryNameAttribute, - ['categoryId' => $data['id']] - ); - $result[$categoryPositionKey] = $data['position']; - $result[$categoryNameKey] = $data['name']; - } - } - } - return $result; - } -} diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapperProxy.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapperProxy.php deleted file mode 100644 index 0fc6100979f21..0000000000000 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/DataMapper/ProductDataMapperProxy.php +++ /dev/null @@ -1,54 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper; - -use Magento\AdvancedSearch\Model\Client\ClientResolver; -use Magento\Elasticsearch\Model\Adapter\DataMapperInterface; - -/** - * Proxy for product data mappers - */ -class ProductDataMapperProxy implements DataMapperInterface -{ - /** - * @var ClientResolver - */ - private $clientResolver; - - /** - * @var DataMapperInterface[] - */ - private $dataMappers; - - /** - * CategoryFieldsProviderProxy constructor. - * @param ClientResolver $clientResolver - * @param DataMapperInterface[] $dataMappers - */ - public function __construct( - ClientResolver $clientResolver, - array $dataMappers - ) { - $this->clientResolver = $clientResolver; - $this->dataMappers = $dataMappers; - } - - /** - * @return DataMapperInterface - */ - private function getDataMapper() - { - return $this->dataMappers[$this->clientResolver->getCurrentEngine()]; - } - - /** - * @inheritdoc - */ - public function map($entityId, array $entityIndexData, $storeId, $context = []) - { - return $this->getDataMapper()->map($entityId, $entityIndexData, $storeId, $context); - } -} diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/DataMapper/DataMapperResolver.php b/app/code/Magento/Elasticsearch/Model/Adapter/DataMapper/DataMapperResolver.php deleted file mode 100644 index dbb37f3363b3d..0000000000000 --- a/app/code/Magento/Elasticsearch/Model/Adapter/DataMapper/DataMapperResolver.php +++ /dev/null @@ -1,92 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Model\Adapter\DataMapper; - -use Magento\Framework\ObjectManagerInterface; -use Magento\Elasticsearch\Model\Adapter\DataMapperInterface; -use Magento\Elasticsearch\Model\Config; - -/** - * @deprecated 100.2.0 - * @see \Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface - */ -class DataMapperResolver implements DataMapperInterface -{ - /** - * Object Manager instance - * - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @var string[] - */ - private $dataMappers; - - /** - * Data Mapper instance - * - * @var DataMapperInterface - */ - private $dataMapperEntity; - - /** - * @param ObjectManagerInterface $objectManager - * @param string[] $dataMappers - */ - public function __construct( - ObjectManagerInterface $objectManager, - array $dataMappers = [] - ) { - $this->objectManager = $objectManager; - $this->dataMappers = $dataMappers; - } - - /** - * {@inheritdoc} - */ - public function map( - $entityId, - array $entityIndexData, - $storeId, - $context = [] - ) { - $entityType = isset($context['entityType']) ? $context['entityType'] : Config::ELASTICSEARCH_TYPE_DEFAULT; - return $this->getEntity($entityType)->map($entityId, $entityIndexData, $storeId, $context); - } - - /** - * Get instance of current data mapper - * - * @param string $entityType - * @return DataMapperInterface - * @throws \Exception - */ - private function getEntity($entityType = '') - { - if (empty($this->dataMapperEntity)) { - if (empty($entityType)) { - throw new \Exception( - 'No entity type given' - ); - } - if (!isset($this->dataMappers[$entityType])) { - throw new \LogicException( - 'There is no such data mapper: ' . $entityType - ); - } - $dataMapperClass = $this->dataMappers[$entityType]; - $this->dataMapperEntity = $this->objectManager->create($dataMapperClass); - if (!($this->dataMapperEntity instanceof DataMapperInterface)) { - throw new \InvalidArgumentException( - 'Data mapper must implement \Magento\Elasticsearch\Model\Adapter\DataMapperInterface' - ); - } - } - return $this->dataMapperEntity; - } -} diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php index 0640b61f9551e..5ab6669a34cc4 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/Elasticsearch.php @@ -29,12 +29,6 @@ class Elasticsearch /**#@-*/ protected $connectionManager; - /** - * @var DataMapperInterface - * @deprecated 100.2.0 Will be replaced with BatchDataMapperInterface - */ - protected $documentDataMapper; - /** * @var \Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver */ @@ -76,10 +70,7 @@ class Elasticsearch private $batchDocumentDataMapper; /** - * Elasticsearch constructor. - * * @param \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager - * @param DataMapperInterface $documentDataMapper * @param FieldMapperInterface $fieldMapper * @param \Magento\Elasticsearch\Model\Config $clientConfig * @param Index\BuilderInterface $indexBuilder @@ -91,7 +82,6 @@ class Elasticsearch */ public function __construct( \Magento\Elasticsearch\SearchAdapter\ConnectionManager $connectionManager, - DataMapperInterface $documentDataMapper, FieldMapperInterface $fieldMapper, \Magento\Elasticsearch\Model\Config $clientConfig, \Magento\Elasticsearch\Model\Adapter\Index\BuilderInterface $indexBuilder, @@ -101,7 +91,6 @@ public function __construct( $options = [] ) { $this->connectionManager = $connectionManager; - $this->documentDataMapper = $documentDataMapper; $this->fieldMapper = $fieldMapper; $this->clientConfig = $clientConfig; $this->indexBuilder = $indexBuilder; diff --git a/app/code/Magento/Elasticsearch/Model/Config.php b/app/code/Magento/Elasticsearch/Model/Config.php index 962c19595c4c0..3766d825aefb5 100644 --- a/app/code/Magento/Elasticsearch/Model/Config.php +++ b/app/code/Magento/Elasticsearch/Model/Config.php @@ -67,7 +67,6 @@ class Config implements ClientOptionsInterface private $engineList; /** - * Config constructor. * @param ScopeConfigInterface $scopeConfig * @param ClientResolver $clientResolver * @param EngineResolverInterface $engineResolver diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php new file mode 100644 index 0000000000000..69442631a87bf --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Client/ElasticsearchTest.php @@ -0,0 +1,601 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\Test\Unit\Elasticsearch5\Model\Client; + +use Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Test elasticsearch client methods. + */ +class ElasticsearchTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch + */ + protected $model; + + /** + * @var \Elasticsearch\Client|\PHPUnit_Framework_MockObject_MockObject + */ + protected $elasticsearchClientMock; + + /** + * @var \Elasticsearch\Namespaces\IndicesNamespace|\PHPUnit_Framework_MockObject_MockObject + */ + protected $indicesMock; + + /** + * @var ObjectManagerHelper + */ + protected $objectManager; + + /** + * Setup + * + * @return void + */ + protected function setUp(): void + { + $this->elasticsearchClientMock = $this->getMockBuilder(\Elasticsearch\Client::class) + ->setMethods( + [ + 'indices', + 'ping', + 'bulk', + 'search', + 'scroll', + 'suggest', + 'info', + ] + ) + ->disableOriginalConstructor() + ->getMock(); + $this->indicesMock = $this->getMockBuilder(\Elasticsearch\Namespaces\IndicesNamespace::class) + ->setMethods( + [ + 'exists', + 'getSettings', + 'create', + 'delete', + 'putMapping', + 'deleteMapping', + 'stats', + 'updateAliases', + 'existsAlias', + 'getAlias', + ] + ) + ->disableOriginalConstructor() + ->getMock(); + $this->elasticsearchClientMock->expects($this->any()) + ->method('indices') + ->willReturn($this->indicesMock); + $this->elasticsearchClientMock->expects($this->any()) + ->method('ping') + ->willReturn(true); + $this->elasticsearchClientMock->expects($this->any()) + ->method('info') + ->willReturn(['version' => ['number' => '5.0.0']]); + + $this->objectManager = new ObjectManagerHelper($this); + $this->model = $this->objectManager->getObject( + \Magento\Elasticsearch\Elasticsearch5\Model\Client\Elasticsearch::class, + [ + 'options' => $this->getOptions(), + 'elasticsearchClient' => $this->elasticsearchClientMock + ] + ); + } + + /** + * Test ping functionality + */ + public function testPing() + { + $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(true); + $this->assertEquals(true, $this->model->ping()); + } + + /** + * Test validation of connection parameters + */ + public function testTestConnection() + { + $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(true); + $this->assertEquals(true, $this->model->testConnection()); + } + + /** + * Test validation of connection parameters returns false + */ + public function testTestConnectionFalse() + { + $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(false); + $this->assertEquals(true, $this->model->testConnection()); + } + + /** + * Test bulkQuery() method + */ + public function testBulkQuery() + { + $this->elasticsearchClientMock->expects($this->once()) + ->method('bulk') + ->with([]); + $this->model->bulkQuery([]); + } + + /** + * Test createIndex() method, case when such index exists + */ + public function testCreateIndexExists() + { + $this->indicesMock->expects($this->once()) + ->method('create') + ->with( + [ + 'index' => 'indexName', + 'body' => [], + ] + ); + $this->model->createIndex('indexName', []); + } + + /** + * Test deleteIndex() method. + */ + public function testDeleteIndex() + { + $this->indicesMock->expects($this->once()) + ->method('delete') + ->with(['index' => 'indexName']); + $this->model->deleteIndex('indexName'); + } + + /** + * Test isEmptyIndex() method. + */ + public function testIsEmptyIndex() + { + $indexName = 'magento2_index'; + $stats['indices'][$indexName]['primaries']['docs']['count'] = 0; + + $this->indicesMock->expects($this->once()) + ->method('stats') + ->with(['index' => $indexName, 'metric' => 'docs']) + ->willReturn($stats); + $this->assertTrue($this->model->isEmptyIndex($indexName)); + } + + /** + * Test isEmptyIndex() method returns false. + */ + public function testIsEmptyIndexFalse() + { + $indexName = 'magento2_index'; + $stats['indices'][$indexName]['primaries']['docs']['count'] = 1; + + $this->indicesMock->expects($this->once()) + ->method('stats') + ->with(['index' => $indexName, 'metric' => 'docs']) + ->willReturn($stats); + $this->assertFalse($this->model->isEmptyIndex($indexName)); + } + + /** + * Test updateAlias() method with new index. + */ + public function testUpdateAlias() + { + $alias = 'alias1'; + $index = 'index1'; + + $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $index]]; + + $this->indicesMock->expects($this->once()) + ->method('updateAliases') + ->with($params); + $this->model->updateAlias($alias, $index); + } + + /** + * Test updateAlias() method with new and old index. + */ + public function testUpdateAliasRemoveOldIndex() + { + $alias = 'alias1'; + $newIndex = 'index1'; + $oldIndex = 'indexOld'; + + $params['body']['actions'][] = ['remove' => ['alias' => $alias, 'index' => $oldIndex]]; + $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]]; + + $this->indicesMock->expects($this->once()) + ->method('updateAliases') + ->with($params); + $this->model->updateAlias($alias, $newIndex, $oldIndex); + } + + /** + * Test indexExists() method, case when no such index exists + */ + public function testIndexExists() + { + $this->indicesMock->expects($this->once()) + ->method('exists') + ->with(['index' => 'indexName']) + ->willReturn(true); + $this->model->indexExists('indexName'); + } + + /** + * Tests existsAlias() method checking for alias. + */ + public function testExistsAlias() + { + $alias = 'alias1'; + $params = ['name' => $alias]; + $this->indicesMock->expects($this->once()) + ->method('existsAlias') + ->with($params) + ->willReturn(true); + $this->assertTrue($this->model->existsAlias($alias)); + } + + /** + * Tests existsAlias() method checking for alias and index. + */ + public function testExistsAliasWithIndex() + { + $alias = 'alias1'; + $index = 'index1'; + $params = ['name' => $alias, 'index' => $index]; + $this->indicesMock->expects($this->once()) + ->method('existsAlias') + ->with($params) + ->willReturn(true); + $this->assertTrue($this->model->existsAlias($alias, $index)); + } + + /** + * Test getAlias() method. + */ + public function testGetAlias() + { + $alias = 'alias1'; + $params = ['name' => $alias]; + $this->indicesMock->expects($this->once()) + ->method('getAlias') + ->with($params) + ->willReturn([]); + $this->assertEquals([], $this->model->getAlias($alias)); + } + + /** + * Test createIndexIfNotExists() method, case when operation fails + * @expectedException \Exception + */ + public function testCreateIndexFailure() + { + $this->indicesMock->expects($this->once()) + ->method('create') + ->with( + [ + 'index' => 'indexName', + 'body' => [], + ] + ) + ->willThrowException(new \Exception('Something went wrong')); + $this->model->createIndex('indexName', []); + } + + /** + * Test testAddFieldsMapping() method + */ + public function testAddFieldsMapping() + { + $this->indicesMock->expects($this->once()) + ->method('putMapping') + ->with( + [ + 'index' => 'indexName', + 'type' => 'product', + 'body' => [ + 'product' => [ + '_all' => [ + 'enabled' => true, + 'type' => 'text', + ], + 'properties' => [ + 'name' => [ + 'type' => 'text', + ], + ], + 'dynamic_templates' => [ + [ + 'price_mapping' => [ + 'match' => 'price_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'float', + 'store' => true, + ], + ], + ], + [ + 'position_mapping' => [ + 'match' => 'position_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'integer', + 'index' => true + ], + ], + ], + [ + 'string_mapping' => [ + 'match' => '*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'text', + 'index' => true, + ], + ], + ], + ], + ], + ], + ] + ); + $this->model->addFieldsMapping( + [ + 'name' => [ + 'type' => 'text', + ], + ], + 'indexName', + 'product' + ); + } + + /** + * Test testAddFieldsMapping() method + * @expectedException \Exception + */ + public function testAddFieldsMappingFailure() + { + $this->indicesMock->expects($this->once()) + ->method('putMapping') + ->with( + [ + 'index' => 'indexName', + 'type' => 'product', + 'body' => [ + 'product' => [ + '_all' => [ + 'enabled' => true, + 'type' => 'text', + ], + 'properties' => [ + 'name' => [ + 'type' => 'text', + ], + ], + 'dynamic_templates' => [ + [ + 'price_mapping' => [ + 'match' => 'price_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'float', + 'store' => true, + ], + ], + ], + [ + 'position_mapping' => [ + 'match' => 'position_*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'integer', + 'index' => true, + ], + ], + ], + [ + 'string_mapping' => [ + 'match' => '*', + 'match_mapping_type' => 'string', + 'mapping' => [ + 'type' => 'text', + 'index' => true, + ], + ], + ] + ], + ], + ], + ] + ) + ->willThrowException(new \Exception('Something went wrong')); + $this->model->addFieldsMapping( + [ + 'name' => [ + 'type' => 'text', + ], + ], + 'indexName', + 'product' + ); + } + + /** + * Test deleteMapping() method + */ + public function testDeleteMapping() + { + $this->indicesMock->expects($this->once()) + ->method('deleteMapping') + ->with( + [ + 'index' => 'indexName', + 'type' => 'product', + ] + ); + $this->model->deleteMapping( + 'indexName', + 'product' + ); + } + + /** + * Ensure that configuration returns correct url. + * + * @param array $options + * @param string $expectedResult + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \ReflectionException + * @dataProvider getOptionsDataProvider + */ + public function testBuildConfig(array $options, $expectedResult): void + { + $buildConfig = new Elasticsearch($options); + $config = $this->getPrivateMethod(Elasticsearch::class, 'buildConfig'); + $result = $config->invoke($buildConfig, $options); + $this->assertEquals($expectedResult, $result['hosts'][0]); + } + + /** + * Return private method for elastic search class. + * + * @param $className + * @param $methodName + * @return \ReflectionMethod + * @throws \ReflectionException + */ + private function getPrivateMethod($className, $methodName) + { + $reflector = new \ReflectionClass($className); + $method = $reflector->getMethod($methodName); + $method->setAccessible(true); + + return $method; + } + + /** + * Test deleteMapping() method + * @expectedException \Exception + */ + public function testDeleteMappingFailure() + { + $this->indicesMock->expects($this->once()) + ->method('deleteMapping') + ->with( + [ + 'index' => 'indexName', + 'type' => 'product', + ] + ) + ->willThrowException(new \Exception('Something went wrong')); + $this->model->deleteMapping( + 'indexName', + 'product' + ); + } + + /** + * Test query() method + * @return void + */ + public function testQuery() + { + $query = 'test phrase query'; + $this->elasticsearchClientMock->expects($this->once()) + ->method('search') + ->with([$query]) + ->willReturn([]); + $this->assertEquals([], $this->model->query([$query])); + } + + /** + * Test suggest() method + * @return void + */ + public function testSuggest() + { + $query = 'query'; + $this->elasticsearchClientMock->expects($this->once()) + ->method('suggest') + ->willReturn([]); + $this->assertEquals([], $this->model->suggest($query)); + } + + /** + * Get options data provider. + */ + public function getOptionsDataProvider() + { + return [ + [ + 'without_protocol' => [ + 'hostname' => 'localhost', + 'port' => '9200', + 'timeout' => 15, + 'index' => 'magento2', + 'enableAuth' => 0, + ], + 'expected_result' => 'http://localhost:9200' + ], + [ + 'with_protocol' => [ + 'hostname' => 'https://localhost', + 'port' => '9200', + 'timeout' => 15, + 'index' => 'magento2', + 'enableAuth' => 0, + ], + 'expected_result' => 'https://localhost:9200' + ] + ]; + } + + /** + * Get elasticsearch client options + * + * @return array + */ + protected function getOptions() + { + return [ + 'hostname' => 'localhost', + 'port' => '9200', + 'timeout' => 15, + 'index' => 'magento2', + 'enableAuth' => 1, + 'username' => 'user', + 'password' => 'passwd', + ]; + } + + /** + * @return array + */ + protected function getEmptyIndexOption() + { + return [ + 'hostname' => 'localhost', + 'port' => '9200', + 'index' => '', + 'timeout' => 15, + 'enableAuth' => 1, + 'username' => 'user', + 'password' => 'passwd', + ]; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Container/AttributeTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Container/AttributeTest.php deleted file mode 100644 index ca5d570d735f5..0000000000000 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Container/AttributeTest.php +++ /dev/null @@ -1,237 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Test\Unit\Model\Adapter\Container; - -use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Unit test for Magento\Elasticsearch\Model\Adapter\Container\Attribute - */ -class AttributeTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Elasticsearch\Model\Adapter\Container\Attribute - */ - private $attribute; - - /** - * @var Collection|\PHPUnit_Framework_MockObject_MockObject - */ - private $collectionMock; - - /** - * {@inheritdoc} - */ - protected function setUp() - { - $this->collectionMock = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); - - $objectManager = new ObjectManagerHelper($this); - $this->attribute = $objectManager->getObject( - \Magento\Elasticsearch\Model\Adapter\Container\Attribute::class, - [ - 'attributeCollection' => $this->collectionMock, - ] - ); - } - - /** - * @return void - */ - public function testGetAttributeCodeById() - { - $attributeId = 555; - $attributeCode = 'test_attr_code1'; - $expected = 'test_attr_code1'; - $this->mockAttributeById($attributeId, $attributeCode); - $result = $this->attribute->getAttributeCodeById($attributeId); - $this->assertEquals($expected, $result); - } - - /** - * @return void - */ - public function testGetOptionsAttributeCodeById() - { - $attributeId = 'options'; - $expected = 'options'; - $result = $this->attribute->getAttributeCodeById($attributeId); - $this->assertEquals($expected, $result); - } - - /** - * @return void - */ - public function testGetAttributeIdByCode() - { - $attributeId = 100; - $attributeCode = 'test_attribute_code'; - $this->mockAttributeByCode($attributeId, $attributeCode); - $result = $this->attribute->getAttributeIdByCode($attributeCode); - $this->assertEquals($attributeId, $result); - } - - /** - * Test getAttributeIdByCode() method. - */ - public function testGetOptionsAttributeIdByCode() - { - $attributeCode = 'options'; - $expected = 'options'; - $result = $this->attribute->getAttributeIdByCode($attributeCode); - $this->assertEquals($expected, $result); - } - - /** - * @return void - */ - public function testGetMultipleAttributeIdsByCode() - { - $firstAttributeId = 100; - $firstAttributeCode = 'test_attribute_code_100'; - $this->mockAttributeByCode($firstAttributeId, $firstAttributeCode, 0); - $this->assertEquals($firstAttributeId, $this->attribute->getAttributeIdByCode($firstAttributeCode)); - - $secondAttributeId = 200; - $secondAttributeCode = 'test_attribute_code_200'; - $this->mockAttributeByCode($secondAttributeId, $secondAttributeCode, 0); - $this->assertEquals($secondAttributeId, $this->attribute->getAttributeIdByCode($secondAttributeCode)); - } - - /** - * @return void - */ - public function testGetAttributeByIdTwice() - { - $attributeId = 555; - $attributeCode = 'test_attr_code2'; - $expected = 'test_attr_code2'; - $this->mockAttributeById($attributeId, $attributeCode, 0); - $this->assertEquals($expected, $this->attribute->getAttributeCodeById($attributeId)); - $this->assertEquals($expected, $this->attribute->getAttributeCodeById($attributeId)); - } - - /** - * @return void - */ - public function testGetAttributeByIdCachedInGetAttributeByCode() - { - $attributeId = 100; - $attributeCode = 'test_attribute_code'; - $this->mockAttributeByCode($attributeId, $attributeCode); - $this->assertEquals($attributeId, $this->attribute->getAttributeIdByCode($attributeCode)); - $this->assertEquals($attributeCode, $this->attribute->getAttributeCodeById($attributeId)); - } - - /** - * @return void - */ - public function testGetAttribute() - { - $attributeCode = 'attr_code_120'; - $attribute = $this->createAttributeMock(120, $attributeCode); - $attributes = [ - $attribute - ]; - $this->mockAttributes($attributes); - $this->assertEquals($attribute, $this->attribute->getAttribute($attributeCode)); - } - - /** - * @return void - */ - public function testGetUnknownAttribute() - { - $attributeCode = 'attr_code_120'; - $attributes = [ - $this->createAttributeMock(120, 'attribute_code') - ]; - $this->mockAttributes($attributes); - $this->assertEquals(null, $this->attribute->getAttribute($attributeCode)); - } - - /** - * @return void - */ - public function testGetAttributes() - { - $attributes = [ - 'attr_1_mock' => $this->createAttributeMock(1, 'attr_1_mock'), - 'attr_20_mock' => $this->createAttributeMock(20, 'attr_20_mock'), - 'attr_25_mock' => $this->createAttributeMock(25, 'attr_25_mock'), - 'attr_40_mock' => $this->createAttributeMock(40, 'attr_40_mock'), - 'attr_73_mock' => $this->createAttributeMock(73, 'attr_73_mock'), - 'attr_52_mock' => $this->createAttributeMock(52, 'attr_52_mock'), - 'attr_97_mock' => $this->createAttributeMock(97, 'attr_97_mock'), - ]; - $this->mockAttributes($attributes); - $this->assertEquals($attributes, $this->attribute->getAttributes()); - } - - /** - * @param array $attributes - * @return void - */ - private function mockAttributes(array $attributes) - { - $this->collectionMock->expects($this->once()) - ->method('getIterator') - ->willReturn(new \ArrayIterator($attributes)); - } - - /** - * @param int $attributeId - * @param string $attributeCode - * @param int $sequence - * @return \PHPUnit_Framework_MockObject_MockObject - */ - private function mockAttributeById($attributeId, $attributeCode, $sequence = 0) - { - $attribute = $this->createAttributeMock($attributeId, $attributeCode); - $this->collectionMock->expects($this->at($sequence)) - ->method('getItemById') - ->with($attributeId) - ->willReturn($attribute); - return $attribute; - } - - /** - * @param int $attributeId - * @param string $attributeCode - * @param int $sequence - * @return \PHPUnit_Framework_MockObject_MockObject - */ - private function mockAttributeByCode($attributeId, $attributeCode, $sequence = 0) - { - $attribute = $this->createAttributeMock($attributeId, $attributeCode); - $this->collectionMock->expects($this->at($sequence)) - ->method('getItemByColumnValue') - ->with('attribute_code', $attributeCode) - ->willReturn($attribute); - return $attribute; - } - - /** - * @param int $attributeId - * @param string $attributeCode - * @return \PHPUnit_Framework_MockObject_MockObject - */ - private function createAttributeMock($attributeId, $attributeCode) - { - $attribute = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) - ->setMethods(['getAttributeCode', 'getId']) - ->disableOriginalConstructor() - ->getMock(); - $attribute->method('getAttributeCode') - ->willReturn($attributeCode); - $attribute->method('getId') - ->willReturn($attributeId); - return $attribute; - } -} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/ElasticsearchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/ElasticsearchTest.php index 153deb8bff845..e549f254cb235 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/ElasticsearchTest.php @@ -73,11 +73,6 @@ class ElasticsearchTest extends \PHPUnit\Framework\TestCase */ protected $indexNameResolver; - /** - * @var \Magento\Elasticsearch\Model\Adapter\DataMapperInterface|PHPUnit_Framework_MockObject_MockObject - */ - private $documentDataMapper; - /** * Setup * @@ -91,9 +86,6 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['getConnection']) ->getMock(); - $this->documentDataMapper = $this->getMockBuilder( - \Magento\Elasticsearch\Model\Adapter\DataMapperInterface::class - )->disableOriginalConstructor()->getMock(); $this->fieldMapper = $this->getMockBuilder(\Magento\Elasticsearch\Model\Adapter\FieldMapperInterface::class) ->disableOriginalConstructor() ->getMock(); diff --git a/app/code/Magento/Elasticsearch/etc/config.xml b/app/code/Magento/Elasticsearch/etc/config.xml index 1d55730fce378..93778cc81c6f4 100644 --- a/app/code/Magento/Elasticsearch/etc/config.xml +++ b/app/code/Magento/Elasticsearch/etc/config.xml @@ -9,13 +9,6 @@ <default> <catalog> <search> -<!-- <elasticsearch_server_hostname>localhost</elasticsearch_server_hostname>--> -<!-- <elasticsearch_server_port>9200</elasticsearch_server_port>--> -<!-- <elasticsearch_index_prefix>magento2</elasticsearch_index_prefix>--> -<!-- <elasticsearch_enable_auth>0</elasticsearch_enable_auth>--> -<!-- <elasticsearch_server_timeout>15</elasticsearch_server_timeout>--> -<!-- <elasticsearch_minimum_should_match></elasticsearch_minimum_should_match>--> - <elasticsearch5_server_hostname>localhost</elasticsearch5_server_hostname> <elasticsearch5_server_port>9200</elasticsearch5_server_port> <elasticsearch5_index_prefix>magento2</elasticsearch5_index_prefix> diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index 5ce0c55fb454a..7983ec3f5ceed 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -136,7 +136,6 @@ </argument> </arguments> </type> - <preference for="Magento\Elasticsearch\Model\Adapter\DataMapperInterface" type="Magento\Elasticsearch\Model\Adapter\DataMapper\DataMapperResolver" /> <virtualType name="additionalFieldsProviderForElasticsearch" type="Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProvider"> <arguments> <argument name="fieldsProviders" xsi:type="array"> @@ -174,13 +173,6 @@ </argument> </arguments> </type> - <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy"> - <arguments> - <argument name="dataMappers" xsi:type="array"> - <item name="elasticsearch5" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper</item> - </argument> - </arguments> - </type> <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> <arguments> <argument name="productFieldMappers" xsi:type="array"> @@ -364,12 +356,6 @@ <argument name="fieldNameResolver" xsi:type="object">elasticsearch5FieldNameResolver</argument> </arguments> </type> - <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper"> - <arguments> - <argument name="fieldNameResolver" xsi:type="object">elasticsearch5FieldNameResolver</argument> - </arguments> - </type> -<!-- todo check if we need this configuration--> <type name="Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> <arguments> <argument name="items" xsi:type="array"> diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml index 19d3b709cb2ea..a1ba8549a47e5 100644 --- a/app/code/Magento/Elasticsearch6/etc/di.xml +++ b/app/code/Magento/Elasticsearch6/etc/di.xml @@ -29,23 +29,6 @@ </argument> </arguments> </type> - - <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy"> - <arguments> - <argument name="dataMappers" xsi:type="array"> - <item name="elasticsearch6" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper</item> - </argument> - </arguments> - </type> - - <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> - <arguments> - <argument name="productFieldMappers" xsi:type="array"> - <item name="elasticsearch6" xsi:type="object">Magento\Elasticsearch6\Model\Adapter\FieldMapper\ProductFieldMapper</item> - </argument> - </arguments> - </type> - <type name="Magento\AdvancedSearch\Model\Client\ClientResolver"> <arguments> <argument name="clientFactories" xsi:type="array"> diff --git a/app/code/Magento/Elasticsearch7/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/Elasticsearch7/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php index 8387bcf0296ff..2fe5bc3f4a597 100644 --- a/app/code/Magento/Elasticsearch7/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php +++ b/app/code/Magento/Elasticsearch7/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php @@ -9,8 +9,6 @@ use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver - as DefaultFiledNameResolver; /** * Default name resolver for Elasticsearch 7 @@ -18,15 +16,15 @@ class DefaultResolver implements ResolverInterface { /** - * @var DefaultFiledNameResolver + * @var ResolverInterface */ private $baseResolver; /** * DefaultResolver constructor. - * @param DefaultFiledNameResolver $baseResolver + * @param ResolverInterface $baseResolver */ - public function __construct(DefaultFiledNameResolver $baseResolver) + public function __construct(ResolverInterface $baseResolver) { $this->baseResolver = $baseResolver; } diff --git a/app/code/Magento/Elasticsearch7/etc/di.xml b/app/code/Magento/Elasticsearch7/etc/di.xml index a35fef8c66f0d..4f8129f8209f4 100644 --- a/app/code/Magento/Elasticsearch7/etc/di.xml +++ b/app/code/Magento/Elasticsearch7/etc/di.xml @@ -29,15 +29,6 @@ </argument> </arguments> </type> - - <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy"> - <arguments> - <argument name="dataMappers" xsi:type="array"> - <item name="elasticsearch7" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper</item> - </argument> - </arguments> - </type> - <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> <arguments> <argument name="productFieldMappers" xsi:type="array"> @@ -139,7 +130,6 @@ </argument> </arguments> </type> -<!--todo rename!!!--> <virtualType name="\Magento\Elasticsearch7\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver" type="\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> <arguments> <argument name="items" xsi:type="array"> @@ -152,7 +142,11 @@ </argument> </arguments> </virtualType> - + <type name="Magento\Elasticsearch7\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver"> + <arguments> + <argument name="baseResolver" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver</argument> + </arguments> + </type> <virtualType name="Magento\Elasticsearch7\Model\Adapter\FieldMapper\ProductFieldMapper" type="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapper"> <arguments> diff --git a/app/code/Magento/Elasticsearch7/etc/module.xml b/app/code/Magento/Elasticsearch7/etc/module.xml index c5ad0d70cd7d1..836068b59ed1e 100644 --- a/app/code/Magento/Elasticsearch7/etc/module.xml +++ b/app/code/Magento/Elasticsearch7/etc/module.xml @@ -8,9 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_Elasticsearch7"> <sequence> - <module name="Magento_CatalogSearch"/> - <module name="Magento_Search"/> - <module name="Magento_AdvancedSearch"/> <module name="Magento_Elasticsearch"/> </sequence> </module> diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php index fc24658bb01fa..2c16f5b0b30ff 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Advanced/ResultTest.php @@ -13,7 +13,7 @@ use Zend\Stdlib\Parameters; /** - * Test cases for catalog advanced search using mysql search engine. + * Test cases for catalog advanced search using search engine. * * @magentoDbIsolation disabled * @magentoAppIsolation enabled @@ -37,7 +37,6 @@ protected function setUp() /** * Advanced search test by difference product attributes. * - * @magentoConfigFixture default/catalog/search/engine mysql * @magentoAppArea frontend * @magentoDataFixture Magento/CatalogSearch/_files/product_for_search.php * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Result/IndexTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Result/IndexTest.php index 0068d6cbaa015..279d718131dcf 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Result/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Controller/Result/IndexTest.php @@ -10,7 +10,7 @@ use Magento\TestFramework\TestCase\AbstractController; /** - * Test cases for catalog quick search using mysql search engine. + * Test cases for catalog quick search using search engine. * * @magentoDbIsolation disabled * @magentoAppIsolation enabled diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProviderTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProviderTest.php index c090f4ea0183c..8f3f9b3b19c0f 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProviderTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProviderTest.php @@ -17,7 +17,7 @@ use PHPUnit\Framework\TestCase; /** - * Search products by attribute value using mysql search engine. + * Search products by attribute value search engine. */ class DataProviderTest extends TestCase { @@ -65,7 +65,6 @@ protected function setUp() /** * Search product by custom attribute value. * - * @magentoConfigFixture default/catalog/search/engine mysql * @magentoDataFixture Magento/CatalogSearch/_files/product_for_search.php * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php * @magentoDbIsolation disabled diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/QuickSearchTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/QuickSearchTest.php index 6884be3b04d14..701fb2c17d966 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/QuickSearchTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/QuickSearchTest.php @@ -16,7 +16,7 @@ use PHPUnit\Framework\TestCase; /** - * Test cases related to find configurable product via quick search using mysql search engine. + * Test cases related to find configurable product via quick search using search engine. * * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php @@ -68,7 +68,6 @@ public function testChildProductsHasNotFoundedByQuery(): void * Assert that child product of configurable will be available by search after * set to product visibility by catalog and search using mysql search engine. * - * @magentoConfigFixture default/catalog/search/engine mysql * @dataProvider productAvailabilityInSearchByVisibilityDataProvider * * @param int $visibility @@ -113,7 +112,6 @@ public function productAvailabilityInSearchByVisibilityDataProvider(): array /** * Assert that configurable product was found by option value using mysql search engine. * - * @magentoConfigFixture default/catalog/search/engine mysql * * @return void */ diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/AdapterTest.php index fd0ce8e684665..d3fa3569ac004 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/AdapterTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/AdapterTest.php @@ -20,7 +20,7 @@ class AdapterTest extends \PHPUnit\Framework\TestCase private $adapter; /** - * @var \Magento\Elasticsearch6\Model\Client\Elasticsearch|\PHPUnit\Framework\MockObject\MockObject + * @var \Magento\AdvancedSearch\Model\Client\ClientInterface|\PHPUnit\Framework\MockObject\MockObject */ private $clientMock; @@ -43,7 +43,8 @@ protected function setUp() $contentManager = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\ConnectionManager::class) ->disableOriginalConstructor() ->getMock(); - $this->clientMock = $this->getMockBuilder(\Magento\Elasticsearch6\Model\Client\Elasticsearch::class) + $this->clientMock = $this->getMockBuilder(\Magento\AdvancedSearch\Model\Client\ClientInterface::class) + ->setMethods(['query', 'testConnection']) ->disableOriginalConstructor() ->getMock(); $contentManager diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index 6954983d049a5..03884669c73b9 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -4249,5 +4249,12 @@ ['Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper'], ['Magento\Elasticsearch\Model\Client\Elasticsearch'], ['Magento\Elasticsearch\SearchAdapter\Aggregation\Interval'], - + ['Magento\Elasticsearch\SearchAdapter\Mapper'], + ['Magento\Elasticsearch6\Model\DataProvider\Suggestions'], + ['Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldType'], + ['Magento\Elasticsearch\Model\Adapter\DataMapperInterface'], + ['Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy'], + ['Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper'], + ['Magento\Elasticsearch\Model\Adapter\DataMapper\DataMapperResolver'], + ['Magento\Elasticsearch\Model\Adapter\Container\Attribute'] ]; diff --git a/lib/internal/Magento/Framework/ObjectManager/etc/config.xsd b/lib/internal/Magento/Framework/ObjectManager/etc/config.xsd index c41e0afbd8054..6ca93d3ab78c3 100644 --- a/lib/internal/Magento/Framework/ObjectManager/etc/config.xsd +++ b/lib/internal/Magento/Framework/ObjectManager/etc/config.xsd @@ -14,6 +14,7 @@ <xs:complexContent> <xs:extension base="argumentType"> <xs:attribute name="translatable" use="optional" type="xs:boolean" /> + <xs:attribute name="sortOrder" use="optional" type="xs:integer"/> </xs:extension> </xs:complexContent> </xs:complexType> From 0def9f41afe8983ed029168d6db8fa9f14a8f9fb Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Thu, 12 Mar 2020 16:19:22 -0500 Subject: [PATCH 284/369] MC-31986: Add support for ES 7 to 2.4-develop --- .../Model/Adapter/FieldMapper/ProductFieldMapper.php | 1 + app/code/Magento/Elasticsearch6/etc/di.xml | 8 +++++++- .../Magento/Test/Legacy/_files/obsolete_classes.php | 1 - 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php index a3236b5492106..9a556460426f6 100644 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php +++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/ProductFieldMapper.php @@ -11,6 +11,7 @@ use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; + /** * Class ProductFieldMapper provides field name by attribute code and retrieve all attribute types */ diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml index a1ba8549a47e5..641fbd069b627 100644 --- a/app/code/Magento/Elasticsearch6/etc/di.xml +++ b/app/code/Magento/Elasticsearch6/etc/di.xml @@ -133,7 +133,13 @@ <argument name="fieldProvider" xsi:type="object">elasticsearch5FieldProvider</argument> </arguments> </virtualType> - + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> + <arguments> + <argument name="productFieldMappers" xsi:type="array"> + <item name="elasticsearch6" xsi:type="object">Magento\Elasticsearch6\Model\Adapter\FieldMapper\ProductFieldMapper</item> + </argument> + </arguments> + </type> <virtualType name="elasticsearch6FieldNameResolver" type="\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> <arguments> <argument name="items" xsi:type="array"> diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index 03884669c73b9..885a84e790b70 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -4250,7 +4250,6 @@ ['Magento\Elasticsearch\Model\Client\Elasticsearch'], ['Magento\Elasticsearch\SearchAdapter\Aggregation\Interval'], ['Magento\Elasticsearch\SearchAdapter\Mapper'], - ['Magento\Elasticsearch6\Model\DataProvider\Suggestions'], ['Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldType'], ['Magento\Elasticsearch\Model\Adapter\DataMapperInterface'], ['Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy'], From d208aef2207abd06d9d7ef869397e48987c400ce Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Fri, 13 Mar 2020 01:56:48 +0100 Subject: [PATCH 285/369] Proper test isolation, consistent naming in backwards-compatible manner --- ...refrontResetCustomerPasswordFailedTest.xml | 7 ++++++ ...frontResetCustomerPasswordSuccessTest.xml} | 23 ++++++++++--------- .../Test/Mftf/Test/_Deprecated_Test.xml | 11 +++++++++ 3 files changed, 30 insertions(+), 11 deletions(-) rename app/code/Magento/Customer/Test/Mftf/Test/{StorefrontForgotPasswordTest.xml => StorefrontResetCustomerPasswordSuccessTest.xml} (54%) create mode 100644 app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml index 5d0eec935e192..a1fa866a0675f 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml @@ -21,9 +21,16 @@ <group value="mtf_migrated"/> </annotations> <before> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaDisableConfigData.path}} {{StorefrontCustomerCaptchaDisableConfigData.value}}" stepKey="disableCaptcha"/> + <magentoCLI command="config:set customer/password/password_reset_protection_type 3" stepKey="setProtectionOnEmail"/> + <magentoCLI command="config:set customer/password/min_time_between_password_reset_requests 30" stepKey="increaseThresholdBetweenRequests"/> <createData stepKey="customer" entity="Simple_US_Customer"/> </before> <after> + <!-- Preferred `Use system value` which is not available from CLI --> + <magentoCLI command="config:set customer/password/password_reset_protection_type 1" stepKey="setDefaultProtection"/> + <magentoCLI command="config:set customer/password/min_time_between_password_reset_requests 30" stepKey="setDefaultThresholdBetweenRequests"/> + <magentoCLI command="config:set {{StorefrontCustomerCaptchaEnableConfigData.path}} {{StorefrontCustomerCaptchaEnableConfigData.value}}" stepKey="enableCaptcha"/> <deleteData stepKey="deleteCustomer" createDataKey="customer" /> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordSuccessTest.xml similarity index 54% rename from app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml rename to app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordSuccessTest.xml index 0c6c7dd9b800c..7ea49f3684afc 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordSuccessTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCustomerForgotPasswordTest"> + <test name="StorefrontResetCustomerPasswordSuccessTest"> <annotations> <features value="Customer"/> <stories value="Customer Login"/> @@ -21,23 +21,24 @@ </annotations> <before> <magentoCLI command="config:set {{StorefrontCustomerCaptchaDisableConfigData.path}} {{StorefrontCustomerCaptchaDisableConfigData.value}}" stepKey="disableCaptcha"/> + <magentoCLI command="config:set customer/password/password_reset_protection_type 3" stepKey="setProtectionOnEmail"/> <magentoCLI command="config:set customer/password/max_number_password_reset_requests 30" stepKey="increaseLimit"/> - <magentoCLI command="config:set customer/password/min_time_between_password_reset_requests 1" stepKey="reduceTimeout"/> + <magentoCLI command="config:set customer/password/min_time_between_password_reset_requests 0" stepKey="reduceTimeout"/> <createData stepKey="customer" entity="Simple_US_Customer"/> </before> <after> + <!-- Preferred `Use system value` which is not available from CLI --> <magentoCLI command="config:set {{StorefrontCustomerCaptchaEnableConfigData.path}} {{StorefrontCustomerCaptchaEnableConfigData.value}}" stepKey="enableCaptcha"/> + <magentoCLI command="config:set customer/password/password_reset_protection_type 1" stepKey="setDefaultProtection"/> <deleteData stepKey="deleteCustomer" createDataKey="customer" /> </after> - <amOnPage stepKey="amOnSignInPage" url="{{StorefrontCustomerSignInPage.url}}"/> - <fillField stepKey="fillEmail" userInput="$$customer.email$$" selector="{{StorefrontCustomerSignInFormSection.emailField}}"/> - <fillField stepKey="fillPassword" userInput="something" selector="{{StorefrontCustomerSignInFormSection.passwordField}}"/> - <click stepKey="clickForgotPasswordLink" selector="{{StorefrontCustomerSignInFormSection.forgotPasswordLink}}"/> - <see stepKey="seePageTitle" userInput="Forgot Your Password" selector="{{StorefrontForgotPasswordSection.pageTitle}}"/> - <fillField stepKey="enterEmail" userInput="$$customer.email$$" selector="{{StorefrontForgotPasswordSection.email}}"/> - <click stepKey="clickResetPassword" selector="{{StorefrontForgotPasswordSection.resetMyPasswordButton}}"/> - <seeInCurrentUrl stepKey="seeInSignInPage" url="account/login"/> - <see stepKey="seeSuccessMessage" userInput="If there is an account associated with $$customer.email$$ you will receive an email with a link to reset your password." selector="{{StorefrontCustomerLoginMessagesSection.successMessage}}"/> + <actionGroup ref="StorefrontCustomerResetPasswordActionGroup" stepKey="resetPasswordFirstAttempt"> + <argument name="email" value="$$customer.email$$" /> + </actionGroup> + <actionGroup ref="AssertCustomerResetPasswordActionGroup" stepKey="seePageWithSuccessMessage"> + <argument name="url" value="{{StorefrontCustomerSignInPage.url}}"/> + <argument name="message" value="If there is an account associated with $$customer.email$$ you will receive an email with a link to reset your password."/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml b/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml new file mode 100644 index 0000000000000..7c3152730524f --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerForgotPasswordTest" extends="StorefrontResetCustomerPasswordSuccessTest" deprecated="Use StorefrontResetCustomerPasswordSuccessTest"/> +</tests> From d25e66736bb3f431091ef9615d99f53c291545a4 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Fri, 13 Mar 2020 02:11:49 +0100 Subject: [PATCH 286/369] Improve the Create Account tests (Success & Failure) --- .../Mftf/Test/StorefrontCreateCustomerTest.xml | 17 ++++++++++------- .../StorefrontCreateExistingCustomerTest.xml | 15 +++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml index 7899f4ac53132..0bc46e8717f33 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml @@ -12,19 +12,22 @@ <annotations> <features value="Customer"/> <stories value="Create a Customer via the Storefront"/> - <title value="Admin should be able to create a customer via the storefront"/> - <description value="Admin should be able to create a customer via the storefront"/> + <title value="As a Customer I should be able to register an account on Storefront"/> + <description value="As a Customer I should be able to register an account on Storefront"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-23546"/> <group value="customer"/> <group value="create"/> </annotations> - <after> - <actionGroup ref="AdminLogoutActionGroup" stepKey="adminLogout"/> - </after> - <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="SignUpNewUser"> - <argument name="Customer" value="CustomerEntityOne"/> + <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/> + <actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="fillCreateAccountForm"> + <argument name="customer" value="Simple_US_Customer"/> + </actionGroup> + <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="submitCreateAccountForm"/> + <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="seeSuccessMessage"> + <argument name="messageType" value="success"/> + <argument name="message" value="Thank you for registering with Main Website Store."/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml index 952ac235d92a4..07ac295e5cce0 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateExistingCustomerTest.xml @@ -8,29 +8,28 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontCreateExistingCustomerTest"> + <test name="StorefrontCreateExistingCustomerTest" extends="StorefrontCreateCustomerTest"> <annotations> <features value="Customer"/> <stories value="Customer Registration"/> - <title value="Attempt to register customer on storefront with existing email"/> - <description value="Attempt to register customer on storefront with existing email"/> + <title value="As a Customer I should not be able to register an account using already registered e-mail"/> + <description value="As a Customer I should not be able to register an account using already registered e-mail"/> <testCaseId value="MC-10907"/> <severity value="MAJOR"/> <group value="customers"/> <group value="mtf_migrated"/> </annotations> <before> - <createData entity="Simple_US_Customer" stepKey="customer"/> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> </before> <after> - <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> </after> - <actionGroup ref="StorefrontOpenCustomerAccountCreatePageActionGroup" stepKey="openCreateAccountPage"/> <actionGroup ref="StorefrontFillCustomerAccountCreationFormActionGroup" stepKey="fillCreateAccountForm"> - <argument name="customer" value="$$customer$$"/> + <argument name="customer" value="$$createCustomer$$"/> </actionGroup> - <actionGroup ref="StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup" stepKey="submitCreateAccountForm"/> + <remove keyForRemoval="seeSuccessMessage"/> <actionGroup ref="AssertMessageCustomerCreateAccountActionGroup" stepKey="seeErrorMessage"> <argument name="messageType" value="error"/> <argument name="message" value="There is already an account with this email address."/> From 6fa5df862e45a70a343e446d2c5201747b1caad1 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Fri, 13 Mar 2020 02:17:13 +0100 Subject: [PATCH 287/369] Add `deprecated` notice about not using super-hero ActionGroups --- .../Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml index 56afa8854ce0d..59601a58e64c7 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="SignUpNewUserFromStorefrontActionGroup"> + <actionGroup name="SignUpNewUserFromStorefrontActionGroup" deprecated="Avoid using super-ActionGroups. See StorefrontCreateCustomerTest for replacement."> <annotations> <description>Goes to the Storefront. Clicks on 'Create Account'. Fills in the provided Customer details, excluding Newsletter Sign-Up. Clicks on 'Create Account' button. Validate that the Customer details are present and correct.</description> </annotations> From 3ed4cf7e149092f253e6ffa2a8fffffb8a2d1c30 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Fri, 13 Mar 2020 02:56:01 +0100 Subject: [PATCH 288/369] Add "Admin" prefix to Test and ActionGroup --- ...GridCustomerGroupEditByCodeActionGroup.xml | 12 ++++++++++ ...avigateToCustomerGroupPageActionGroup.xml} | 2 +- .../ActionGroup/_Deprecated_ActionGroup.xml | 1 + ...nVerifyDisabledCustomerGroupFieldTest.xml} | 22 +++++++++---------- .../Test/Mftf/Test/_Deprecated_Test.xml | 11 ++++++++++ 5 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminGridCustomerGroupEditByCodeActionGroup.xml rename app/code/Magento/Customer/Test/Mftf/ActionGroup/{NavigateCustomerGroupActionGroup.xml => AdminNavigateToCustomerGroupPageActionGroup.xml} (89%) rename app/code/Magento/Customer/Test/Mftf/Test/{VerifyDisabledCustomerGroupFieldTest.xml => AdminVerifyDisabledCustomerGroupFieldTest.xml} (56%) create mode 100644 app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminGridCustomerGroupEditByCodeActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminGridCustomerGroupEditByCodeActionGroup.xml new file mode 100644 index 0000000000000..7d1cb80682ae5 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminGridCustomerGroupEditByCodeActionGroup.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminGridCustomerGroupEditByCodeActionGroup"> + <arguments> + <argument name="customerGroupCode" type="string"/> + </arguments> + + <click selector="{{AdminCustomerGroupMainSection.editButtonByCustomerGroupCode(customerGroupCode)}}" stepKey="clickOnEditCustomerGroup" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminNavigateToCustomerGroupPageActionGroup.xml similarity index 89% rename from app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml rename to app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminNavigateToCustomerGroupPageActionGroup.xml index 78e9a2f18da95..b782436a20949 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminNavigateToCustomerGroupPageActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="NavigateToCustomerGroupPage"> + <actionGroup name="AdminNavigateToCustomerGroupPageActionGroup"> <annotations> <description>Goes to the Admin Customer Groups page.</description> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml index 5efcfc0e79b0d..46f45ce3802f1 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/_Deprecated_ActionGroup.xml @@ -12,6 +12,7 @@ NOTICE: Action Groups in this file are DEPRECATED and SHOULD NOT BE USED anymore --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="NavigateToCustomerGroupPage" extends="AdminNavigateToCustomerGroupPageActionGroup" deprecated="Use AdminNavigateToCustomerGroupPageActionGroup"/> <actionGroup name="AdminCreateCustomerWithWebSiteAndGroup" deprecated="Use `AdminCreateCustomerWithWebSiteAndGroupActionGroup` instead"> <annotations> <description>Goes to the Customer grid page. Click on 'Add New Customer'. Fills provided Customer Data. Fill provided Customer Address data. Assigns Product to Website and Store View. Clicks on Save.</description> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyDisabledCustomerGroupFieldTest.xml similarity index 56% rename from app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml rename to app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyDisabledCustomerGroupFieldTest.xml index 0f98184aafb4f..e1342f26809ee 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyDisabledCustomerGroupFieldTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="VerifyDisabledCustomerGroupFieldTest"> + <test name="AdminVerifyDisabledCustomerGroupFieldTest"> <annotations> <stories value="Check that field is disabled in system Customer Group"/> <title value="Check that field is disabled in system Customer Group"/> @@ -19,20 +19,20 @@ <group value="mtf_migrated"/> </annotations> - <!-- Steps --> - <!-- 1. Login to backend as admin user --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <waitForPageLoad stepKey="waitForAdminPageLoad" /> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <!-- 2. Navigate to Customers > Customer Groups --> - <amOnPage url="{{AdminCustomerGroupPage.url}}" stepKey="amOnCustomerGroupPage" /> - <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearFiltersIfTheySet"/> + <actionGroup ref="AdminNavigateToCustomerGroupPageActionGroup" stepKey="amOnCustomerGroupPage"/> + <actionGroup ref="AdminFilterCustomerGroupByNameActionGroup" stepKey="clearFiltersIfTheySet"> + <argument name="customerGroupName" value="{{NotLoggedInCustomerGroup.code}}"/> + </actionGroup> - <!-- 3. Select system Customer Group specified in data set from grid --> - <click selector="{{AdminCustomerGroupMainSection.editButtonByCustomerGroupCode(NotLoggedInCustomerGroup.code)}}" stepKey="clickOnEditCustomerGroup" /> + <actionGroup ref="AdminGridCustomerGroupEditByCodeActionGroup" stepKey="openCustomerGroup"> + <argument name="customerGroupCode" value="{{NotLoggedInCustomerGroup.code}}"/> + </actionGroup> - <!-- 4. Perform all assertions --> <seeInField selector="{{AdminNewCustomerGroupSection.groupName}}" userInput="{{NotLoggedInCustomerGroup.code}}" stepKey="seeNotLoggedInTextInGroupName" /> <assertElementContainsAttribute selector="{{AdminNewCustomerGroupSection.groupName}}" attribute="disabled" expectedValue="true" stepKey="checkIfGroupNameIsDisabled" /> + + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml b/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml new file mode 100644 index 0000000000000..795ce85340f41 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="VerifyDisabledCustomerGroupFieldTest" extends="AdminVerifyDisabledCustomerGroupFieldTest" deprecated="Use AdminVerifyDisabledCustomerGroupFieldTest instead"/> +</tests> From 657ff0f8b1adb7eb36b03f2fdde35b4aa163aa2a Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Fri, 13 Mar 2020 03:42:23 +0100 Subject: [PATCH 289/369] Add to cart with Expired session: - Added information about avoiding excessive Action Groups - Extracted atomic action groups with minimal features - Rewritten StorefrontAddProductToCartWithExpiredSessionTest.xml --- .../AddSimpleProductToCartActionGroup.xml | 2 +- ...roductAddToCartErrorMessageActionGroup.xml | 17 +++++++++++++ ...ductAddToCartSuccessMessageActionGroup.xml | 17 +++++++++++++ ...efrontOpenProductEntityPageActionGroup.xml | 21 ++++++++++++++++ ...tPageAddSimpleProductToCartActionGroup.xml | 15 ++++++++++++ ...StorefrontFakeBrokenSessionActionGroup.xml | 14 +++++++++++ ...ddProductToCartWithExpiredSessionTest.xml} | 24 ++++++++----------- .../Test/Mftf/Test/_Deprecated_Test.xml | 11 +++++++++ 8 files changed, 106 insertions(+), 15 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductEntityPageActionGroup.xml create mode 100644 app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductPageAddSimpleProductToCartActionGroup.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFakeBrokenSessionActionGroup.xml rename app/code/Magento/Customer/Test/Mftf/Test/{AddingProductWithExpiredSessionTest.xml => StorefrontAddProductToCartWithExpiredSessionTest.xml} (64%) create mode 100644 app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddSimpleProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddSimpleProductToCartActionGroup.xml index 81e3b8c99d9d2..68a051c232338 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddSimpleProductToCartActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddSimpleProductToCartActionGroup.xml @@ -8,7 +8,7 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AddSimpleProductToCartActionGroup"> + <actionGroup name="AddSimpleProductToCartActionGroup" deprecated="Avoid using super-ActionGroups. Use StorefrontOpenProductEntityPageActionGroup, StorefrontAddSimpleProductToCartActionGroup and StorefrontAssertProductAddedToCartResultMessageActionGroup"> <annotations> <description>Navigates to the Storefront Product page. Then adds the Product to the Cart. Validates that the Success Message is present and correct.</description> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml new file mode 100644 index 0000000000000..2147f837d0abc --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertProductAddToCartErrorMessageActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue=""/> + </arguments> + <waitForElementVisible selector="{{StorefrontMessagesSection.error}}" time="10" stepKey="waitForProductAddedMessage"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput="{{message}}" stepKey="seeAddToCartErrorMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml new file mode 100644 index 0000000000000..60c9461c53f7b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertProductAddToCartSuccessMessageActionGroup"> + <arguments> + <argument name="message" type="string" defaultValue=""/> + </arguments> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" time="30" stepKey="waitForProductAddedMessage"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput="message" stepKey="seeAddToCartSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductEntityPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductEntityPageActionGroup.xml new file mode 100644 index 0000000000000..88dcaff646799 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductEntityPageActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenProductEntityPageActionGroup"> + <annotations> + <description>Opens Storefront Product page for the provided Product Entity</description> + </annotations> + <arguments> + <argument name="product" type="entity"/> + </arguments> + + <amOnPage url="{{StorefrontProductPage.url(product.custom_attributes[url_key])}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoaded"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductPageAddSimpleProductToCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductPageAddSimpleProductToCartActionGroup.xml new file mode 100644 index 0000000000000..202f8989dce0f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductPageAddSimpleProductToCartActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontProductPageAddSimpleProductToCartActionGroup"> + <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCart"/> + <waitForElementVisible selector="{{StorefrontProductActionSection.addToCartButtonTitleIsAddToCart}}" stepKey="waitForElementVisibleAddToCartButtonTitleIsAddToCart"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFakeBrokenSessionActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFakeBrokenSessionActionGroup.xml new file mode 100644 index 0000000000000..3d989a7833b8a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFakeBrokenSessionActionGroup.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontFakeBrokenSessionActionGroup"> + <resetCookie userInput="PHPSESSID" stepKey="resetCookieForCart"/> + <resetCookie userInput="form_key" stepKey="resetCookieForCart2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartWithExpiredSessionTest.xml similarity index 64% rename from app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml rename to app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartWithExpiredSessionTest.xml index 01f35439f23b8..73d050cfa09b9 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AddingProductWithExpiredSessionTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddProductToCartWithExpiredSessionTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AddingProductWithExpiredSessionTest"> + <test name="StorefrontAddProductToCartWithExpiredSessionTest"> <annotations> <title value="Adding a product to cart from category page with an expired session"/> <description value="Adding a product to cart from category page with an expired session"/> @@ -23,24 +23,20 @@ <createData entity="_defaultProduct" stepKey="createSimpleProduct"> <requiredEntity createDataKey="createCategory"/> </createData> + <magentoCron stepKey="runCronReindex" groups="index"/> </before> - - <!--Navigate to a category page --> - <amOnPage url="$$createSimpleProduct.name$$.html" stepKey="goToProductPage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> - - <!-- Remove PHPSESSID and form_key to replicate an expired session--> - <resetCookie userInput="PHPSESSID" stepKey="resetCookieForCart"/> - <resetCookie userInput="form_key" stepKey="resetCookieForCart2"/> - - <!-- "Add to Cart" any product--> - <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCart"/> - <see stepKey="assertErrorMessage" userInput="Your session has expired"/> <after> - <!--Delete created product--> <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> </after> + <actionGroup ref="StorefrontOpenProductEntityPageActionGroup" stepKey="openProductPage"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontFakeBrokenSessionActionGroup" stepKey="fakeBrokenSession"/> + <actionGroup ref="StorefrontProductPageAddSimpleProductToCartActionGroup" stepKey="addProductToCart"/> + <actionGroup ref="StorefrontAssertProductAddToCartErrorMessageActionGroup" stepKey="assertFailure"> + <argument name="message" value="Your session has expired"/> + </actionGroup> </test> </tests> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml b/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml new file mode 100644 index 0000000000000..5f948f5b69cae --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/_Deprecated_Test.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AddingProductWithExpiredSessionTest" extends="StorefrontAddProductToCartWithExpiredSessionTest" deprecated="Use StorefrontAddProductToCartWithExpiredSessionTest"/> +</tests> From 9d78f60c856b842d30ed6e76c1f882e6cb7f7d46 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Fri, 13 Mar 2020 03:44:30 +0100 Subject: [PATCH 290/369] Add missing copyright --- .../AdminGridCustomerGroupEditByCodeActionGroup.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminGridCustomerGroupEditByCodeActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminGridCustomerGroupEditByCodeActionGroup.xml index 7d1cb80682ae5..68620931393c4 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminGridCustomerGroupEditByCodeActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminGridCustomerGroupEditByCodeActionGroup.xml @@ -1,4 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> From 2dd2121d4c5bc6b01fd1c37447515a6277e7ab15 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Fri, 13 Mar 2020 03:54:14 +0100 Subject: [PATCH 291/369] Move Assertions to the right module --- .../StorefrontAssertProductAddToCartErrorMessageActionGroup.xml | 0 .../StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename app/code/Magento/{Catalog => Checkout}/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml (100%) rename app/code/Magento/{Catalog => Checkout}/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml (100%) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml similarity index 100% rename from app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml rename to app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartErrorMessageActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml similarity index 100% rename from app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml rename to app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertProductAddToCartSuccessMessageActionGroup.xml From 158f788ec5a49557104fe1722c7e9551721b5932 Mon Sep 17 00:00:00 2001 From: AleksLi <aleksliwork@gmail.com> Date: Fri, 13 Mar 2020 08:22:22 +0100 Subject: [PATCH 292/369] MC-26683: Added test case for 'Some of the products are out of stock.' case --- .../Quote/Guest/AddSimpleProductToCartTest.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php index e5953e7b7ad72..986de59ff7bd2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php @@ -8,6 +8,7 @@ namespace Magento\GraphQl\Quote\Guest; use Exception; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException; @@ -107,22 +108,26 @@ public function testAddDisabledProductToCart(): void /** * Add out of stock product to cart * - * @magentoApiDataFixture Magento/Catalog/_files/product_virtual_out_of_stock.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php * @return void + * @throws NoSuchEntityException */ public function testAddOutOfStockProductToCart(): void { - $sku = 'virtual-product-out'; - $quantity = 2; + $sku = 'simple1'; + $quantity = 1; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); $query = $this->getQuery($maskedQuoteId, $sku, $quantity); $this->expectException(ResponseContainsErrorsException::class); $this->expectExceptionMessage( - 'Could not add the product with SKU ' . $sku . ' to the shopping cart: ' . - 'Product that you are trying to add is not available.' + 'Some of the products are out of stock.' ); $this->graphQlMutation($query); From 9948da7e013245ec3af6f6e7633ffa08a12b4ad9 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Fri, 13 Mar 2020 12:09:00 +0200 Subject: [PATCH 293/369] MC-31196: [2.4.0] Paypal issue with region on 2.3.4 --- .../Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml index 061bc795b2bff..77ae5dbf64840 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml @@ -22,6 +22,9 @@ <createData entity="FirstLevelSubCat" stepKey="createDefaultCategory"> <field key="is_active">true</field> </createData> + <!-- Perform reindex and flush cache --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> </before> <after> <deleteData createDataKey="createDefaultCategory" stepKey="deleteCategory"/> From 8673f269870df9bb0580d84bb72b235a1ffc6f91 Mon Sep 17 00:00:00 2001 From: Dmitry Tsymbal <d.tsymbal@atwix.com> Date: Thu, 12 Mar 2020 16:42:08 +0200 Subject: [PATCH 294/369] Enable Persistent Shopping Cart --- ...sistentShoppingCartSettingsActionGroup.xml | 15 ++++++++ ...hoppingCartOptionsAvailableActionGroup.xml | 18 ++++++++++ ...onfigurationPersistentShoppingCartPage.xml | 14 ++++++++ ...dminPersistentShoppingCartSettingsTest.xml | 34 +++++++++++++++++++ .../AdminPersistentShoppingCartSection.xml | 20 +++++++++++ 5 files changed, 101 insertions(+) create mode 100644 app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToPersistentShoppingCartSettingsActionGroup.xml create mode 100644 app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPersistentShoppingCartOptionsAvailableActionGroup.xml create mode 100644 app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationPersistentShoppingCartPage.xml create mode 100644 app/code/Magento/Backend/Test/Mftf/Test/AdminPersistentShoppingCartSettingsTest.xml create mode 100644 app/code/Magento/Config/Test/Mftf/Section/AdminPersistentShoppingCartSection.xml diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToPersistentShoppingCartSettingsActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToPersistentShoppingCartSettingsActionGroup.xml new file mode 100644 index 0000000000000..811187b9fe95e --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToPersistentShoppingCartSettingsActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminNavigateToPersistentShoppingCartSettingsActionGroup"> + <amOnPage url="{{AdminConfigurationPersistentShoppingCartPage.url}}" stepKey="navigateToPersistencePage"/> + <conditionalClick selector="{{AdminPersistentShoppingCartSection.DefaultLayoutsTab}}" dependentSelector="{{AdminPersistentShoppingCartSection.CheckIfTabExpand}}" visible="true" stepKey="clickTab"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPersistentShoppingCartOptionsAvailableActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPersistentShoppingCartOptionsAvailableActionGroup.xml new file mode 100644 index 0000000000000..3b75602e6d89c --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPersistentShoppingCartOptionsAvailableActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminPersistentShoppingCartOptionsAvailableActionGroup"> + <seeElement stepKey="seeLifetimeInput" selector="{{AdminPersistentShoppingCartSection.persistenceLifeTime}}"/> + <seeElement stepKey="seeRememberMeEnableInput" selector="{{AdminPersistentShoppingCartSection.rememberMeEnable}}"/> + <seeElement stepKey="seeRememberMeDefaultInput" selector="{{AdminPersistentShoppingCartSection.rememberMeDefault}}"/> + <seeElement stepKey="seeClearPersistence" selector="{{AdminPersistentShoppingCartSection.clearPersistenceOnLogout}}"/> + <seeElement stepKey="seePersistShoppingCart" selector="{{AdminPersistentShoppingCartSection.persistShoppingCart}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationPersistentShoppingCartPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationPersistentShoppingCartPage.xml new file mode 100644 index 0000000000000..f7e579c57e21c --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationPersistentShoppingCartPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminConfigurationPersistentShoppingCartPage" url="admin/system_config/edit/section/persistent/" module="Customers" area="admin"> + <section name="AdminPersistentShoppingCartSection"/> + </page> +</pages> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminPersistentShoppingCartSettingsTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminPersistentShoppingCartSettingsTest.xml new file mode 100644 index 0000000000000..cf1ed2d63034f --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminPersistentShoppingCartSettingsTest.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminPersistentShoppingCartSettingsTest"> + <annotations> + <features value="Backend"/> + <stories value="Enable Persistent Shopping cart"/> + <title value="Admin should be able to manage persistent shopping cart settings"/> + <description value="Admin should be able to enable persistent shopping cart in Magento Admin backend and see additional options"/> + <group value="backend"/> + </annotations> + + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <magentoCLI stepKey="enablePersistentShoppingCart" command="config:set persistent/options/enabled 1"/> + <magentoCLI stepKey="cacheClean" command="cache:clean config"/> + </before> + <after> + <magentoCLI stepKey="disablePersistentShoppingCart" command="config:set persistent/options/enabled 0"/> + <magentoCLI stepKey="cacheClean" command="cache:clean config"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <actionGroup ref="AdminNavigateToPersistentShoppingCartSettingsActionGroup" stepKey="navigateToPersistenceSettings"/> + <actionGroup ref="AssertAdminPersistentShoppingCartOptionsAvailableActionGroup" stepKey="assertOptions"/> + </test> +</tests> diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminPersistentShoppingCartSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminPersistentShoppingCartSection.xml new file mode 100644 index 0000000000000..7dc0d16e39556 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminPersistentShoppingCartSection.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminPersistentShoppingCartSection"> + <element name="DefaultLayoutsTab" type="button" selector=".entry-edit-head-link"/> + <element name="CheckIfTabExpand" type="button" selector=".entry-edit-head-link:not(.open)"/> + <element name="persistenceLifeTime" type="input" selector="#persistent_options_lifetime"/> + <element name="rememberMeEnable" type="input" selector="#persistent_options_remember_enabled"/> + <element name="rememberMeDefault" type="input" selector="#persistent_options_remember_default"/> + <element name="clearPersistenceOnLogout" type="input" selector="#persistent_options_logout_clear"/> + <element name="persistShoppingCart" type="input" selector="#persistent_options_shopping_cart"/> + </section> +</sections> From c0aef729db5e169ba9663901a5ff6a1bc57f82c4 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Fri, 13 Mar 2020 11:54:24 +0100 Subject: [PATCH 295/369] Annoying typo :-) --- .../Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml | 6 +++--- .../AdminCorrectnessInvoicedItemInBundleProductTest.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml index 24c60006a3504..f016af2144022 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml @@ -13,8 +13,8 @@ <annotations> <features value="ConfigurableProduct"/> <stories value="Cancel order"/> - <title value="Product qunatity return after order cancel"/> - <description value="Check Product qunatity return after order cancel"/> + <title value="Product quantity return after order cancel"/> + <description value="Check Product quantity return after order cancel"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-97228"/> <useCaseId value="MAGETWO-82221"/> @@ -75,7 +75,7 @@ <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> <waitForPageLoad stepKey="waitForNewInvoicePageLoad"/> <fillField selector="{{AdminInvoiceItemsSection.qtyToInvoiceColumn}}" userInput="1" stepKey="ChangeQtyToInvoice"/> - <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQunatity"/> + <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQuantity"/> <waitForPageLoad stepKey="waitPageToBeLoaded"/> <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml index e55cdfeb284b4..d7d5036c45c8f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml @@ -83,7 +83,7 @@ <waitForPageLoad stepKey="waitForCreatedOrderPageOpened"/> <actionGroup ref="GoToInvoiceIntoOrderActionGroup" stepKey="goToInvoiceIntoOrderPage"/> <fillField selector="{{AdminInvoiceItemsSection.qtyToInvoiceColumn}}" userInput="5" stepKey="ChangeQtyToInvoice"/> - <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQunatity"/> + <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQuantity"/> <waitForPageLoad stepKey="waitPageToBeLoaded"/> <actionGroup ref="SubmitInvoiceActionGroup" stepKey="submitInvoice"/> From 6fc1e3cf00a028c55d89b9f6ac64d3e6e5679671 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 13 Mar 2020 15:54:57 +0200 Subject: [PATCH 296/369] MC-32273: Part of URL is missed after saving category image --- .../Catalog/Model/Category/DataProvider.php | 16 +- .../Catalog/Model/Category/FileInfo.php | 11 ++ .../Magento/Catalog/Model/Category/Image.php | 75 +++++++++ .../Unit/Model/Category/DataProviderTest.php | 18 ++- .../Test/Unit/Model/Category/ImageTest.php | 146 ++++++++++++++++++ .../Catalog/ViewModel/Category/Image.php | 46 ++++++ .../Catalog/ViewModel/Category/Output.php | 46 ++++++ .../frontend/layout/catalog_category_view.xml | 7 +- .../frontend/templates/category/image.phtml | 5 +- 9 files changed, 357 insertions(+), 13 deletions(-) create mode 100644 app/code/Magento/Catalog/Model/Category/Image.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/Category/ImageTest.php create mode 100644 app/code/Magento/Catalog/ViewModel/Category/Image.php create mode 100644 app/code/Magento/Catalog/ViewModel/Category/Output.php diff --git a/app/code/Magento/Catalog/Model/Category/DataProvider.php b/app/code/Magento/Catalog/Model/Category/DataProvider.php index fe7258398d191..d8c79c485e3e5 100644 --- a/app/code/Magento/Catalog/Model/Category/DataProvider.php +++ b/app/code/Magento/Catalog/Model/Category/DataProvider.php @@ -41,6 +41,7 @@ * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @since 101.0.0 */ class DataProvider extends ModifierPoolDataProvider @@ -176,6 +177,10 @@ class DataProvider extends ModifierPoolDataProvider * @var AuthorizationInterface */ private $auth; + /** + * @var Image + */ + private $categoryImage; /** * @param string $name @@ -196,6 +201,7 @@ class DataProvider extends ModifierPoolDataProvider * @param ScopeOverriddenValue|null $scopeOverriddenValue * @param ArrayManager|null $arrayManager * @param FileInfo|null $fileInfo + * @param Image|null $categoryImage * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -216,7 +222,8 @@ public function __construct( ?ArrayUtils $arrayUtils = null, ScopeOverriddenValue $scopeOverriddenValue = null, ArrayManager $arrayManager = null, - FileInfo $fileInfo = null + FileInfo $fileInfo = null, + ?Image $categoryImage = null ) { $this->eavValidationRules = $eavValidationRules; $this->collection = $categoryCollectionFactory->create(); @@ -232,6 +239,7 @@ public function __construct( ObjectManager::getInstance()->get(ScopeOverriddenValue::class); $this->arrayManager = $arrayManager ?: ObjectManager::getInstance()->get(ArrayManager::class); $this->fileInfo = $fileInfo ?: ObjectManager::getInstance()->get(FileInfo::class); + $this->categoryImage = $categoryImage ?? ObjectManager::getInstance()->get(Image::class); parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data, $pool); } @@ -601,11 +609,7 @@ private function convertValues($category, $categoryData): array // phpcs:ignore Magento2.Functions.DiscouragedFunction $categoryData[$attributeCode][0]['name'] = basename($fileName); - if ($this->fileInfo->isBeginsWithMediaDirectoryPath($fileName)) { - $categoryData[$attributeCode][0]['url'] = $fileName; - } else { - $categoryData[$attributeCode][0]['url'] = $category->getImageUrl($attributeCode); - } + $categoryData[$attributeCode][0]['url'] = $this->categoryImage->getUrl($category, $attributeCode); $categoryData[$attributeCode][0]['size'] = isset($stat) ? $stat['size'] : 0; $categoryData[$attributeCode][0]['type'] = $mime; diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php index 76b6a2e75d0ea..7d679f2645be1 100644 --- a/app/code/Magento/Catalog/Model/Category/FileInfo.php +++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php @@ -245,4 +245,15 @@ private function getMediaDirectoryPathRelativeToBaseDirectoryPath(string $filePa return $mediaDirectoryRelativeSubpath; } + + /** + * Get file relative path to media directory + * + * @param string $filename + * @return string + */ + public function getRelativePathToMediaDirectory(string $filename): string + { + return $this->getFilePath($filename); + } } diff --git a/app/code/Magento/Catalog/Model/Category/Image.php b/app/code/Magento/Catalog/Model/Category/Image.php new file mode 100644 index 0000000000000..ea5700cb386d0 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Category/Image.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Category; + +use Magento\Catalog\Model\Category; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Category Image Service + */ +class Image +{ + private const ATTRIBUTE_NAME = 'image'; + /** + * @var FileInfo + */ + private $fileInfo; + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * Initialize dependencies. + * + * @param FileInfo $fileInfo + * @param StoreManagerInterface $storeManager + */ + public function __construct( + FileInfo $fileInfo, + StoreManagerInterface $storeManager + ) { + $this->fileInfo = $fileInfo; + $this->storeManager = $storeManager; + } + /** + * Resolve category image URL + * + * @param Category $category + * @param string $attributeCode + * @return string + * @throws LocalizedException + */ + public function getUrl(Category $category, string $attributeCode = self::ATTRIBUTE_NAME): string + { + $url = ''; + $image = $category->getData($attributeCode); + if ($image) { + if (is_string($image)) { + $store = $this->storeManager->getStore(); + $mediaBaseUrl = $store->getBaseUrl(UrlInterface::URL_TYPE_MEDIA); + if ($this->fileInfo->isBeginsWithMediaDirectoryPath($image)) { + $relativePath = $this->fileInfo->getRelativePathToMediaDirectory($image); + $url = rtrim($mediaBaseUrl, '/') . '/' . ltrim($relativePath, '/'); + } elseif (substr($image, 0, 1) !== '/') { + $url = rtrim($mediaBaseUrl, '/') . '/' . ltrim(FileInfo::ENTITY_MEDIA_PATH, '/') . '/' . $image; + } else { + $url = $image; + } + } else { + throw new LocalizedException( + __('Something went wrong while getting the image url.') + ); + } + } + return $url; + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/DataProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/DataProviderTest.php index 4ce50537f27bd..ce131a1953bfd 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/DataProviderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/DataProviderTest.php @@ -10,6 +10,7 @@ use Magento\Catalog\Model\Category\Attribute\Backend\Image; use Magento\Catalog\Model\Category\DataProvider; use Magento\Catalog\Model\Category\FileInfo; +use Magento\Catalog\Model\Category\Image as CategoryImage; use Magento\Catalog\Model\CategoryFactory; use Magento\Catalog\Model\ResourceModel\Category\Collection; use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; @@ -98,6 +99,11 @@ class DataProviderTest extends TestCase */ private $auth; + /** + * @var CategoryImage|MockObject + */ + private $categoryImage; + /** * @inheritDoc */ @@ -155,6 +161,11 @@ protected function setUp() $this->arrayUtils = $this->getMockBuilder(ArrayUtils::class) ->setMethods(['flatten']) ->disableOriginalConstructor()->getMock(); + + $this->categoryImage = $this->createPartialMock( + CategoryImage::class, + ['getUrl'] + ); } /** @@ -185,7 +196,8 @@ private function getModel() 'categoryFactory' => $this->categoryFactory, 'pool' => $this->modifierPool, 'auth' => $this->auth, - 'arrayUtils' => $this->arrayUtils + 'arrayUtils' => $this->arrayUtils, + 'categoryImage' => $this->categoryImage, ] ); @@ -324,8 +336,8 @@ public function testGetData() $categoryMock->expects($this->once()) ->method('getAttributes') ->willReturn(['image' => $attributeMock]); - $categoryMock->expects($this->once()) - ->method('getImageUrl') + $this->categoryImage->expects($this->once()) + ->method('getUrl') ->willReturn($categoryUrl); $this->registry->expects($this->once()) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/ImageTest.php new file mode 100644 index 0000000000000..7cf63a56283f2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/ImageTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Test\Unit\Model\Category; + +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Category\FileInfo; +use Magento\Catalog\Model\Category\Image; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Test category image resolver + */ +class ImageTest extends TestCase +{ + /** + * @var Store|MockObject + */ + private $store; + /** + * @var Category + */ + private $category; + /** + * @var Image + */ + private $model; + + /** + * @inheritDoc + */ + protected function setUp() + { + $storeManager = $this->createPartialMock(StoreManager::class, ['getStore']); + $this->store = $this->createPartialMock(Store::class, ['getBaseUrl']); + $storeManager->method('getStore')->willReturn($this->store); + $objectManager = new ObjectManager($this); + $this->category = $objectManager->getObject(Category::class); + $this->model = $objectManager->getObject( + Image::class, + [ + 'storeManager' => $storeManager, + 'fileInfo' => $this->getFileInfo() + ] + ); + } + + /** + * Test that image URL resolver works correctly with different base URL format + * + * @param string $baseUrl + * @param string $imagePath + * @param string $url + * @dataProvider getUrlDataProvider + */ + public function testGetUrl(string $imagePath, string $baseUrl, string $url) + { + $this->store->method('getBaseUrl') + ->with(UrlInterface::URL_TYPE_MEDIA) + ->willReturn($baseUrl); + $this->category->setData('image_attr_code', $imagePath); + $this->assertEquals($url, $this->model->getUrl($this->category, 'image_attr_code')); + } + + /** + * @return array + */ + public function getUrlDataProvider() + { + return [ + [ + 'testimage', + 'http://www.example.com/', + 'http://www.example.com/catalog/category/testimage' + ], + [ + 'testimage', + 'http://www.example.com/pub/media/', + 'http://www.example.com/pub/media/catalog/category/testimage' + ], + [ + 'testimage', + 'http://www.example.com/base/path/pub/media/', + 'http://www.example.com/base/path/pub/media/catalog/category/testimage' + ], + [ + '/pub/media/catalog/category/testimage', + 'http://www.example.com/pub/media/', + 'http://www.example.com/pub/media/catalog/category/testimage' + ], + [ + '/pub/media/catalog/category/testimage', + 'http://www.example.com/base/path/pub/media/', + 'http://www.example.com/base/path/pub/media/catalog/category/testimage' + ], + [ + '/pub/media/posters/testimage', + 'http://www.example.com/pub/media/', + 'http://www.example.com/pub/media/posters/testimage' + ], + [ + '/pub/media/posters/testimage', + 'http://www.example.com/base/path/pub/media/', + 'http://www.example.com/base/path/pub/media/posters/testimage' + ], + [ + '', + 'http://www.example.com/', + '' + ] + ]; + } + + /** + * Get FileInfo mock + * + * @return MockObject + */ + private function getFileInfo(): MockObject + { + $mediaDir = 'pub/media'; + $fileInfo = $this->createMock(FileInfo::class); + $fileInfo->method('isBeginsWithMediaDirectoryPath') + ->willReturnCallback( + function ($path) use ($mediaDir) { + return strpos(ltrim($path, '/'), $mediaDir) === 0; + } + ); + $fileInfo->method('getRelativePathToMediaDirectory') + ->willReturnCallback( + function ($path) use ($mediaDir) { + return str_replace($mediaDir, '', $path); + } + ); + return $fileInfo; + } +} diff --git a/app/code/Magento/Catalog/ViewModel/Category/Image.php b/app/code/Magento/Catalog/ViewModel/Category/Image.php new file mode 100644 index 0000000000000..2982779bd2eb3 --- /dev/null +++ b/app/code/Magento/Catalog/ViewModel/Category/Image.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\ViewModel\Category; + +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Category\Image as CategoryImage; +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * Category image view model + */ +class Image implements ArgumentInterface +{ + private const ATTRIBUTE_NAME = 'image'; + /** + * @var CategoryImage + */ + private $image; + + /** + * Initialize dependencies. + * + * @param CategoryImage $image + */ + public function __construct(CategoryImage $image) + { + $this->image = $image; + } + + /** + * Resolve category image URL + * + * @param Category $category + * @param string $attributeCode + * @return string + */ + public function getUrl(Category $category, string $attributeCode = self::ATTRIBUTE_NAME): string + { + return $this->image->getUrl($category, $attributeCode); + } +} diff --git a/app/code/Magento/Catalog/ViewModel/Category/Output.php b/app/code/Magento/Catalog/ViewModel/Category/Output.php new file mode 100644 index 0000000000000..367d59daea48e --- /dev/null +++ b/app/code/Magento/Catalog/ViewModel/Category/Output.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\ViewModel\Category; + +use Magento\Catalog\Helper\Output as OutputHelper; +use Magento\Catalog\Model\Category; +use Magento\Framework\View\Element\Block\ArgumentInterface; + +/** + * Category attribute output view model + */ +class Output implements ArgumentInterface +{ + /** + * @var OutputHelper + */ + private $outputHelper; + + /** + * Initialize dependencies. + * + * @param OutputHelper $outputHelper + */ + public function __construct(OutputHelper $outputHelper) + { + $this->outputHelper = $outputHelper; + } + + /** + * Prepare category attribute html output + * + * @param Category $category + * @param string $attributeHtml + * @param string $attributeName + * @return string + */ + public function categoryAttribute(Category $category, string $attributeHtml, string $attributeName): string + { + return $this->outputHelper->categoryAttribute($category, $attributeHtml, $attributeName); + } +} diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml index 5fee1d8447e5a..c4adcaf785012 100644 --- a/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml +++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml @@ -9,7 +9,12 @@ <body> <referenceContainer name="columns.top"> <container name="category.view.container" htmlTag="div" htmlClass="category-view" after="-"> - <block class="Magento\Catalog\Block\Category\View" name="category.image" template="Magento_Catalog::category/image.phtml"/> + <block class="Magento\Catalog\Block\Category\View" name="category.image" template="Magento_Catalog::category/image.phtml"> + <arguments> + <argument name="image" xsi:type="object">Magento\Catalog\ViewModel\Category\Image</argument> + <argument name="output" xsi:type="object">Magento\Catalog\ViewModel\Category\Output</argument> + </arguments> + </block> <block class="Magento\Catalog\Block\Category\View" name="category.description" template="Magento_Catalog::category/description.phtml"/> <block class="Magento\Catalog\Block\Category\View" name="category.cms" template="Magento_Catalog::category/cms.phtml"/> </container> diff --git a/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml b/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml index 02593d3b541a1..8f72e4713d22b 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/category/image.phtml @@ -16,10 +16,9 @@ // phpcs:disable Magento2.Security.LanguageConstruct.DirectOutput ?> <?php - $_helper = $this->helper(Magento\Catalog\Helper\Output::class); $_category = $block->getCurrentCategory(); $_imgHtml = ''; - if ($_imgUrl = $_category->getImageUrl()) { + if ($_imgUrl = $block->getImage()->getUrl($_category)) { $_imgHtml = '<div class="category-image"><img src="' . $block->escapeUrl($_imgUrl) . '" alt="' @@ -27,7 +26,7 @@ . '" title="' . $block->escapeHtmlAttr($_category->getName()) . '" class="image" /></div>'; - $_imgHtml = $_helper->categoryAttribute($_category, $_imgHtml, 'image'); + $_imgHtml = $block->getOutput()->categoryAttribute($_category, $_imgHtml, 'image'); /* @noEscape */ echo $_imgHtml; } ?> From aba3b37493b34a465565b3bb8be878408873f3db Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Fri, 13 Mar 2020 16:48:01 +0200 Subject: [PATCH 297/369] Fix static test failures for class annotaions These checks were moved to Magento Coding Standards --- .../ClassAnnotationStructureSniff.php | 125 ------------------ .../ClassAnnotationStructureSniffTest.php | 86 ------------ .../_files/AbstractClassAnnotationFixture.php | 13 -- .../_files/ClassAnnotationFixture.php | 20 --- ...assAnnotationNoShortDescriptionFixture.php | 16 --- ...AnnotationNoSpacingBetweenLinesFixture.php | 18 --- .../abstract_class_annotation_errors.txt | 10 -- .../_files/class_annotation_errors.txt | 11 -- ...s_annotation_noshortdescription_errors.txt | 14 -- ...nnotation_nospacingbetweenLines_errors.txt | 12 -- 10 files changed, 325 deletions(-) delete mode 100644 dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/ClassAnnotationStructureSniffTest.php delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/AbstractClassAnnotationFixture.php delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationFixture.php delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoShortDescriptionFixture.php delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoSpacingBetweenLinesFixture.php delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/abstract_class_annotation_errors.txt delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_errors.txt delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_noshortdescription_errors.txt delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_nospacingbetweenLines_errors.txt diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php deleted file mode 100644 index 43df5658bbe0d..0000000000000 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/ClassAnnotationStructureSniff.php +++ /dev/null @@ -1,125 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); -namespace Magento\Sniffs\Annotation; - -use PHP_CodeSniffer\Sniffs\Sniff; -use PHP_CodeSniffer\Files\File; - -/** - * Sniff to validate structure of class, interface annotations - */ -class ClassAnnotationStructureSniff implements Sniff -{ - /** - * @var AnnotationFormatValidator - */ - private $annotationFormatValidator; - - /** - * @inheritdoc - */ - public function register() - { - return [ - T_CLASS - ]; - } - - /** - * AnnotationStructureSniff constructor. - */ - public function __construct() - { - $this->annotationFormatValidator = new AnnotationFormatValidator(); - } - - /** - * Validates whether annotation block exists for interface, abstract or final classes - * - * @param File $phpcsFile - * @param int $previousCommentClosePtr - * @param int $stackPtr - */ - private function validateInterfaceOrAbstractOrFinalClassAnnotationBlockExists( - File $phpcsFile, - int $previousCommentClosePtr, - int $stackPtr - ) : void { - $tokens = $phpcsFile->getTokens(); - if ($tokens[$stackPtr]['type'] === 'T_CLASS') { - if ($tokens[$stackPtr - 2]['type'] === 'T_ABSTRACT' && - $tokens[$stackPtr - 4]['content'] != $tokens[$previousCommentClosePtr]['content'] - ) { - $error = 'Interface or abstract class is missing annotation block'; - $phpcsFile->addFixableError($error, $stackPtr, 'ClassAnnotation'); - } - if ($tokens[$stackPtr - 2]['type'] === 'T_FINAL' && - $tokens[$stackPtr - 4]['content'] != $tokens[$previousCommentClosePtr]['content'] - ) { - $error = 'Final class is missing annotation block'; - $phpcsFile->addFixableError($error, $stackPtr, 'ClassAnnotation'); - } - } - } - - /** - * Validates whether annotation block exists - * - * @param File $phpcsFile - * @param int $previousCommentClosePtr - * @param int $stackPtr - */ - private function validateAnnotationBlockExists(File $phpcsFile, int $previousCommentClosePtr, int $stackPtr) : void - { - $tokens = $phpcsFile->getTokens(); - $this->validateInterfaceOrAbstractOrFinalClassAnnotationBlockExists( - $phpcsFile, - $previousCommentClosePtr, - $stackPtr - ); - if ($tokens[$stackPtr - 2]['content'] != 'class' && $tokens[$stackPtr - 2]['content'] != 'abstract' - && $tokens[$stackPtr - 2]['content'] != 'final' - && $tokens[$stackPtr - 2]['content'] !== $tokens[$previousCommentClosePtr]['content'] - ) { - $error = 'Class is missing annotation block'; - $phpcsFile->addFixableError($error, $stackPtr, 'ClassAnnotation'); - } - } - - /** - * @inheritdoc - */ - public function process(File $phpcsFile, $stackPtr) - { - $tokens = $phpcsFile->getTokens(); - $previousCommentClosePtr = $phpcsFile->findPrevious(T_DOC_COMMENT_CLOSE_TAG, $stackPtr - 1, 0); - if (!$previousCommentClosePtr) { - $phpcsFile->addError('Comment block is missing', $stackPtr -1, 'MethodArguments'); - return; - } - $this->validateAnnotationBlockExists($phpcsFile, $previousCommentClosePtr, $stackPtr); - $commentStartPtr = $phpcsFile->findPrevious(T_DOC_COMMENT_OPEN_TAG, $stackPtr - 1, 0); - $commentCloserPtr = $tokens[$commentStartPtr]['comment_closer']; - $emptyTypeTokens = [ - T_DOC_COMMENT_WHITESPACE, - T_DOC_COMMENT_STAR - ]; - $shortPtr = $phpcsFile->findNext($emptyTypeTokens, $commentStartPtr +1, $commentCloserPtr, true); - if ($shortPtr === false) { - $error = 'Annotation block is empty'; - $phpcsFile->addError($error, $commentStartPtr, 'MethodAnnotation'); - } else { - $this->annotationFormatValidator->validateDescriptionFormatStructure( - $phpcsFile, - $commentStartPtr, - (int) $shortPtr, - $previousCommentClosePtr, - $emptyTypeTokens - ); - } - } -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/ClassAnnotationStructureSniffTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/ClassAnnotationStructureSniffTest.php deleted file mode 100644 index afe559fdd6759..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/ClassAnnotationStructureSniffTest.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -class ClassAnnotationStructureSniffTest extends \PHPUnit\Framework\TestCase -{ - /** - * @return array - */ - public function processDataProvider() - { - return [ - [ - 'ClassAnnotationFixture.php', - 'class_annotation_errors.txt', - ], - [ - 'AbstractClassAnnotationFixture.php', - 'abstract_class_annotation_errors.txt', - ], - [ - 'ClassAnnotationNoShortDescriptionFixture.php', - 'class_annotation_noshortdescription_errors.txt', - ], - [ - 'ClassAnnotationNoSpacingBetweenLinesFixture.php', - 'class_annotation_nospacingbetweenLines_errors.txt', - ] - ]; - } - - /** - * Copy a file - * - * @param string $source - * @param string $destination - */ - private function copyFile($source, $destination) : void - { - $sourcePath = $source; - $destinationPath = $destination; - $sourceDirectory = opendir($sourcePath); - while ($readFile = readdir($sourceDirectory)) { - if ($readFile != '.' && $readFile != '..') { - if (!file_exists($destinationPath . $readFile)) { - copy($sourcePath . $readFile, $destinationPath . $readFile); - } - } - } - closedir($sourceDirectory); - } - - /** - * @param string $fileUnderTest - * @param string $expectedReportFile - * @dataProvider processDataProvider - */ - public function testProcess($fileUnderTest, $expectedReportFile) - { - $reportFile = __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'phpcs_report.txt'; - $this->copyFile( - __DIR__ . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR, - TESTS_TEMP_DIR . DIRECTORY_SEPARATOR - ); - $codeSniffer = new \Magento\TestFramework\CodingStandard\Tool\CodeSniffer( - 'Magento', - $reportFile, - new \Magento\TestFramework\CodingStandard\Tool\CodeSniffer\Wrapper() - ); - $result = $codeSniffer->run( - [TESTS_TEMP_DIR . DIRECTORY_SEPARATOR . $fileUnderTest] - ); - $actual = file_get_contents($reportFile); - $expected = file_get_contents( - TESTS_TEMP_DIR . DIRECTORY_SEPARATOR . $expectedReportFile - ); - unlink($reportFile); - $this->assertEquals(2, $result); - $this->assertEquals($expected, $actual); - } -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/AbstractClassAnnotationFixture.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/AbstractClassAnnotationFixture.php deleted file mode 100644 index 43973b3083e56..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/AbstractClassAnnotationFixture.php +++ /dev/null @@ -1,13 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -abstract class AbstractClassAnnotationFixture -{ -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationFixture.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationFixture.php deleted file mode 100644 index f9a997dcfeb9d..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationFixture.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -class ClassAnnotationFixture -{ - /** - * - * @inheritdoc - */ - public function getProductListDefaultSortBy1() - { - } -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoShortDescriptionFixture.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoShortDescriptionFixture.php deleted file mode 100644 index be49cc6c788e9..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoShortDescriptionFixture.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -/** - * @SuppressWarnings(PHPMD.NumberOfChildren) - */ -class ClassAnnotationNoShortDescriptionFixture -{ -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoSpacingBetweenLinesFixture.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoSpacingBetweenLinesFixture.php deleted file mode 100644 index 382185734c281..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/ClassAnnotationNoSpacingBetweenLinesFixture.php +++ /dev/null @@ -1,18 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Sniffs\Annotation; - -/** - * Class ClassAnnotationNoSpacingBetweenLinesFixture - * @see Magento\Sniffs\Annotation\_files - */ -class ClassAnnotationNoSpacingBetweenLinesFixture -{ - -} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/abstract_class_annotation_errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/abstract_class_annotation_errors.txt deleted file mode 100644 index 23698cfb72e27..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/abstract_class_annotation_errors.txt +++ /dev/null @@ -1,10 +0,0 @@ -FILE: ...o/Sniffs/Annotation/_fixtures/AbstractClassAnnotationFixture.php ----------------------------------------------------------------------- -FOUND 1 ERROR AFFECTING 1 LINE ----------------------------------------------------------------------- - 11 | ERROR | [x] Interface or abstract class is missing annotation - | | block ----------------------------------------------------------------------- -PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n - - diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_errors.txt deleted file mode 100644 index aca2721131608..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_errors.txt +++ /dev/null @@ -1,11 +0,0 @@ - -FILE: ...Magento/Sniffs/Annotation/_fixtures/class_annotation_fixture.php ----------------------------------------------------------------------- -FOUND 1 ERROR AFFECTING 1 LINE ----------------------------------------------------------------------- - 11 | ERROR | [x] Class is missing annotation block ----------------------------------------------------------------------- -PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY ----------------------------------------------------------------------- - - diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_noshortdescription_errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_noshortdescription_errors.txt deleted file mode 100644 index ecc702c568934..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_noshortdescription_errors.txt +++ /dev/null @@ -1,14 +0,0 @@ -'\n -FILE: ...nnotation/_fixtures/ClassAnnotationNoShortDescriptionFixture.php\n -----------------------------------------------------------------------\n -FOUND 1 ERROR AFFECTING 1 LINE\n -----------------------------------------------------------------------\n - 11 | ERROR | [x] Missing short description\n -----------------------------------------------------------------------\n -PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n -----------------------------------------------------------------------\n -\n -\n -' - - diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_nospacingbetweenLines_errors.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_nospacingbetweenLines_errors.txt deleted file mode 100644 index 0102ca7f79a1f..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/Sniffs/Annotation/_files/class_annotation_nospacingbetweenLines_errors.txt +++ /dev/null @@ -1,12 +0,0 @@ -'\n -FILE: ...tation/_fixtures/ClassAnnotationNoSpacingBetweenLinesFixture.php\n -----------------------------------------------------------------------\n -FOUND 1 ERROR AFFECTING 1 LINE\n -----------------------------------------------------------------------\n - 13 | ERROR | [x] There must be exactly one blank line between lines short and long descriptions\n -----------------------------------------------------------------------\n -PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY\n -----------------------------------------------------------------------\n -\n -\n -' From 9dfeccff6fac75ccc9239e8c65dabaa387577424 Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Fri, 13 Mar 2020 19:57:46 +0200 Subject: [PATCH 298/369] Make the "Display Category Filter" field appear after after additional fields for "Price Navigation Step Calculation" --- app/code/Magento/Catalog/etc/adminhtml/system.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 30a8ec8a81ec5..4e10453f542bb 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -139,7 +139,7 @@ </field> </group> <group id="layered_navigation"> - <field id="display_category" translate="label" type="select" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <field id="display_category" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Display Category Filter</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> From 106e24426ac1c7437c43d801d2b437560114980a Mon Sep 17 00:00:00 2001 From: Dan Wallis <mrdanwallis@gmail.com> Date: Fri, 13 Mar 2020 18:43:37 +0000 Subject: [PATCH 299/369] Load view from indexer object --- .../Grid/Column/Renderer/ScheduleStatus.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php index 4d90d9c178c12..eba7591c17286 100644 --- a/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php +++ b/app/code/Magento/Indexer/Block/Backend/Grid/Column/Renderer/ScheduleStatus.php @@ -11,9 +11,8 @@ use Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer; use Magento\Framework\DataObject; use Magento\Framework\Escaper; -use Magento\Framework\Mview\View; -use Magento\Framework\Mview\ViewInterface; use Magento\Framework\Phrase; +use Magento\Indexer\Model\IndexerFactory; /** * Renderer for 'Schedule Status' column in indexer grid @@ -26,25 +25,25 @@ class ScheduleStatus extends AbstractRenderer private $escaper; /** - * @var ViewInterface + * @var IndexerFactory */ - private $viewModel; + private $indexerFactory; /** * @param Context $context * @param Escaper $escaper - * @param ViewInterface $viewModel + * @param IndexerFactory $indexerFactory * @param array $data */ public function __construct( Context $context, Escaper $escaper, - View $viewModel, + IndexerFactory $indexerFactory, array $data = [] ) { parent::__construct($context, $data); $this->escaper = $escaper; - $this->viewModel = $viewModel; + $this->indexerFactory = $indexerFactory; } /** @@ -61,7 +60,9 @@ public function render(DataObject $row) } try { - $view = $this->viewModel->load($row->getIndexerId()); + $indexer = $this->indexerFactory->create(); + $indexer->load($row->getIndexerId()); + $view = $indexer->getView(); } catch (\InvalidArgumentException $exception) { // No view for this index. return ''; From 764cdf94634311e8468af2c6ddadc75e30e10211 Mon Sep 17 00:00:00 2001 From: Alexander Taranovsky <firster@atwix.com> Date: Fri, 13 Mar 2020 21:21:48 +0200 Subject: [PATCH 300/369] Update AbstractExtensibleObject.php --- lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php b/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php index bf2967ba564ff..f96e91dfbd45c 100644 --- a/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php +++ b/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php @@ -12,6 +12,7 @@ * * @SuppressWarnings(PHPMD.NumberOfChildren) * @api + * @codeCoverageIgnore * @deprecated * @see \Magento\Framework\Model\AbstractExtensibleModel */ From 987d7138351adedd5f33af3e5525a6dee03cb1ed Mon Sep 17 00:00:00 2001 From: Alexander Taranovsky <firster@atwix.com> Date: Fri, 13 Mar 2020 21:22:07 +0200 Subject: [PATCH 301/369] Update AbstractExtensibleModel.php --- lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php index 20669ae20dc7a..3e53dbfdc118d 100644 --- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php @@ -16,6 +16,7 @@ * Implementations may choose to process custom attributes as their persistence requires them to. * @SuppressWarnings(PHPMD.NumberOfChildren) * @api + * @codeCoverageIgnore */ abstract class AbstractExtensibleModel extends AbstractModel implements \Magento\Framework\Api\CustomAttributesDataInterface From 70c5e12d4ebbf08d35cf8ebb0575826808e887d1 Mon Sep 17 00:00:00 2001 From: Alexander Taranovsky <firster@atwix.com> Date: Fri, 13 Mar 2020 21:34:37 +0200 Subject: [PATCH 302/369] Update AbstractExtensibleObject.php --- lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php b/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php index f96e91dfbd45c..902709bbedcd3 100644 --- a/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php +++ b/lib/internal/Magento/Framework/Api/AbstractExtensibleObject.php @@ -11,8 +11,8 @@ * Base Class for extensible data Objects * * @SuppressWarnings(PHPMD.NumberOfChildren) + * phpcs:disable Magento2.Classes.AbstractApi * @api - * @codeCoverageIgnore * @deprecated * @see \Magento\Framework\Model\AbstractExtensibleModel */ From 852bf03d88da8d5d6cbd09bf6263043490995bb2 Mon Sep 17 00:00:00 2001 From: Alexander Taranovsky <firster@atwix.com> Date: Fri, 13 Mar 2020 21:35:07 +0200 Subject: [PATCH 303/369] Update AbstractExtensibleModel.php --- .../Magento/Framework/Model/AbstractExtensibleModel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php index 3e53dbfdc118d..5484103cc27ef 100644 --- a/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php +++ b/lib/internal/Magento/Framework/Model/AbstractExtensibleModel.php @@ -15,8 +15,8 @@ * This class defines basic data structure of how custom attributes are stored in an ExtensibleModel. * Implementations may choose to process custom attributes as their persistence requires them to. * @SuppressWarnings(PHPMD.NumberOfChildren) + * phpcs:disable Magento2.Classes.AbstractApi * @api - * @codeCoverageIgnore */ abstract class AbstractExtensibleModel extends AbstractModel implements \Magento\Framework\Api\CustomAttributesDataInterface From fff5ce9c09b3d0e9650d7754dff384aad74af7f1 Mon Sep 17 00:00:00 2001 From: AleksLi <aleksliwork@gmail.com> Date: Fri, 13 Mar 2020 20:35:55 +0100 Subject: [PATCH 304/369] MC-26683: Removed unused set_guest_email.php fixture for test --- .../Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php index 986de59ff7bd2..3ee27acfa2418 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/AddSimpleProductToCartTest.php @@ -111,7 +111,6 @@ public function testAddDisabledProductToCart(): void * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/set_guest_email.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/set_simple_product_out_of_stock.php * @return void From 1e8ed89642906a7e20684da2f63cf72f149b52f8 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Sun, 15 Mar 2020 03:44:34 +0100 Subject: [PATCH 305/369] Magento_Bundle / Remove `cache:flush` and extract Tests to separate files --- ...ndEditBundleProductOptionsNegativeTest.xml | 122 +++++++ ...CreateAndEditBundleProductSettingsTest.xml | 111 ------ ...alogSearchBundleByDescriptionMysqlTest.xml | 47 +++ ...ceCatalogSearchBundleByDescriptionTest.xml | 51 +++ ...anceCatalogSearchBundleByNameMysqlTest.xml | 47 +++ ...nceCatalogSearchBundleByPriceMysqlTest.xml | 56 +++ .../AdvanceCatalogSearchBundleByPriceTest.xml | 60 ++++ ...earchBundleByShortDescriptionMysqlTest.xml | 47 +++ ...alogSearchBundleByShortDescriptionTest.xml | 51 +++ .../AdvanceCatalogSearchBundleBySkuTest.xml | 46 +++ .../AdvanceCatalogSearchBundleProductTest.xml | 321 +----------------- .../MassEnableDisableBundleProductsTest.xml | 4 +- ...CatalogSearchBundleBySkuWithHyphenTest.xml | 3 +- .../StorefrontBundleAddToCartSuccessTest.xml | 114 +++++++ .../Mftf/Test/StorefrontBundleCartTest.xml | 104 ------ ...undleProductShownInCategoryListAndGrid.xml | 4 +- 16 files changed, 646 insertions(+), 542 deletions(-) create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductOptionsNegativeTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionMysqlTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByNameMysqlTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceMysqlTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionMysqlTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleBySkuTest.xml create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductOptionsNegativeTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductOptionsNegativeTest.xml new file mode 100644 index 0000000000000..8a8e9dd275ee4 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductOptionsNegativeTest.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateAndEditBundleProductOptionsNegativeTest"> + <annotations> + <features value="Bundle"/> + <stories value="Modify bundle product in Admin"/> + <title value="Admin should be able to remove any bundle option a bundle product"/> + <description value="Admin should be able to set/edit other product information when creating/editing a bundle product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-224"/> + <skip> + <issueId value="https://github.com/magento/magento2/issues/25468"/> + </skip> + <group value="Catalog"/> + </annotations> + <before> + <!-- Create a Website --> + <createData entity="customWebsite" stepKey="createWebsite"/> + + <!-- Create first simple product for a bundle option --> + <createData entity="SimpleProduct2" stepKey="createFirstSimpleProduct"/> + + <!-- Create second simple product for a bundle option --> + <createData entity="SimpleProduct2" stepKey="createSecondSimpleProduct"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Delete the simple product --> + <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> + + <!-- Delete the simple product --> + <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> + + <!-- Delete a Website --> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="Second Website"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <!-- Create new bundle product --> + <actionGroup ref="GoToSpecifiedCreateProductPageActionGroup" stepKey="createBundleProduct"> + <argument name="productType" value="bundle"/> + </actionGroup> + + <!-- Fill all main fields --> + <actionGroup ref="FillMainBundleProductFormActionGroup" stepKey="fillMainProductFields"/> + + <!-- Add first bundle option to the product --> + <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addFirstBundleOption"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$createFirstSimpleProduct.sku$$"/> + <argument name="prodTwoSku" value="$$createSecondSimpleProduct.sku$$"/> + <argument name="optionTitle" value="{{RadioButtonsOption.title}}"/> + <argument name="inputType" value="{{RadioButtonsOption.type}}"/> + </actionGroup> + + <!-- Add second bundle option to the product --> + <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addSecondBundleOption"> + <argument name="x" value="1"/> + <argument name="n" value="2"/> + <argument name="prodOneSku" value="$$createFirstSimpleProduct.sku$$"/> + <argument name="prodTwoSku" value="$$createSecondSimpleProduct.sku$$"/> + <argument name="optionTitle" value="{{CheckboxOption.title}}"/> + <argument name="inputType" value="{{CheckboxOption.type}}"/> + </actionGroup> + + <!-- Add third bundle option to the product --> + <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addThirdBundleOption"> + <argument name="x" value="2"/> + <argument name="n" value="3"/> + <argument name="prodOneSku" value="$$createFirstSimpleProduct.sku$$"/> + <argument name="prodTwoSku" value="$$createSecondSimpleProduct.sku$$"/> + <argument name="optionTitle" value="{{RadioButtonsOption.title}}"/> + <argument name="inputType" value="{{RadioButtonsOption.type}}"/> + </actionGroup> + + <!-- Set product in created Website --> + <actionGroup ref="AdminAssignProductInWebsiteActionGroup" stepKey="selectProductInWebsites"> + <argument name="website" value="$createWebsite.website[name]$"/> + </actionGroup> + + <!-- Save product form --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveWithThreeOptions"/> + + <!-- Open created product --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <!-- Remove second option --> + <actionGroup ref="DeleteBundleOptionByIndexActionGroup" stepKey="deleteSecondOption"> + <argument name="deleteIndex" value="1"/> + </actionGroup> + + <!-- Save product form --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="clickSaveProduct"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveWithTwoOptions"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="messageYouSavedTheProductIsShown"/> + + <!-- Delete created bundle product --> + <actionGroup ref="DeleteProductUsingProductGridActionGroup" stepKey="deleteProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml index b143bd63280ea..8442f9e583cab 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminCreateAndEditBundleProductSettingsTest.xml @@ -136,117 +136,6 @@ <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> <dontSeeElement selector="{{StorefrontProductCartGiftOptionSection.giftOptions}}" stepKey="dontSeeGiftOptionBtn"/> - <!-- Delete created bundle product --> - <actionGroup ref="DeleteProductUsingProductGridActionGroup" stepKey="deleteProduct"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - </test> - <test name="AdminCreateAndEditBundleProductOptionsNegativeTest"> - <annotations> - <features value="Bundle"/> - <stories value="Modify bundle product in Admin"/> - <title value="Admin should be able to remove any bundle option a bundle product"/> - <description value="Admin should be able to set/edit other product information when creating/editing a bundle product"/> - <severity value="MAJOR"/> - <testCaseId value="MC-224"/> - <skip> - <issueId value="https://github.com/magento/magento2/issues/25468"/> - </skip> - <group value="Catalog"/> - </annotations> - <before> - <!-- Create a Website --> - <createData entity="customWebsite" stepKey="createWebsite"/> - - <!-- Create first simple product for a bundle option --> - <createData entity="SimpleProduct2" stepKey="createFirstSimpleProduct"/> - - <!-- Create second simple product for a bundle option --> - <createData entity="SimpleProduct2" stepKey="createSecondSimpleProduct"/> - - <!-- Login as admin --> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - </before> - <after> - <!-- Delete the simple product --> - <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> - - <!-- Delete the simple product --> - <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> - - <!-- Delete a Website --> - <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> - <argument name="websiteName" value="Second Website"/> - </actionGroup> - - <!-- Log out --> - <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> - </after> - - <!-- Create new bundle product --> - <actionGroup ref="GoToSpecifiedCreateProductPageActionGroup" stepKey="createBundleProduct"> - <argument name="productType" value="bundle"/> - </actionGroup> - - <!-- Fill all main fields --> - <actionGroup ref="FillMainBundleProductFormActionGroup" stepKey="fillMainProductFields"/> - - <!-- Add first bundle option to the product --> - <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addFirstBundleOption"> - <argument name="x" value="0"/> - <argument name="n" value="1"/> - <argument name="prodOneSku" value="$$createFirstSimpleProduct.sku$$"/> - <argument name="prodTwoSku" value="$$createSecondSimpleProduct.sku$$"/> - <argument name="optionTitle" value="{{RadioButtonsOption.title}}"/> - <argument name="inputType" value="{{RadioButtonsOption.type}}"/> - </actionGroup> - - <!-- Add second bundle option to the product --> - <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addSecondBundleOption"> - <argument name="x" value="1"/> - <argument name="n" value="2"/> - <argument name="prodOneSku" value="$$createFirstSimpleProduct.sku$$"/> - <argument name="prodTwoSku" value="$$createSecondSimpleProduct.sku$$"/> - <argument name="optionTitle" value="{{CheckboxOption.title}}"/> - <argument name="inputType" value="{{CheckboxOption.type}}"/> - </actionGroup> - - <!-- Add third bundle option to the product --> - <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addThirdBundleOption"> - <argument name="x" value="2"/> - <argument name="n" value="3"/> - <argument name="prodOneSku" value="$$createFirstSimpleProduct.sku$$"/> - <argument name="prodTwoSku" value="$$createSecondSimpleProduct.sku$$"/> - <argument name="optionTitle" value="{{RadioButtonsOption.title}}"/> - <argument name="inputType" value="{{RadioButtonsOption.type}}"/> - </actionGroup> - - <!-- Set product in created Website --> - <actionGroup ref="AdminAssignProductInWebsiteActionGroup" stepKey="selectProductInWebsites"> - <argument name="website" value="$createWebsite.website[name]$"/> - </actionGroup> - - <!-- Save product form --> - <actionGroup ref="SaveProductFormActionGroup" stepKey="saveWithThreeOptions"/> - - <!-- Open created product --> - <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - - <!-- Remove second option --> - <actionGroup ref="DeleteBundleOptionByIndexActionGroup" stepKey="deleteSecondOption"> - <argument name="deleteIndex" value="1"/> - </actionGroup> - - <!-- Save product form --> - <actionGroup ref="SaveProductFormActionGroup" stepKey="clickSaveProduct"/> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveWithTwoOptions"/> - <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="messageYouSavedTheProductIsShown"/> - <!-- Delete created bundle product --> <actionGroup ref="DeleteProductUsingProductGridActionGroup" stepKey="deleteProduct"> <argument name="product" value="BundleProduct"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionMysqlTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionMysqlTest.xml new file mode 100644 index 0000000000000..e9525e2a144fb --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionMysqlTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleByDescriptionMysqlTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product description using the MySQL search engine"/> + <description value="Guest customer should be able to advance search Bundle product with product description using the MySQL search engine"/> + <severity value="MAJOR"/> + <testCaseId value="MC-20473"/> + <group value="Bundle"/> + <group value="SearchEngineMysql"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + + <magentoCron stepKey="runCronReindex" groups="index"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionTest.xml new file mode 100644 index 0000000000000..95b4e06678af2 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByDescriptionTest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleByDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product description"/> + <description value="Guest customer should be able to advance search Bundle product with product description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-242"/> + <group value="Bundle"/> + <group value="SearchEngineElasticsearch"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + + <magentoCron stepKey="runCronReindex" groups="index"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + <see userInput="3 items" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> + <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('1')}}" stepKey="seeProductName"/> + <see userInput="$$simple1.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('2')}}" stepKey="seeSimple1ProductName"/> + <see userInput="$$simple2.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('3')}}" stepKey="seeSimple2ProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByNameMysqlTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByNameMysqlTest.xml new file mode 100644 index 0000000000000..05fe8dd7de27e --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByNameMysqlTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleByNameMysqlTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product name using the MySQL search engine"/> + <description value="Guest customer should be able to advance search Bundle product with product name using the MySQL search engine"/> + <severity value="MAJOR"/> + <testCaseId value="MC-20472"/> + <group value="Bundle"/> + <group value="SearchEngineMysql"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + + <magentoCron stepKey="runCronReindex" groups="index"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceMysqlTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceMysqlTest.xml new file mode 100644 index 0000000000000..c70e9a95ab532 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceMysqlTest.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleByPriceMysqlTest" extends="AdvanceCatalogSearchSimpleProductByPriceTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product price using the MySQL search engine"/> + <description value="Guest customer should be able to advance search Bundle product with product price the MySQL search engine"/> + <severity value="MAJOR"/> + <testCaseId value="MC-20475"/> + <group value="Bundle"/> + <group value="SearchEngineMysql"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + <getData entity="GetProduct" stepKey="arg1"> + <requiredEntity createDataKey="product"/> + </getData> + <getData entity="GetProduct" stepKey="arg2"> + <requiredEntity createDataKey="simple1"/> + </getData> + <getData entity="GetProduct" stepKey="arg3"> + <requiredEntity createDataKey="simple2"/> + </getData> + + <magentoCron stepKey="runCronReindex" groups="index"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceTest.xml new file mode 100644 index 0000000000000..f45c9ceba635f --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByPriceTest.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleByPriceTest" extends="AdvanceCatalogSearchSimpleProductByPriceTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product price"/> + <description value="Guest customer should be able to advance search Bundle product with product price"/> + <severity value="MAJOR"/> + <testCaseId value="MC-251"/> + <group value="Bundle"/> + <group value="SearchEngineElasticsearch"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + <getData entity="GetProduct" stepKey="arg1"> + <requiredEntity createDataKey="product"/> + </getData> + <getData entity="GetProduct" stepKey="arg2"> + <requiredEntity createDataKey="simple1"/> + </getData> + <getData entity="GetProduct" stepKey="arg3"> + <requiredEntity createDataKey="simple2"/> + </getData> + + <magentoCron stepKey="runCronReindex" groups="index"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + <see userInput="3 items" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> + <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('1')}}" stepKey="seeProductName"/> + <see userInput="$$simple1.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('2')}}" stepKey="seeSimple1ProductName"/> + <see userInput="$$simple2.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('3')}}" stepKey="seeSimple2ProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionMysqlTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionMysqlTest.xml new file mode 100644 index 0000000000000..2bb2974b78555 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionMysqlTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleByShortDescriptionMysqlTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product short description using the MySQL search engine"/> + <description value="Guest customer should be able to advance search Bundle product with product short description using the MySQL search engine"/> + <severity value="MAJOR"/> + <testCaseId value="MC-20474"/> + <group value="Bundle"/> + <group value="SearchEngineMysql"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + + <magentoCron stepKey="runCronReindex" groups="index"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionTest.xml new file mode 100644 index 0000000000000..05e14174d14a5 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleByShortDescriptionTest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleByShortDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product short description"/> + <description value="Guest customer should be able to advance search Bundle product with product short description"/> + <severity value="MAJOR"/> + <testCaseId value="MC-250"/> + <group value="Bundle"/> + <group value="SearchEngineElasticsearch"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProduct" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + + <magentoCron stepKey="runCronReindex" groups="index"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + <see userInput="3 items" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> + <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('1')}}" stepKey="seeProductName"/> + <see userInput="$$simple1.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('2')}}" stepKey="seeSimple1ProductName"/> + <see userInput="$$simple2.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('3')}}" stepKey="seeSimple2ProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleBySkuTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleBySkuTest.xml new file mode 100644 index 0000000000000..eadf7667b010b --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleBySkuTest.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdvanceCatalogSearchBundleBySkuTest" extends="AdvanceCatalogSearchSimpleProductBySkuTest"> + <annotations> + <features value="Bundle"/> + <stories value="Advanced Catalog Product Search for all product types"/> + <title value="Guest customer should be able to advance search Bundle product with product sku"/> + <description value="Guest customer should be able to advance search Bundle product with product sku"/> + <severity value="MAJOR"/> + <testCaseId value="MC-143"/> + <group value="Bundle"/> + </annotations> + <before> + <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> + <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> + <createData entity="ApiBundleProductUnderscoredSku" stepKey="product"/> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="product"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink2"> + <requiredEntity createDataKey="product"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simple2"/> + </createData> + + <magentoCron stepKey="runCronReindex" groups="index"/> + </before> + <after> + <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> + <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml index c6aab0ea54ea2..e6af89181e558 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml @@ -36,280 +36,8 @@ <requiredEntity createDataKey="bundleOption"/> <requiredEntity createDataKey="simple2"/> </createData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </before> - <after> - <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> - <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> - </after> - <see userInput="3 items" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('1')}}" stepKey="seeProductName"/> - <see userInput="$$simple1.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('2')}}" stepKey="seeSimple1ProductName"/> - <see userInput="$$simple2.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('3')}}" stepKey="seeSimple2ProductName"/> - </test> - <test name="AdvanceCatalogSearchBundleByNameMysqlTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> - <annotations> - <features value="Bundle"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search Bundle product with product name using the MySQL search engine"/> - <description value="Guest customer should be able to advance search Bundle product with product name using the MySQL search engine"/> - <severity value="MAJOR"/> - <testCaseId value="MC-20472"/> - <group value="Bundle"/> - <group value="SearchEngineMysql"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> - <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> - <createData entity="ApiBundleProduct" stepKey="product"/> - <createData entity="DropDownBundleOption" stepKey="bundleOption"> - <requiredEntity createDataKey="product"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink1"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple1"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink2"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple2"/> - </createData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </before> - <after> - <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> - <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> - </after> - </test> - <test name="AdvanceCatalogSearchBundleBySkuTest" extends="AdvanceCatalogSearchSimpleProductBySkuTest"> - <annotations> - <features value="Bundle"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search Bundle product with product sku"/> - <description value="Guest customer should be able to advance search Bundle product with product sku"/> - <severity value="MAJOR"/> - <testCaseId value="MC-143"/> - <group value="Bundle"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> - <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> - <createData entity="ApiBundleProductUnderscoredSku" stepKey="product"/> - <createData entity="DropDownBundleOption" stepKey="bundleOption"> - <requiredEntity createDataKey="product"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink1"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple1"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink2"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple2"/> - </createData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </before> - <after> - <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> - <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> - </after> - </test> - <test name="AdvanceCatalogSearchBundleByDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> - <annotations> - <features value="Bundle"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search Bundle product with product description"/> - <description value="Guest customer should be able to advance search Bundle product with product description"/> - <severity value="MAJOR"/> - <testCaseId value="MC-242"/> - <group value="Bundle"/> - <group value="SearchEngineElasticsearch"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> - <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> - <createData entity="ApiBundleProduct" stepKey="product"/> - <createData entity="DropDownBundleOption" stepKey="bundleOption"> - <requiredEntity createDataKey="product"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink1"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple1"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink2"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple2"/> - </createData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </before> - <after> - <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> - <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> - </after> - <see userInput="3 items" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('1')}}" stepKey="seeProductName"/> - <see userInput="$$simple1.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('2')}}" stepKey="seeSimple1ProductName"/> - <see userInput="$$simple2.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('3')}}" stepKey="seeSimple2ProductName"/> - </test> - <test name="AdvanceCatalogSearchBundleByDescriptionMysqlTest" extends="AdvanceCatalogSearchSimpleProductByDescriptionTest"> - <annotations> - <features value="Bundle"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search Bundle product with product description using the MySQL search engine"/> - <description value="Guest customer should be able to advance search Bundle product with product description using the MySQL search engine"/> - <severity value="MAJOR"/> - <testCaseId value="MC-20473"/> - <group value="Bundle"/> - <group value="SearchEngineMysql"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> - <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> - <createData entity="ApiBundleProduct" stepKey="product"/> - <createData entity="DropDownBundleOption" stepKey="bundleOption"> - <requiredEntity createDataKey="product"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink1"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple1"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink2"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple2"/> - </createData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </before> - <after> - <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> - <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> - </after> - </test> - <test name="AdvanceCatalogSearchBundleByShortDescriptionTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> - <annotations> - <features value="Bundle"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search Bundle product with product short description"/> - <description value="Guest customer should be able to advance search Bundle product with product short description"/> - <severity value="MAJOR"/> - <testCaseId value="MC-250"/> - <group value="Bundle"/> - <group value="SearchEngineElasticsearch"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> - <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> - <createData entity="ApiBundleProduct" stepKey="product"/> - <createData entity="DropDownBundleOption" stepKey="bundleOption"> - <requiredEntity createDataKey="product"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink1"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple1"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink2"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple2"/> - </createData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </before> - <after> - <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> - <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> - </after> - <see userInput="3 items" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.itemFound}}" stepKey="see"/> - <see userInput="$$product.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('1')}}" stepKey="seeProductName"/> - <see userInput="$$simple1.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('2')}}" stepKey="seeSimple1ProductName"/> - <see userInput="$$simple2.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('3')}}" stepKey="seeSimple2ProductName"/> - </test> - <test name="AdvanceCatalogSearchBundleByShortDescriptionMysqlTest" extends="AdvanceCatalogSearchSimpleProductByShortDescriptionTest"> - <annotations> - <features value="Bundle"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search Bundle product with product short description using the MySQL search engine"/> - <description value="Guest customer should be able to advance search Bundle product with product short description using the MySQL search engine"/> - <severity value="MAJOR"/> - <testCaseId value="MC-20474"/> - <group value="Bundle"/> - <group value="SearchEngineMysql"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> - <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> - <createData entity="ApiBundleProduct" stepKey="product"/> - <createData entity="DropDownBundleOption" stepKey="bundleOption"> - <requiredEntity createDataKey="product"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink1"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple1"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink2"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple2"/> - </createData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </before> - <after> - <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> - <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> - </after> - </test> - <test name="AdvanceCatalogSearchBundleByPriceTest" extends="AdvanceCatalogSearchSimpleProductByPriceTest"> - <annotations> - <features value="Bundle"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search Bundle product with product price"/> - <description value="Guest customer should be able to advance search Bundle product with product price"/> - <severity value="MAJOR"/> - <testCaseId value="MC-251"/> - <group value="Bundle"/> - <group value="SearchEngineElasticsearch"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> - <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> - <createData entity="ApiBundleProduct" stepKey="product"/> - <createData entity="DropDownBundleOption" stepKey="bundleOption"> - <requiredEntity createDataKey="product"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink1"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple1"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink2"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple2"/> - </createData> - <getData entity="GetProduct" stepKey="arg1"> - <requiredEntity createDataKey="product"/> - </getData> - <getData entity="GetProduct" stepKey="arg2"> - <requiredEntity createDataKey="simple1"/> - </getData> - <getData entity="GetProduct" stepKey="arg3"> - <requiredEntity createDataKey="simple2"/> - </getData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <magentoCron stepKey="runCronReindex" groups="index"/> </before> <after> <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> @@ -320,49 +48,4 @@ <see userInput="$$simple1.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('2')}}" stepKey="seeSimple1ProductName"/> <see userInput="$$simple2.name$$" selector="{{StorefrontCatalogSearchAdvancedResultMainSection.nthProductName('3')}}" stepKey="seeSimple2ProductName"/> </test> - <test name="AdvanceCatalogSearchBundleByPriceMysqlTest" extends="AdvanceCatalogSearchSimpleProductByPriceTest"> - <annotations> - <features value="Bundle"/> - <stories value="Advanced Catalog Product Search for all product types"/> - <title value="Guest customer should be able to advance search Bundle product with product price using the MySQL search engine"/> - <description value="Guest customer should be able to advance search Bundle product with product price the MySQL search engine"/> - <severity value="MAJOR"/> - <testCaseId value="MC-20475"/> - <group value="Bundle"/> - <group value="SearchEngineMysql"/> - </annotations> - <before> - <createData entity="ApiProductWithDescription" stepKey="simple1" before="simple2"/> - <createData entity="ApiProductWithDescription" stepKey="simple2" before="product"/> - <createData entity="ApiBundleProduct" stepKey="product"/> - <createData entity="DropDownBundleOption" stepKey="bundleOption"> - <requiredEntity createDataKey="product"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink1"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple1"/> - </createData> - <createData entity="ApiBundleLink" stepKey="createBundleLink2"> - <requiredEntity createDataKey="product"/> - <requiredEntity createDataKey="bundleOption"/> - <requiredEntity createDataKey="simple2"/> - </createData> - <getData entity="GetProduct" stepKey="arg1"> - <requiredEntity createDataKey="product"/> - </getData> - <getData entity="GetProduct" stepKey="arg2"> - <requiredEntity createDataKey="simple1"/> - </getData> - <getData entity="GetProduct" stepKey="arg3"> - <requiredEntity createDataKey="simple2"/> - </getData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> - </before> - <after> - <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> - <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> - </after> - </test> </tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml index 97e509db39fa7..4760570adfa2e 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml @@ -120,9 +120,7 @@ <click selector="{{AdminProductFiltersSection.disable}}" stepKey="ClickOnDisable"/> <waitForPageLoad stepKey="waitForPageloadToExecute"/> - <!--Clear Cache - reindex - resets products according to enabled/disabled view--> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> + <magentoCron stepKey="runCronReindex" groups="index"/> <actionGroup ref="ClearPageCacheActionGroup" stepKey="clearing"/> <!--Confirm bundle products have been disabled--> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdvanceCatalogSearchBundleBySkuWithHyphenTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdvanceCatalogSearchBundleBySkuWithHyphenTest.xml index d8d6034cd1a21..07be59b998dec 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdvanceCatalogSearchBundleBySkuWithHyphenTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAdvanceCatalogSearchBundleBySkuWithHyphenTest.xml @@ -36,8 +36,7 @@ <requiredEntity createDataKey="bundleOption"/> <requiredEntity createDataKey="simple2"/> </createData> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> + <magentoCron stepKey="runCronReindex" groups="index"/> </before> <after> <deleteData createDataKey="simple1" stepKey="deleteSimple1" before="deleteSimple2"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml new file mode 100644 index 0000000000000..9fc19f5c5750f --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleAddToCartSuccessTest.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontBundleAddToCartSuccessTest"> + <annotations> + <features value="Bundle"/> + <stories value="Bundle product details page"/> + <title value="Customer should be able to add the bundle product to the cart"/> + <description value="Customer should be able to add the bundle product to the cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-232"/> + <group value="Bundle"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="logout"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + </after> + + <!-- Start creating a bundle product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="FillProductNameAndSkuInProductFormActionGroup" stepKey="fillNameAndSku"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <!-- Add Option One, a "Drop-down" type option --> + <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addBundleOptionWithTwoProducts1"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="optionTitle" value="Option One"/> + <argument name="inputType" value="select"/> + </actionGroup> + + <!-- Add Option Two, a "Radio Buttons" type option --> + <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addBundleOptionWithTwoProducts2"> + <argument name="x" value="1"/> + <argument name="n" value="2"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="optionTitle" value="Option Two"/> + <argument name="inputType" value="radio"/> + </actionGroup> + + <!-- Add Option Three, a "Checkbox" type option --> + <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addBundleOptionWithTwoProducts3"> + <argument name="x" value="2"/> + <argument name="n" value="3"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="optionTitle" value="Option Three"/> + <argument name="inputType" value="checkbox"/> + </actionGroup> + + <!-- Add Option Four, a "Multi Select" type option --> + <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addBundleOptionWithTwoProducts4"> + <argument name="x" value="3"/> + <argument name="n" value="4"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="optionTitle" value="Option Four"/> + <argument name="inputType" value="multi"/> + </actionGroup> + + <!-- Save product and go to storefront --> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> + <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomize"/> + + <!-- Select all applicable options --> + <selectOption selector="select.bundle-option-select" userInput="$$simpleProduct1.name$$ +$123.00" stepKey="selectOption1"/> + <click selector="input[type='radio']:nth-of-type(1)" stepKey="selectOption2"/> + <checkOption selector="input[type='checkbox']:nth-of-type(1)" stepKey="selectOption3"/> + <selectOption selector="select[multiple='multiple']" userInput="$$simpleProduct1.name$$ +$123.00" stepKey="selectOption4"/> + + <!-- Customize and add the bundle product to our cart --> + <click selector="{{StorefrontBundledSection.addToCartConfigured}}" stepKey="clickAddToCart1"/> + <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('1')}}" userInput="This is a required field." stepKey="validForm1"/> + <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('2')}}" userInput="Please select one of the options." stepKey="validForm2"/> + <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('3')}}" userInput="Please select one of the options." stepKey="validForm3"/> + <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('4')}}" userInput="This is a required field." stepKey="validForm4"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput="You added {{BundleProduct.name}} to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> + + <!-- Verify cart contents --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> + <waitForPageLoad stepKey="waitForCart"/> + <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('1')}}" userInput="Option One" stepKey="seeOption1"/> + <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('2')}}" userInput="Option Two" stepKey="seeOption2"/> + <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('3')}}" userInput="Option Three" stepKey="seeOption3"/> + <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('4')}}" userInput="Option Four" stepKey="seeOption4"/> + <see selector="{{StorefrontBundledSection.nthItemOptionsValue('1')}}" userInput="50 x $$simpleProduct1.name$$ $123.00" stepKey="seeOptionValue1"/> + <see selector="{{StorefrontBundledSection.nthItemOptionsValue('2')}}" userInput="50 x $$simpleProduct1.name$$ $123.00" stepKey="seeOptionValue2"/> + <see selector="{{StorefrontBundledSection.nthItemOptionsValue('3')}}" userInput="50 x $$simpleProduct1.name$$ $123.00" stepKey="seeOptionValue3"/> + <see selector="{{StorefrontBundledSection.nthItemOptionsValue('4')}}" userInput="50 x $$simpleProduct1.name$$ $123.00" stepKey="seeOptionValue4"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml index d617ced82074e..ed4a592b0d71e 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleCartTest.xml @@ -124,108 +124,4 @@ <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('3')}}" userInput="Please select one of the options." stepKey="error19"/> <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('4')}}" userInput="This is a required field." stepKey="error20"/> </test> - - <test name="StorefrontBundleAddToCartSuccessTest"> - <annotations> - <features value="Bundle"/> - <stories value="Bundle product details page"/> - <title value="Customer should be able to add the bundle product to the cart"/> - <description value="Customer should be able to add the bundle product to the cart"/> - <severity value="CRITICAL"/> - <testCaseId value="MC-232"/> - <group value="Bundle"/> - </annotations> - <before> - <actionGroup ref="LoginAsAdmin" stepKey="login"/> - <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> - <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> - </before> - <after> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="logout"/> - <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> - <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> - </after> - - <!-- Start creating a bundle product --> - <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> - <waitForPageLoad stepKey="waitForProductList"/> - <actionGroup ref="GoToCreateProductPageActionGroup" stepKey="goToCreateProduct"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - <actionGroup ref="FillProductNameAndSkuInProductFormActionGroup" stepKey="fillNameAndSku"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - - <!-- Add Option One, a "Drop-down" type option --> - <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addBundleOptionWithTwoProducts1"> - <argument name="x" value="0"/> - <argument name="n" value="1"/> - <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> - <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> - <argument name="optionTitle" value="Option One"/> - <argument name="inputType" value="select"/> - </actionGroup> - - <!-- Add Option Two, a "Radio Buttons" type option --> - <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addBundleOptionWithTwoProducts2"> - <argument name="x" value="1"/> - <argument name="n" value="2"/> - <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> - <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> - <argument name="optionTitle" value="Option Two"/> - <argument name="inputType" value="radio"/> - </actionGroup> - - <!-- Add Option Three, a "Checkbox" type option --> - <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addBundleOptionWithTwoProducts3"> - <argument name="x" value="2"/> - <argument name="n" value="3"/> - <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> - <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> - <argument name="optionTitle" value="Option Three"/> - <argument name="inputType" value="checkbox"/> - </actionGroup> - - <!-- Add Option Four, a "Multi Select" type option --> - <actionGroup ref="AddBundleOptionWithTwoProductsActionGroup" stepKey="addBundleOptionWithTwoProducts4"> - <argument name="x" value="3"/> - <argument name="n" value="4"/> - <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> - <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> - <argument name="optionTitle" value="Option Four"/> - <argument name="inputType" value="multi"/> - </actionGroup> - - <!-- Save product and go to storefront --> - <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> - <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> - <waitForPageLoad stepKey="waitForStorefront"/> - <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomize"/> - - <!-- Select all applicable options --> - <selectOption selector="select.bundle-option-select" userInput="$$simpleProduct1.name$$ +$123.00" stepKey="selectOption1"/> - <click selector="input[type='radio']:nth-of-type(1)" stepKey="selectOption2"/> - <checkOption selector="input[type='checkbox']:nth-of-type(1)" stepKey="selectOption3"/> - <selectOption selector="select[multiple='multiple']" userInput="$$simpleProduct1.name$$ +$123.00" stepKey="selectOption4"/> - - <!-- Customize and add the bundle product to our cart --> - <click selector="{{StorefrontBundledSection.addToCartConfigured}}" stepKey="clickAddToCart1"/> - <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('1')}}" userInput="This is a required field." stepKey="validForm1"/> - <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('2')}}" userInput="Please select one of the options." stepKey="validForm2"/> - <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('3')}}" userInput="Please select one of the options." stepKey="validForm3"/> - <dontSee selector="{{StorefrontBundledSection.nthOptionDiv('4')}}" userInput="This is a required field." stepKey="validForm4"/> - <see selector="{{StorefrontMessagesSection.success}}" userInput="You added {{BundleProduct.name}} to your shopping cart." stepKey="seeAddToCartSuccessMessage"/> - - <!-- Verify cart contents --> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCart"/> - <waitForPageLoad stepKey="waitForCart"/> - <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('1')}}" userInput="Option One" stepKey="seeOption1"/> - <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('2')}}" userInput="Option Two" stepKey="seeOption2"/> - <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('3')}}" userInput="Option Three" stepKey="seeOption3"/> - <see selector="{{StorefrontBundledSection.nthItemOptionsTitle('4')}}" userInput="Option Four" stepKey="seeOption4"/> - <see selector="{{StorefrontBundledSection.nthItemOptionsValue('1')}}" userInput="50 x $$simpleProduct1.name$$ $123.00" stepKey="seeOptionValue1"/> - <see selector="{{StorefrontBundledSection.nthItemOptionsValue('2')}}" userInput="50 x $$simpleProduct1.name$$ $123.00" stepKey="seeOptionValue2"/> - <see selector="{{StorefrontBundledSection.nthItemOptionsValue('3')}}" userInput="50 x $$simpleProduct1.name$$ $123.00" stepKey="seeOptionValue3"/> - <see selector="{{StorefrontBundledSection.nthItemOptionsValue('4')}}" userInput="50 x $$simpleProduct1.name$$ $123.00" stepKey="seeOptionValue4"/> - </test> </tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml index 364d4fa68e590..11984bc200e15 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGrid.xml @@ -76,9 +76,7 @@ <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="messageYouSavedTheProductIsShown"/> - <!-- Perform reindex and flush cache --> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> + <magentoCron stepKey="runCronReindex" groups="index"/> <!--Go to category page--> <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToHomePage"/> From f49060be8ffa842445f7f8b2488e37bd6bd4ffbb Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 16 Mar 2020 00:17:27 +0100 Subject: [PATCH 306/369] magento/module-cms Replace inheritance with Composition `cms/page/view` --- app/code/Magento/Cms/Controller/Page/View.php | 54 ++++++++++---- app/code/Magento/Cms/Helper/Page.php | 21 +++--- .../Test/Unit/Controller/Page/ViewTest.php | 71 ++++++++++--------- 3 files changed, 93 insertions(+), 53 deletions(-) diff --git a/app/code/Magento/Cms/Controller/Page/View.php b/app/code/Magento/Cms/Controller/Page/View.php index 9d5785450ec71..da35e7a9ea9e4 100644 --- a/app/code/Magento/Cms/Controller/Page/View.php +++ b/app/code/Magento/Cms/Controller/Page/View.php @@ -1,50 +1,78 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Cms\Controller\Page; -use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Cms\Helper\Page as PageHelper; use Magento\Framework\App\Action\HttpGetActionInterface; -use Magento\Framework\App\Action\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\ForwardFactory; +use Magento\Framework\Controller\ResultInterface; /** * Custom page for storefront. Needs to be accessible by POST because of the store switching. */ -class View extends Action implements HttpGetActionInterface, HttpPostActionInterface +class View implements HttpGetActionInterface, HttpPostActionInterface { /** - * @var \Magento\Framework\Controller\Result\ForwardFactory + * @var ForwardFactory */ protected $resultForwardFactory; /** - * @param \Magento\Framework\App\Action\Context $context - * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory + * @var RequestInterface + */ + private $request; + + /** + * @var PageHelper + */ + private $pageHelper; + + /** + * @param RequestInterface $request + * @param PageHelper $pageHelper + * @param ForwardFactory $resultForwardFactory */ public function __construct( - \Magento\Framework\App\Action\Context $context, - \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory + RequestInterface $request, + PageHelper $pageHelper, + ForwardFactory $resultForwardFactory ) { + $this->request = $request; + $this->pageHelper = $pageHelper; $this->resultForwardFactory = $resultForwardFactory; - parent::__construct($context); } /** * View CMS page action * - * @return \Magento\Framework\Controller\ResultInterface + * @return ResultInterface */ public function execute() { - $pageId = $this->getRequest()->getParam('page_id', $this->getRequest()->getParam('id', false)); - $resultPage = $this->_objectManager->get(\Magento\Cms\Helper\Page::class)->prepareResultPage($this, $pageId); + $resultPage = $this->pageHelper->prepareResultPage($this, $this->getPageId()); if (!$resultPage) { $resultForward = $this->resultForwardFactory->create(); return $resultForward->forward('noroute'); } return $resultPage; } + + /** + * Returns Page ID if provided or null + * + * @return int|null + */ + private function getPageId(): ?int + { + $id = $this->request->getParam('page_id') ?? $this->request->getParam('id'); + + return $id ? (int)$id : null; + } } diff --git a/app/code/Magento/Cms/Helper/Page.php b/app/code/Magento/Cms/Helper/Page.php index 39b292bf07239..501c2a5b051a2 100644 --- a/app/code/Magento/Cms/Helper/Page.php +++ b/app/code/Magento/Cms/Helper/Page.php @@ -3,22 +3,27 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Cms\Helper; use Magento\Cms\Model\Page\CustomLayoutManagerInterface; use Magento\Cms\Model\Page\CustomLayoutRepositoryInterface; use Magento\Cms\Model\Page\IdentityMap; -use Magento\Framework\App\Action\Action; +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\Helper\AbstractHelper; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\View\Result\Page as ResultPage; /** * CMS Page Helper + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ -class Page extends \Magento\Framework\App\Helper\AbstractHelper +class Page extends AbstractHelper { /** * CMS no-route config path @@ -146,11 +151,11 @@ public function __construct( /** * Return result CMS page * - * @param Action $action + * @param ActionInterface $action * @param int $pageId - * @return \Magento\Framework\View\Result\Page|bool + * @return ResultPage|bool */ - public function prepareResultPage(Action $action, $pageId = null) + public function prepareResultPage(ActionInterface $action, $pageId = null) { if ($pageId !== null && $pageId !== $this->_page->getId()) { $delimiterPosition = strrpos($pageId, '|'); @@ -180,7 +185,7 @@ public function prepareResultPage(Action $action, $pageId = null) $this->_design->setDesignTheme($this->_page->getCustomTheme()); } } - /** @var \Magento\Framework\View\Result\Page $resultPage */ + /** @var ResultPage $resultPage */ $resultPage = $this->resultPageFactory->create(); $this->setLayoutType($inRange, $resultPage); $resultPage->addHandle('cms_page_view'); @@ -247,8 +252,8 @@ public function getPageUrl($pageId = null) * Set layout type * * @param bool $inRange - * @param \Magento\Framework\View\Result\Page $resultPage - * @return \Magento\Framework\View\Result\Page + * @param ResultPage $resultPage + * @return ResultPage */ protected function setLayoutType($inRange, $resultPage) { diff --git a/app/code/Magento/Cms/Test/Unit/Controller/Page/ViewTest.php b/app/code/Magento/Cms/Test/Unit/Controller/Page/ViewTest.php index f15e6ff3e3bf2..e4c2beaa75aa7 100644 --- a/app/code/Magento/Cms/Test/Unit/Controller/Page/ViewTest.php +++ b/app/code/Magento/Cms/Test/Unit/Controller/Page/ViewTest.php @@ -3,73 +3,80 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Cms\Test\Unit\Controller\Page; -class ViewTest extends \PHPUnit\Framework\TestCase +use Magento\Cms\Controller\Page\View; +use Magento\Cms\Helper\Page as PageHelper; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Controller\Result\Forward; +use Magento\Framework\Controller\Result\ForwardFactory; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\View\Result\Page; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ViewTest extends TestCase { + private const STUB_PAGE_ID = 2; + /** - * @var \Magento\Cms\Controller\Page\View + * @var View */ protected $controller; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ - protected $cmsHelperMock; + protected $pageHelperMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject|RequestInterface */ protected $requestMock; /** - * @var \Magento\Framework\Controller\Result\ForwardFactory|\PHPUnit_Framework_MockObject_MockObject + * @var MockObject|ForwardFactory */ protected $forwardFactoryMock; /** - * @var \Magento\Framework\Controller\Result\Forward|\PHPUnit_Framework_MockObject_MockObject + * @var MockObject|Forward */ protected $forwardMock; /** - * @var \Magento\Framework\View\Result\Page|\PHPUnit_Framework_MockObject_MockObject + * @var MockObject|Page */ protected $resultPageMock; - /** - * @var string - */ - protected $pageId = '2'; - protected function setUp() { - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); - $responseMock = $this->createMock(\Magento\Framework\App\Response\Http::class); - $this->resultPageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) + $objectManager = new ObjectManagerHelper($this); + + $this->resultPageMock = $this->getMockBuilder(Page::class) ->disableOriginalConstructor() ->getMock(); - $this->forwardFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\ForwardFactory::class) + $this->forwardFactoryMock = $this->getMockBuilder(ForwardFactory::class) ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); - $this->forwardMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Forward::class) + $this->forwardMock = $this->getMockBuilder(Forward::class) ->disableOriginalConstructor() ->getMock(); $this->forwardFactoryMock->expects($this->any()) ->method('create') ->willReturn($this->forwardMock); - $this->requestMock = $this->createMock(\Magento\Framework\App\Request\Http::class); - $this->cmsHelperMock = $this->createMock(\Magento\Cms\Helper\Page::class); - $objectManagerMock->expects($this->once())->method('get')->willReturn($this->cmsHelperMock); - $this->controller = $helper->getObject( - \Magento\Cms\Controller\Page\View::class, + $this->requestMock = $this->createMock(RequestInterface::class); + $this->pageHelperMock = $this->createMock(PageHelper::class); + + $this->controller = $objectManager->getObject( + View::class, [ - 'response' => $responseMock, - 'objectManager' => $objectManagerMock, 'request' => $this->requestMock, + 'pageHelper' => $this->pageHelperMock, 'resultForwardFactory' => $this->forwardFactoryMock ] ); @@ -81,13 +88,13 @@ public function testExecuteResultPage() ->method('getParam') ->willReturnMap( [ - ['page_id', $this->pageId, $this->pageId], - ['id', false, $this->pageId] + ['page_id', null, self::STUB_PAGE_ID], + ['id', null, self::STUB_PAGE_ID] ] ); - $this->cmsHelperMock->expects($this->once()) + $this->pageHelperMock->expects($this->once()) ->method('prepareResultPage') - ->with($this->controller, $this->pageId) + ->with($this->controller, self::STUB_PAGE_ID) ->willReturn($this->resultPageMock); $this->assertSame($this->resultPageMock, $this->controller->execute()); } @@ -98,8 +105,8 @@ public function testExecuteResultForward() ->method('getParam') ->willReturnMap( [ - ['page_id', $this->pageId, $this->pageId], - ['id', false, $this->pageId] + ['page_id', null, self::STUB_PAGE_ID], + ['id', null, self::STUB_PAGE_ID] ] ); $this->forwardMock->expects($this->once()) From 4fd4f99410b3fd10069c14f3ab38cd42a063c05f Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 16 Mar 2020 01:19:51 +0100 Subject: [PATCH 307/369] Fix failing Unit Test due to Page ID that can be INT --- app/code/Magento/Cms/Helper/Page.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Cms/Helper/Page.php b/app/code/Magento/Cms/Helper/Page.php index 501c2a5b051a2..24581cb9ee90e 100644 --- a/app/code/Magento/Cms/Helper/Page.php +++ b/app/code/Magento/Cms/Helper/Page.php @@ -158,7 +158,7 @@ public function __construct( public function prepareResultPage(ActionInterface $action, $pageId = null) { if ($pageId !== null && $pageId !== $this->_page->getId()) { - $delimiterPosition = strrpos($pageId, '|'); + $delimiterPosition = strrpos((string)$pageId, '|'); if ($delimiterPosition) { $pageId = substr($pageId, 0, $delimiterPosition); } From 4a7adebc2060d561626981ba65a66609d2d4ed40 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 16 Mar 2020 03:11:22 +0100 Subject: [PATCH 308/369] We are not ready for `strict_types` in this Class --- app/code/Magento/Cms/Helper/Page.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/code/Magento/Cms/Helper/Page.php b/app/code/Magento/Cms/Helper/Page.php index 24581cb9ee90e..d899a5cea985a 100644 --- a/app/code/Magento/Cms/Helper/Page.php +++ b/app/code/Magento/Cms/Helper/Page.php @@ -3,8 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -declare(strict_types=1); - namespace Magento\Cms\Helper; use Magento\Cms\Model\Page\CustomLayoutManagerInterface; From ebb1fae1439e178463092c38a2448a74942b9e3c Mon Sep 17 00:00:00 2001 From: DmytroPaidych <dimonovp@gmail.com> Date: Mon, 16 Mar 2020 10:17:26 +0200 Subject: [PATCH 309/369] MC-32128: Admin: create shipment for order --- ...th_two_order_items_with_simple_product.php | 88 +++++ ...der_items_with_simple_product_rollback.php | 36 ++ .../Save/CreateShipmentForOrderTest.php | 334 ++++++++++++++++++ 3 files changed, 458 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Sales/_files/order_with_two_order_items_with_simple_product.php create mode 100644 dev/tests/integration/testsuite/Magento/Sales/_files/order_with_two_order_items_with_simple_product_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save/CreateShipmentForOrderTest.php diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_two_order_items_with_simple_product.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_two_order_items_with_simple_product.php new file mode 100644 index 0000000000000..48f5d5dd99f80 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_two_order_items_with_simple_product.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\Data\OrderAddressInterfaceFactory; +use Magento\Sales\Api\Data\OrderInterfaceFactory; +use Magento\Sales\Api\Data\OrderItemInterfaceFactory; +use Magento\Sales\Api\Data\OrderPaymentInterfaceFactory; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Store\Model\StoreManagerInterface; + +require __DIR__ . '/../../../Magento/Customer/_files/customer.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/products.php'; + +$addressData = include __DIR__ . '/address_data.php'; + +/** @var OrderAddressInterfaceFactory $addressFactory */ +$addressFactory = $objectManager->get(OrderAddressInterfaceFactory::class); +/** @var OrderPaymentInterfaceFactory $paymentFactory */ +$paymentFactory = $objectManager->get(OrderPaymentInterfaceFactory::class); +/** @var OrderInterfaceFactory $orderFactory */ +$orderFactory = $objectManager->get(OrderInterfaceFactory::class); +/** @var OrderItemInterfaceFactory $orderItemFactory */ +$orderItemFactory = $objectManager->get(OrderItemInterfaceFactory::class); +/** @var StoreManagerInterface $storeManager */ +$storeManager = $objectManager->get(StoreManagerInterface::class); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); + +$billingAddress = $addressFactory->create(['data' => $addressData]); +$billingAddress->setAddressType(Address::TYPE_BILLING); +$shippingAddress = $addressFactory->create(['data' => $addressData]); +$shippingAddress->setAddressType(Address::TYPE_SHIPPING); +$payment = $paymentFactory->create(); +$payment->setMethod('checkmo')->setAdditionalInformation( + [ + 'last_trans_id' => '11122', + 'metadata' => [ + 'type' => 'free', + 'fraudulent' => false, + ] + ] +); + +$defaultStoreId = $storeManager->getStore('default')->getId(); +$order = $orderFactory->create(); +$order->setIncrementId('100000001') + ->setState(Order::STATE_PROCESSING) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)) + ->setSubtotal(20) + ->setGrandTotal(20) + ->setBaseSubtotal(20) + ->setBaseGrandTotal(20) + ->setCustomerIsGuest(false) + ->setCustomerId($customer->getId()) + ->setCustomerEmail($customer->getEmail()) + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setStoreId($defaultStoreId) + ->setPayment($payment); + +$orderItem = $orderItemFactory->create(); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(5) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType($product->getTypeId()) + ->setName($product->getName()) + ->setSku($product->getSku()); +$order->addItem($orderItem); + +$orderItem = $orderItemFactory->create(); +$orderItem->setProductId($customDesignProduct->getId()) + ->setQtyOrdered(5) + ->setBasePrice($customDesignProduct->getPrice()) + ->setPrice($customDesignProduct->getPrice()) + ->setRowTotal($customDesignProduct->getPrice()) + ->setProductType($customDesignProduct->getTypeId()) + ->setName($customDesignProduct->getName()) + ->setSku($customDesignProduct->getSku()); +$order->addItem($orderItem); +$orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_two_order_items_with_simple_product_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_two_order_items_with_simple_product_rollback.php new file mode 100644 index 0000000000000..b76b5178d3d25 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_two_order_items_with_simple_product_rollback.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +/** @var CollectionFactory $orderCollectionFactory */ +$orderCollectionFactory = $objectManager->get(CollectionFactory::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +$order = $orderCollectionFactory->create() + ->addFieldToFilter(OrderInterface::INCREMENT_ID, '100000001') + ->setPageSize(1) + ->getFirstItem(); +if ($order->getId()) { + $orderRepository->delete($order); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +require __DIR__ . '/../../../Magento/Customer/_files/customer_rollback.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/products_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save/CreateShipmentForOrderTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save/CreateShipmentForOrderTest.php new file mode 100644 index 0000000000000..20543f808c316 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save/CreateShipmentForOrderTest.php @@ -0,0 +1,334 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment\Save; + +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Message\MessageInterface; +use Magento\Shipping\Controller\Adminhtml\Order\Shipment\AbstractShipmentControllerTest; +use Magento\Framework\Escaper; + +/** + * Test cases related to check that shipment creates as expected or proper error message appear. + * + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + * + * @see \Magento\Shipping\Controller\Adminhtml\Order\Shipment\Save::execute + */ +class CreateShipmentForOrderTest extends AbstractShipmentControllerTest +{ + /** + * @var Escaper + */ + private $escaper; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->escaper = $this->_objectManager->get(Escaper::class); + } + + /** + * Assert that shipment created successfully. + * + * @magentoDataFixture Magento/Sales/_files/order_with_two_order_items_with_simple_product.php + * @dataProvider dataForCreateShipmentDataProvider + * + * @param array $dataForBuildPostData + * @param array $expectedShippedQtyBySku + * @return void + */ + public function testCreateOrderShipment(array $dataForBuildPostData, array $expectedShippedQtyBySku): void + { + $postData = $this->createPostData($dataForBuildPostData); + $order = $this->orderRepository->get($postData['order_id']); + $shipment = $this->getShipment($order); + $this->assertNull($shipment->getEntityId()); + $this->performShipmentCreationRequest($postData); + $this->assertCreateShipmentRequestSuccessfullyPerformed((int)$order->getEntityId()); + $createdShipment = $this->getShipment($order); + $this->assertNotNull($createdShipment->getEntityId()); + $shipmentItems = $createdShipment->getItems(); + $this->assertCount(count($expectedShippedQtyBySku), $shipmentItems); + foreach ($shipmentItems as $shipmentItem) { + $this->assertEquals($expectedShippedQtyBySku[$shipmentItem->getSku()], $shipmentItem->getQty()); + } + } + + /** + * Data for create full or partial order shipment POST data. + * + * @return array + */ + public function dataForCreateShipmentDataProvider(): array + { + return [ + 'create_full_shipment' => [ + [ + 'order_increment_id' => '100000001', + 'items_count_to_ship_by_sku' => [ + 'simple' => 5, + 'custom-design-simple-product' => 5, + ], + 'comment_text' => 'Create full shipment', + ], + [ + 'simple' => 5, + 'custom-design-simple-product' => 5, + ], + ], + 'create_partial_shipment' => [ + [ + 'order_increment_id' => '100000001', + 'items_count_to_ship_by_sku' => [ + 'simple' => 3, + 'custom-design-simple-product' => 2, + ], + 'comment_text' => 'Create partial shipment', + ], + [ + 'simple' => 3, + 'custom-design-simple-product' => 2, + ], + ], + 'create_shipment_for_one_of_product' => [ + [ + 'order_increment_id' => '100000001', + 'items_count_to_ship_by_sku' => [ + 'simple' => 4, + 'custom-design-simple-product' => 0, + ], + 'comment_text' => 'Create partial shipment', + ], + [ + 'simple' => 4, + ], + ], + 'create_shipment_qty_more_that_ordered' => [ + [ + 'order_increment_id' => '100000001', + 'items_count_to_ship_by_sku' => [ + 'simple' => 150, + 'custom-design-simple-product' => 5, + ], + 'comment_text' => 'Create partial shipment', + ], + [ + 'simple' => 5, + 'custom-design-simple-product' => 5, + ], + ], + 'create_shipment_qty_less_that_zero' => [ + [ + 'order_increment_id' => '100000001', + 'items_count_to_ship_by_sku' => [ + 'simple' => -10, + 'custom-design-simple-product' => 5, + ], + 'comment_text' => 'Create partial shipment', + ], + [ + 'custom-design-simple-product' => 5, + ], + ], + 'create_shipment_without_counts' => [ + [ + 'order_increment_id' => '100000001', + 'items_count_to_ship_by_sku' => [], + 'comment_text' => 'Create partial shipment', + ], + [ + 'simple' => 5, + 'custom-design-simple-product' => 5, + ], + ], + ]; + } + + /** + * Assert that body contains 404 error and forwarded to no-route if + * requested POST data is empty. + * + * @magentoDataFixture Magento/Sales/_files/order_with_customer.php + * + * @return void + */ + public function testCreateOrderShipmentWithoutPostData(): void + { + $this->performShipmentCreationRequest([]); + $this->assert404NotFound(); + } + + /** + * Assert that if we doesn't send order_id it will forwarded to no-rout and contains error 404. + * + * @magentoDataFixture Magento/Sales/_files/order_with_customer.php + * + * @return void + */ + public function testCreateOrderShipmentWithoutOrderId(): void + { + $dataToSend = [ + 'order_increment_id' => '100000001', + 'items_count_to_ship_by_sku' => [ + 'simple' => 2, + ], + 'comment_text' => 'Create full shipment', + ]; + $postData = $this->createPostData($dataToSend); + unset($postData['order_id']); + $this->performShipmentCreationRequest($postData); + $this->assert404NotFound(); + } + + /** + * Assert that if we send POST data with wrong order item ids we got error message. + * + * @magentoDataFixture Magento/Sales/_files/order_with_customer.php + * + * @return void + */ + public function testCreateOrderShipmentWithWrongItemsIds(): void + { + $orderId = $this->getOrder('100000001')->getEntityId(); + $postData = [ + 'order_id' => $orderId, + 'shipment' => + [ + 'items' => [ + 678678 => 4 + ], + ], + ]; + $this->performShipmentCreationRequest($postData); + $this->assertCreateShipmentRequestWithError( + "Shipment Document Validation Error(s):\nYou can't create a shipment without products.", + "admin/order_shipment/new/order_id/{$orderId}" + ); + } + + /** + * Assert that if we send POST data with alphabet order item count we got error message. + * + * @magentoDataFixture Magento/Sales/_files/order_with_customer.php + * + * @return void + */ + public function testCreateOrderShipmentWithAlphabetQty(): void + { + $dataToSend = [ + 'order_increment_id' => '100000001', + 'items_count_to_ship_by_sku' => [ + 'simple' => 'test_letters', + ], + 'comment_text' => 'Create full shipment', + ]; + $postData = $this->createPostData($dataToSend); + $this->performShipmentCreationRequest($postData); + $this->assertCreateShipmentRequestWithError( + "Shipment Document Validation Error(s):\nYou can't create a shipment without products.", + "admin/order_shipment/new/order_id/{$postData['order_id']}" + ); + } + + /** + * @inheritdoc + */ + public function assert404NotFound() + { + $this->assertEquals('noroute', $this->getRequest()->getControllerName()); + $this->assertContains('404 Error', $this->getResponse()->getBody()); + $this->assertContains('Page not found', $this->getResponse()->getBody()); + } + + /** + * Set POST data and type POST to request + * and perform request by path backend/admin/order_shipment/save. + * + * @param array $postData + * @return void + */ + private function performShipmentCreationRequest(array $postData): void + { + $this->getRequest()->setPostValue($postData) + ->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('backend/admin/order_shipment/save'); + } + + /** + * Assert that after create shipment session message manager + * contain message "The shipment has been created." and redirect to sales/order/view is created. + * + * @param int $orderId + * @return void + */ + private function assertCreateShipmentRequestSuccessfullyPerformed(int $orderId): void + { + $this->assertSessionMessages( + $this->equalTo([(string)__('The shipment has been created.')]), + MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringContains('sales/order/view/order_id/' . $orderId)); + } + + /** + * Assert that session message manager contain provided error message and + * redirect created if it was provided. + * + * @param string $message + * @param string|null $redirect + * @param bool $isNeedEscapeMessage + * @return void + */ + private function assertCreateShipmentRequestWithError( + string $message, + ?string $redirect = null, + bool $isNeedEscapeMessage = true + ): void { + $assertMessage = $isNeedEscapeMessage + ? $this->escaper->escapeHtml((string)__($message)) : (string)__($message); + $this->assertSessionMessages( + $this->equalTo([$assertMessage]), + MessageInterface::TYPE_ERROR + ); + $this->assertRedirect($this->stringContains($redirect)); + } + + /** + * Create POST data for create shipment for order. + * + * @param array $dataForBuildPostData + * @return array + */ + private function createPostData(array $dataForBuildPostData): array + { + $result = []; + $order = $this->getOrder($dataForBuildPostData['order_increment_id']); + $result['order_id'] = (int)$order->getEntityId(); + $shipment = []; + $shipment['items'] = []; + foreach ($order->getItems() as $orderItem) { + if (!isset($dataForBuildPostData['items_count_to_ship_by_sku'][$orderItem->getSku()])) { + continue; + } + $shipment['items'][$orderItem->getItemId()] + = $dataForBuildPostData['items_count_to_ship_by_sku'][$orderItem->getSku()]; + } + if (empty($shipment['items'])) { + unset($shipment['items']); + } + + $shipment['comment_text'] = $dataForBuildPostData['comment_text']; + $result['shipment'] = $shipment; + + return $result; + } +} From 5932b5904fe7460ad687c96107399089219a3df6 Mon Sep 17 00:00:00 2001 From: DmytroPaidych <dimonovp@gmail.com> Date: Mon, 16 Mar 2020 12:24:06 +0200 Subject: [PATCH 310/369] MC-32134: Storefront: Customer wish list on customer profile --- .../Model/GetWishlistByCustomerId.php | 61 +++++ .../Wishlist/Block/Customer/SharingTest.php | 61 +++++ .../Customer/Wishlist/Item/ColumnTest.php | 124 +++++++-- .../Wishlist/Block/Customer/WishlistTest.php | 39 ++- .../Wishlist/Controller/Index/AddTest.php | 189 ++++++++++++++ .../Wishlist/Controller/Index/AllcartTest.php | 110 ++++++++ .../Wishlist/Controller/Index/CartTest.php | 120 +++++++++ .../Wishlist/Controller/Index/IndexTest.php | 82 ++++++ .../Wishlist/Controller/Index/PluginTest.php | 45 +++- .../Wishlist/Controller/Index/RemoveTest.php | 79 ++++++ .../Wishlist/Controller/Index/SendTest.php | 180 +++++++++++++ .../Index/UpdateItemOptionsTest.php | 198 ++++++++++++++ .../Wishlist/Controller/Index/UpdateTest.php | 98 +++++++ .../Magento/Wishlist/Controller/IndexTest.php | 190 -------------- .../Magento/Wishlist/Model/ItemTest.php | 168 +++++++++--- .../Magento/Wishlist/Model/WishlistTest.php | 246 ++++++++++++++---- .../_files/wishlist_shared_rollback.php | 9 + .../wishlist_with_configurable_product.php | 17 ++ ...ist_with_configurable_product_rollback.php | 10 + .../_files/wishlist_with_simple_product.php | 16 ++ .../wishlist_with_simple_product_rollback.php | 9 + 21 files changed, 1735 insertions(+), 316 deletions(-) create mode 100644 dev/tests/integration/framework/Magento/TestFramework/Wishlist/Model/GetWishlistByCustomerId.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/SharingTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/AddTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/AllcartTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/CartTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/IndexTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/RemoveTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/SendTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/UpdateItemOptionsTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/UpdateTest.php delete mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_shared_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_configurable_product.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_configurable_product_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_simple_product.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_simple_product_rollback.php diff --git a/dev/tests/integration/framework/Magento/TestFramework/Wishlist/Model/GetWishlistByCustomerId.php b/dev/tests/integration/framework/Magento/TestFramework/Wishlist/Model/GetWishlistByCustomerId.php new file mode 100644 index 0000000000000..93f170466cb40 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Wishlist/Model/GetWishlistByCustomerId.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Wishlist\Model; + +use Magento\Wishlist\Model\Item; +use Magento\Wishlist\Model\Wishlist; +use Magento\Wishlist\Model\WishlistFactory; + +/** + * Load wish list by customer id. + */ +class GetWishlistByCustomerId +{ + /** @var WishlistFactory */ + private $wishlistFactory; + + /** + * @param WishlistFactory $wishlistFactory + */ + public function __construct(WishlistFactory $wishlistFactory) + { + $this->wishlistFactory = $wishlistFactory; + } + + /** + * Load wish list by customer id. + * + * @param int $customerId + * @return Wishlist + */ + public function execute(int $customerId): Wishlist + { + return $this->wishlistFactory->create()->loadByCustomerId($customerId, true); + } + + /** + * Get wish list item by sku. + * + * @param int $customerId + * @param string $sku + * @return null|Item + */ + public function getItemBySku(int $customerId, string $sku): ?Item + { + $result = null; + $items = $this->execute($customerId)->getItemCollection()->getItems(); + foreach ($items as $item) { + if ($item->getProduct()->getData('sku') === $sku) { + $result = $item; + break; + } + } + + return $result; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/SharingTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/SharingTest.php new file mode 100644 index 0000000000000..a96a7e9b4c7b3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/SharingTest.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Block\Customer; + +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; + +/** + * Class test share wish list block. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + */ +class SharingTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Sharing */ + private $block; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->block = $this->objectManager->get(LayoutInterface::class)->createBlock(Sharing::class); + } + + /** + * @return void + */ + public function testDisplayWishListSharingForm(): void + { + $elementsXpath = [ + 'Emails input' => "//form[contains(@class, 'share')]//textarea[@name='emails' and @id='email_address']", + 'Message input' => "//form[contains(@class, 'share')]//textarea[@name='message' and @id='message']", + 'Share button' => "//form[contains(@class, 'share')]//button[contains(@class, 'submit')]" + . "/span[contains(text(), 'Share Wish List')]", + ]; + $blockHtml = $this->block->setTemplate('Magento_Wishlist::sharing.phtml')->toHtml(); + foreach ($elementsXpath as $element => $xpath) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($xpath, $blockHtml), + sprintf("%s was not found.", $element) + ); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/Wishlist/Item/ColumnTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/Wishlist/Item/ColumnTest.php index 668aec616533c..ffce4354dd097 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/Wishlist/Item/ColumnTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/Wishlist/Item/ColumnTest.php @@ -3,44 +3,130 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Wishlist\Block\Customer\Wishlist\Item; -class ColumnTest extends \PHPUnit\Framework\TestCase +use Magento\Customer\Model\Session; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\Element\Text; +use Magento\Framework\View\LayoutInterface; +use Magento\Framework\View\Result\PageFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; +use Magento\Wishlist\Block\Customer\Wishlist\Items; +use PHPUnit\Framework\TestCase; + +/** + * Test wish list item column. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ColumnTest extends TestCase { + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Session */ + private $customerSession; + + /** @var LayoutInterface */ + private $layout; + + /** @var Column */ + private $block; + + /** @var GetWishlistByCustomerId */ + private $getWishlistItemsByCustomerId; + /** - * @var \Magento\Framework\View\LayoutInterface + * @inheritdoc */ - protected $_layout = null; + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->customerSession = $this->objectManager->get(Session::class); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->block = $this->layout->addBlock(Column::class, 'test'); + $this->layout->addBlock(Text::class, 'child', 'test'); + $this->getWishlistItemsByCustomerId = $this->objectManager->get(GetWishlistByCustomerId::class); + } /** - * @var \Magento\Wishlist\Block\Customer\Wishlist\Item\Column + * @inheritdoc */ - protected $_block = null; - - protected function setUp() + protected function tearDown() { - $this->_layout = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Framework\View\LayoutInterface::class - ); - $this->_block = $this->_layout->addBlock(\Magento\Wishlist\Block\Customer\Wishlist\Item\Column::class, 'test'); - $this->_layout->addBlock(\Magento\Framework\View\Element\Text::class, 'child', 'test'); + $this->customerSession->setCustomerId(null); + + parent::tearDown(); } /** * @magentoAppIsolation enabled + * + * @return void */ - public function testToHtml() + public function testToHtml(): void { $item = new \StdClass(); - $this->_block->setItem($item); - $this->_block->toHtml(); - $this->assertSame($item, $this->_layout->getBlock('child')->getItem()); + $this->block->setItem($item); + $this->block->toHtml(); + $this->assertSame($item, $this->layout->getBlock('child')->getItem()); } - public function testGetJs() + /** + * @return void + */ + public function testGetJs(): void { $expected = uniqid(); - $this->_layout->getBlock('child')->setJs($expected); - $this->assertEquals($expected, $this->_block->getJs()); + $this->layout->getBlock('child')->setJs($expected); + $this->assertEquals($expected, $this->block->getJs()); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + * + * @return void + */ + public function testWishListItemButtons(): void + { + $buttons = [ + "Add to Cart button" => "//button[contains(@class, 'tocart')]/span[contains(text(), 'Add to Cart')]", + "Edit button" => "//a[contains(@class, 'edit')]/span[contains(text(), 'Edit')]", + "Remove item button" => "//a[contains(@class, 'delete')]/span[contains(text(), 'Remove item')]", + ]; + $item = $this->getWishlistItemsByCustomerId->getItemBySku(1, 'simple'); + $this->assertNotNull($item); + $block = $this->getWishListItemsBlock()->getChildBlock('customer.wishlist.item.inner'); + $blockHtml = $block->setItem($item)->toHtml(); + foreach ($buttons as $buttonName => $xpath) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($xpath, $blockHtml), + sprintf("%s wasn't found.", $buttonName) + ); + } + } + + /** + * Get wish list items block. + * + * @return Items + */ + private function getWishListItemsBlock(): Items + { + $page = $this->objectManager->create(PageFactory::class)->create(); + $page->addHandle([ + 'default', + 'wishlist_index_index', + ]); + $page->getLayout()->generateXml(); + + return $page->getLayout()->getBlock('customer.wishlist.items'); } } diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/WishlistTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/WishlistTest.php index 944d2ac6faada..36cd7fe3e4c89 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/WishlistTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Block/Customer/WishlistTest.php @@ -15,7 +15,7 @@ use PHPUnit\Framework\TestCase; /** - * Class test my wish list on customer account page. + * Class test block wish list on customer account page. * * @magentoAppArea frontend * @magentoDbIsolation enabled @@ -66,9 +66,11 @@ protected function tearDown() public function testDisplayNumberOfItemsInWishList(): void { $this->customerSession->setCustomerId(1); + $pagerBlockHtml = $this->getWishListBlock()->getChildBlock('wishlist_item_pager')->toHtml(); $this->assertEquals( 1, - Xpath::getElementsCountForXpath(sprintf(self::ITEMS_COUNT_XPATH, 1), $this->getWishListPagerBlockHtml()) + Xpath::getElementsCountForXpath(sprintf(self::ITEMS_COUNT_XPATH, 1), $pagerBlockHtml), + "Element items count wasn't found." ); } @@ -82,27 +84,46 @@ public function testDisplayItemQuantitiesInWishList(): void { $this->markTestSkipped('Test is blocked by issue MC-31595'); $this->customerSession->setCustomerId(1); + $pagerBlockHtml = $this->getWishListBlock()->getChildBlock('wishlist_item_pager')->toHtml(); $this->assertEquals( 1, - Xpath::getElementsCountForXpath(sprintf(self::ITEMS_COUNT_XPATH, 3), $this->getWishListPagerBlockHtml()) + Xpath::getElementsCountForXpath(sprintf(self::ITEMS_COUNT_XPATH, 3), $pagerBlockHtml), + "Element items count wasn't found." ); } /** - * Get wish list pager block html. + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php * - * @return string + * @return void + */ + public function testDisplayActionButtonsInWishList(): void + { + $buttonsXpath = [ + "//button[contains(@class, 'update') and @type='submit']/span[contains(text(), 'Update Wish List')]", + "//button[contains(@class, 'share') and @type='submit']/span[contains(text(), 'Share Wish List')]", + "//button[contains(@class, 'tocart') and @type='button']/span[contains(text(), 'Add All to Cart')]", + ]; + $this->customerSession->setCustomerId(1); + $blockHtml = $this->getWishListBlock()->toHtml(); + foreach ($buttonsXpath as $xpath) { + $this->assertEquals(1, Xpath::getElementsCountForXpath($xpath, $blockHtml)); + } + } + + /** + * Get wish list block. + * + * @return Wishlist */ - private function getWishListPagerBlockHtml(): string + private function getWishListBlock(): Wishlist { $this->page->addHandle([ 'default', 'wishlist_index_index', ]); $this->page->getLayout()->generateXml(); - /** @var Wishlist $customerWishlistBlock */ - $customerWishlistBlock = $this->page->getLayout()->getBlock('customer.wishlist'); - return $customerWishlistBlock->getChildBlock('wishlist_item_pager')->toHtml(); + return $this->page->getLayout()->getBlock('customer.wishlist'); } } diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/AddTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/AddTest.php new file mode 100644 index 0000000000000..82ae8e92d2979 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/AddTest.php @@ -0,0 +1,189 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Controller\Index; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Escaper; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractController; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; +use Zend\Stdlib\Parameters; + +/** + * Test for add product to wish list. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + * @magentoDataFixture Magento/Customer/_files/customer.php + */ +class AddTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** @var GetWishlistByCustomerId */ + private $getWishlistByCustomerId; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var Escaper */ + private $escaper; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->getWishlistByCustomerId = $this->_objectManager->get(GetWishlistByCustomerId::class); + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->escaper = $this->_objectManager->get(Escaper::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_xss.php + * + * @return void + */ + public function testAddActionProductNameXss(): void + { + $this->prepareReferer(); + $this->customerSession->setCustomerId(1); + $product = $this->productRepository->get('product-with-xss'); + $escapedProductName = $this->escaper->escapeHtml($product->getName()); + $this->performAddToWishListRequest(['product' => $product->getId()]); + $this->assertSuccess(1, 1, $escapedProductName); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_configurable_product.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testAddConfigurableProductToWishList(): void + { + $this->prepareReferer(); + $this->customerSession->setCustomerId(1); + $product = $this->productRepository->get('Configurable product'); + $this->performAddToWishListRequest(['product' => $product->getId()]); + $this->assertSuccess(1, 1, $product->getName()); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Catalog/_files/multiple_products.php + * + * @return void + */ + public function testAddDisabledProductToWishList(): void + { + $expectedMessage = $this->escaper->escapeHtml("We can't specify a product."); + $this->customerSession->setCustomerId(1); + $product = $this->productRepository->get('simple3'); + $this->performAddToWishListRequest(['product' => $product->getId()]); + $this->assertSessionMessages($this->equalTo([(string)__($expectedMessage)]), MessageInterface::TYPE_ERROR); + $this->assertRedirect($this->stringContains('wishlist/')); + } + + /** + * @return void + */ + public function testAddToWishListWithoutParams(): void + { + $this->customerSession->setCustomerId(1); + $this->performAddToWishListRequest([]); + $this->assertSessionMessages($this->isEmpty(), MessageInterface::TYPE_ERROR); + $this->assertSessionMessages($this->isEmpty(), MessageInterface::TYPE_SUCCESS); + $this->assertRedirect($this->stringContains('wishlist/')); + } + + /** + * @return void + */ + public function testAddNotExistingProductToWishList(): void + { + $this->customerSession->setCustomerId(1); + $expectedMessage = $this->escaper->escapeHtml("We can't specify a product."); + $this->performAddToWishListRequest(['product' => 989]); + $this->assertSessionMessages($this->equalTo([(string)__($expectedMessage)]), MessageInterface::TYPE_ERROR); + $this->assertRedirect($this->stringContains('wishlist/')); + } + + /** + * @return void + */ + public function testAddToNotExistingWishList(): void + { + $expectedMessage = $this->escaper->escapeHtml("The requested Wish List doesn't exist."); + $this->customerSession->setCustomerId(1); + $this->performAddToWishListRequest(['wishlist_id' => 989]); + $this->assertSessionMessages($this->equalTo([(string)__($expectedMessage)]), MessageInterface::TYPE_ERROR); + $this->assert404NotFound(); + } + + /** + * Perform request add item to wish list. + * + * @param array $params + * @return void + */ + private function performAddToWishListRequest(array $params): void + { + $this->getRequest()->setParams($params)->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('wishlist/index/add'); + } + + /** + * Assert success response and items count. + * + * @param int $customerId + * @param int $itemsCount + * @param string $productName + * @return void + */ + private function assertSuccess(int $customerId, int $itemsCount, string $productName): void + { + $expectedMessage = sprintf("\n%s has been added to your Wish List.", $productName) + . " Click <a href=\"http://localhost/test\">here</a> to continue shopping."; + $this->assertSessionMessages($this->equalTo([(string)__($expectedMessage)]), MessageInterface::TYPE_SUCCESS); + $wishlist = $this->getWishlistByCustomerId->execute($customerId); + $this->assertCount($itemsCount, $wishlist->getItemCollection()); + $this->assertRedirect($this->stringContains('wishlist/index/index/wishlist_id/' . $wishlist->getId())); + } + + /** + * Prepare referer to test. + * + * @return void + */ + private function prepareReferer(): void + { + $parameters = $this->_objectManager->create(Parameters::class); + $parameters->set('HTTP_REFERER', 'http://localhost/test'); + $this->getRequest()->setServer($parameters); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/AllcartTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/AllcartTest.php new file mode 100644 index 0000000000000..bc589c2791eb5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/AllcartTest.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Controller\Index; + +use Magento\Checkout\Model\CartFactory; +use Magento\Checkout\Model\Cart as CartModel; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Escaper; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractController; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; + +/** + * Test for add all products to cart from wish list. + * + * @magentoDbIsolation disabled + * @magentoAppArea frontend + */ +class AllcartTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** @var CartModel */ + private $cart; + + /** @var Escaper */ + private $escaper; + + /** @var GetWishlistByCustomerId */ + private $getWishlistByCustomerId; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->cart = $this->_objectManager->get(CartFactory::class)->create(); + $this->escaper = $this->_objectManager->get(Escaper::class); + $this->getWishlistByCustomerId = $this->_objectManager->get(GetWishlistByCustomerId::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_product_qty_increments.php + * + * @return void + */ + public function testAddProductQtyIncrementToCartFromWishList(): void + { + $this->customerSession->setCustomerId(1); + $this->performAddAllToCartRequest(); + $wishlistCollection = $this->getWishlistByCustomerId->execute(1)->getItemCollection(); + $this->assertCount(1, $wishlistCollection); + $this->assertCount(0, $this->cart->getQuote()->getItemsCollection()); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple'); + $this->assertNotNull($item); + $expectedMessage = $this->escaper->escapeHtml( + sprintf('You can buy this product only in quantities of 5 at a time for "%s".', $item->getName()) + ); + $this->assertSessionMessages($this->equalTo([(string)__($expectedMessage)]), MessageInterface::TYPE_ERROR); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_simple_product.php + * + * @return void + */ + public function testAddAllProductToCartFromWishList(): void + { + $this->customerSession->setCustomerId(1); + $this->performAddAllToCartRequest(); + $quoteCollection = $this->cart->getQuote()->getItemsCollection(); + $this->assertCount(1, $quoteCollection); + $item = $quoteCollection->getFirstItem(); + $expectedMessage = $this->escaper->escapeHtml( + sprintf('1 product(s) have been added to shopping cart: "%s".', $item->getName()) + ); + $this->assertSessionMessages($this->equalTo([(string)__($expectedMessage)]), MessageInterface::TYPE_SUCCESS); + } + + /** + * Perform add all products to cart from wish list request. + * + * @return void + */ + private function performAddAllToCartRequest(): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('wishlist/index/allcart'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/CartTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/CartTest.php new file mode 100644 index 0000000000000..3681409e40156 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/CartTest.php @@ -0,0 +1,120 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Controller\Index; + +use Magento\Checkout\Model\CartFactory; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Escaper; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractController; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; + +/** + * Test for add product to cart from wish list. + * + * @magentoDbIsolation disabled + * @magentoAppArea frontend + */ +class CartTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** @var GetWishlistByCustomerId */ + private $getWishlistByCustomerId; + + /** @var CartFactory */ + private $cartFactory; + + /** @var Escaper */ + private $escaper; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->getWishlistByCustomerId = $this->_objectManager->get(GetWishlistByCustomerId::class); + $this->cartFactory = $this->_objectManager->get(CartFactory::class); + $this->escaper = $this->_objectManager->get(Escaper::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_simple_product.php + * + * @return void + */ + public function testAddSimpleProductToCart(): void + { + $this->customerSession->setCustomerId(1); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple-1'); + $this->assertNotNull($item); + $this->performAddToCartRequest(['item' => $item->getId(), 'qty' => 3]); + $message = sprintf('You added %s to your shopping cart.', $item->getName()); + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_SUCCESS); + $this->assertCount(0, $this->getWishlistByCustomerId->execute(1)->getItemCollection()); + $cart = $this->cartFactory->create(); + $this->assertEquals(1, $cart->getItemsCount()); + $this->assertEquals(3, $cart->getItemsQty()); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_configurable_product.php + * + * @return void + */ + public function testAddItemWithNotChosenOptionToCart(): void + { + $this->customerSession->setCustomerId(1); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'Configurable product'); + $this->assertNotNull($item); + $this->performAddToCartRequest(['item' => $item->getId(), 'qty' => 1]); + $redirectUrl = sprintf("wishlist/index/configure/id/%s/product_id/%s", $item->getId(), $item->getProductId()); + $this->assertRedirect($this->stringContains($redirectUrl)); + $message = 'You need to choose options for your item.'; + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_NOTICE); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testAddNotExistingItemToCart(): void + { + $this->customerSession->setCustomerId(1); + $this->performAddToCartRequest(['item' => 989]); + $this->assertRedirect($this->stringContains('wishlist/index/')); + } + + /** + * Perform request add to cart from wish list. + * + * @param array $params + * @return void + */ + private function performAddToCartRequest(array $params): void + { + $this->getRequest()->setParams($params)->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('wishlist/index/cart'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/IndexTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/IndexTest.php new file mode 100644 index 0000000000000..b92b2f3e5e85b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/IndexTest.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Controller\Index; + +use Magento\Customer\Model\Session; +use Magento\TestFramework\Helper\Xpath; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Test wish list on customer account page. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + */ +class IndexTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * Verify wishlist view action + * + * The following is verified: + * - \Magento\Wishlist\Model\ResourceModel\Item\Collection + * - \Magento\Wishlist\Block\Customer\Wishlist + * - \Magento\Wishlist\Block\Customer\Wishlist\Items + * - \Magento\Wishlist\Block\Customer\Wishlist\Item\Column + * - \Magento\Wishlist\Block\Customer\Wishlist\Item\Column\Cart + * - \Magento\Wishlist\Block\Customer\Wishlist\Item\Column\Comment + * - \Magento\Wishlist\Block\Customer\Wishlist\Button + * - that \Magento\Wishlist\Block\Customer\Wishlist\Item\Options doesn't throw a fatal error + * + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + * + * @return void + */ + public function testItemColumnBlock(): void + { + $this->customerSession->setCustomerId(1); + $this->dispatch('wishlist/index/index'); + $body = $this->getResponse()->getBody(); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + '//img[contains(@src, "small_image.jpg") and @alt = "Simple Product"]', + $body + ) + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + '//textarea[contains(@name, "description")]', + $body + ) + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/PluginTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/PluginTest.php index 5303c9f352b87..7193791bdbe6c 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/PluginTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/PluginTest.php @@ -7,12 +7,17 @@ namespace Magento\Wishlist\Controller\Index; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Message\MessageInterface; use Magento\TestFramework\TestCase\AbstractController; use Magento\Customer\Model\Session as CustomerSession; use Magento\Catalog\Api\ProductRepositoryInterface; /** * Test for wishlist plugin before dispatch + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend */ class PluginTest extends AbstractController { @@ -21,13 +26,20 @@ class PluginTest extends AbstractController */ private $customerSession; + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + /** * @inheritdoc */ protected function setUp() { parent::setUp(); + $this->customerSession = $this->_objectManager->get(CustomerSession::class); + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); } /** @@ -37,20 +49,22 @@ protected function tearDown() { $this->customerSession->logout(); $this->customerSession = null; + parent::tearDown(); } /** * Test for adding product to wishlist with invalidate credentials * - * @return void * @magentoDataFixture Magento/Catalog/_files/product_simple_xss.php * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoAppArea frontend + * + * @return void */ public function testAddActionProductWithInvalidCredentials(): void { - $this->getRequest()->setMethod('POST'); + $product = $this->productRepository->get('product-with-xss'); + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue( [ 'login' => [ @@ -59,14 +73,23 @@ public function testAddActionProductWithInvalidCredentials(): void ], ] ); - - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); - - $product = $productRepository->get('product-with-xss'); - - $this->dispatch('wishlist/index/add/product/' . $product->getId() . '?nocookie=1'); - + $this->getRequest()->setParams(['product' => $product->getId(), 'nocookie' => 1]); + $this->dispatch('wishlist/index/add'); $this->assertArrayNotHasKey('login', $this->customerSession->getBeforeWishlistRequest()); + $expectedMessage = 'You must login or register to add items to your wishlist.'; + $this->assertSessionMessages($this->equalTo([(string)__($expectedMessage)]), MessageInterface::TYPE_ERROR); + } + + /** + * @magentoConfigFixture current_store wishlist/general/active 0 + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testWithDisabledWishList(): void + { + $this->customerSession->setCustomerId(1); + $this->dispatch('wishlist/index/index'); + $this->assert404NotFound(); } } diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/RemoveTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/RemoveTest.php new file mode 100644 index 0000000000000..5bd8d006e5fe5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/RemoveTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Controller\Index; + +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractController; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; + +/** + * Test for remove product from wish list. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + */ +class RemoveTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** @var GetWishlistByCustomerId */ + private $getWishlistByCustomerId; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->getWishlistByCustomerId = $this->_objectManager->get(GetWishlistByCustomerId::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @return void + */ + public function testRemoveProductFromWishList(): void + { + $customerId = 1; + $this->customerSession->setCustomerId($customerId); + $item = $this->getWishlistByCustomerId->getItemBySku($customerId, 'simple'); + $this->assertNotNull($item); + $productName = $item->getProduct()->getName(); + $this->getRequest()->setParam('item', $item->getId())->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('wishlist/index/remove'); + $message = sprintf("\n%s has been removed from your Wish List.\n", $productName); + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_SUCCESS); + $this->assertCount(0, $this->getWishlistByCustomerId->execute($customerId)->getItemCollection()); + } + + /** + * @return void + */ + public function testRemoveNotExistingItemFromWishList(): void + { + $this->customerSession->setCustomerId(1); + $this->getRequest()->setParams(['item' => 989])->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('wishlist/index/remove'); + $this->assert404NotFound(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/SendTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/SendTest.php new file mode 100644 index 0000000000000..6a89be8bdb3b0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/SendTest.php @@ -0,0 +1,180 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Controller\Index; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Api\CustomerNameGenerationInterface; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\Helper\Xpath; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Test sending wish list. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + */ +class SendTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** @var CustomerNameGenerationInterface */ + private $customerNameGeneration; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var TransportBuilderMock */ + private $transportBuilder; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->customerNameGeneration = $this->_objectManager->get(CustomerNameGenerationInterface::class); + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @return void + */ + public function testSendWishList(): void + { + $product = $this->productRepository->get('simple'); + $this->customerSession->setCustomerId(1); + $shareMessage = 'Here\'s what I want for my birthday.'; + $postValues = ['emails' => 'test@example.com', 'message' => $shareMessage]; + $this->dispatchSendWishListRequest($postValues); + $this->assertSessionMessages( + $this->equalTo([(string)__('Your wish list has been shared.')]), + MessageInterface::TYPE_SUCCESS + ); + $this->assertNotNull($this->transportBuilder->getSentMessage()); + $messageContent = $this->transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent(); + $this->assertContains($shareMessage, $messageContent); + $this->assertContains( + sprintf( + '%s wants to share this Wish List', + $this->customerNameGeneration->getCustomerName($this->customerSession->getCustomerDataObject()) + ), + $messageContent + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf( + "//a[contains(@href, '%s')]/strong[contains(text(), '%s')]", + $product->getProductUrl(), + $product->getName() + ), + $messageContent + ) + ); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + "//a[contains(@href, 'wishlist/shared/index/code/fixture_unique_code/')" + . " and contains(text(), 'View all Wish List')]", + $messageContent + ) + ); + } + + /** + * @magentoConfigFixture current_store wishlist/email/number_limit 2 + * + * @return void + */ + public function testSendWishListWithEmailsLimit(): void + { + $this->customerSession->setCustomerId(1); + $postValues = ['emails' => 'test@example.com, test2@example.com, test3@example.com']; + $this->dispatchSendWishListRequest($postValues); + $this->assertResponseWithError('Maximum of 2 emails can be sent.'); + } + + /** + * @magentoConfigFixture current_store wishlist/email/text_limit 10 + * + * @return void + */ + public function testSendWishListWithTextLimit(): void + { + $this->customerSession->setCustomerId(1); + $postValues = ['emails' => 'test@example.com', 'message' => 'Test message']; + $this->dispatchSendWishListRequest($postValues); + $this->assertResponseWithError('Message length must not exceed 10 symbols'); + } + + /** + * @return void + */ + public function testSendWishListWithoutEmails(): void + { + $this->customerSession->setCustomerId(1); + $postValues = ['emails' => '']; + $this->dispatchSendWishListRequest($postValues); + $this->assertResponseWithError('Please enter an email address.'); + } + + /** + * @return void + */ + public function testSendWishListWithInvalidEmail(): void + { + $this->customerSession->setCustomerId(1); + $postValues = ['emails' => 'test @example.com']; + $this->dispatchSendWishListRequest($postValues); + $this->assertResponseWithError('Please enter a valid email address.'); + } + + /** + * Dispatch send wish list request. + * + * @param array $postValues + * @return void + */ + private function dispatchSendWishListRequest(array $postValues): void + { + $this->getRequest()->setPostValue($postValues)->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('wishlist/index/send'); + } + + /** + * Assert error message and redirect. + * + * @param string $message + * @return void + */ + private function assertResponseWithError(string $message): void + { + $this->assertSessionMessages($this->equalTo([__($message)]), MessageInterface::TYPE_ERROR); + $this->assertRedirect($this->stringContains('wishlist/index/share')); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/UpdateItemOptionsTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/UpdateItemOptionsTest.php new file mode 100644 index 0000000000000..40646be11f684 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/UpdateItemOptionsTest.php @@ -0,0 +1,198 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Controller\Index; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Escaper; +use Magento\Framework\Message\MessageInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\TestFramework\TestCase\AbstractController; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; +use Magento\Wishlist\Model\Item; + +/** + * Test for update wish list item. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + */ +class UpdateItemOptionsTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var Escaper */ + private $escaper; + + /** @var SerializerInterface */ + private $json; + + /** @var GetWishlistByCustomerId */ + private $getWishlistByCustomerId; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->getWishlistByCustomerId = $this->_objectManager->get(GetWishlistByCustomerId::class); + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->escaper = $this->_objectManager->get(Escaper::class); + $this->json = $this->_objectManager->get(SerializerInterface::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_configurable_product.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testUpdateItemOptions(): void + { + $this->customerSession->setCustomerId(1); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'Configurable product'); + $this->assertNotNull($item); + $params = [ + 'id' => $item->getId(), + 'product' => $item->getProductId(), + 'super_attribute' => $this->performConfigurableOption($item->getProduct()), + 'qty' => 5, + ]; + $this->performUpdateWishListItemRequest($params); + $message = sprintf("%s has been updated in your Wish List.", $item->getProduct()->getName()); + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_SUCCESS); + $this->assertRedirect($this->stringContains('wishlist/index/index/wishlist_id/' . $item->getWishlistId())); + $this->assertUpdatedItem( + $this->getWishlistByCustomerId->getItemBySku(1, 'Configurable product'), + $params + ); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testUpdateItemOptionsWithoutParams(): void + { + $this->customerSession->setCustomerId(1); + $this->performUpdateWishListItemRequest([]); + $this->assertSessionMessages($this->isEmpty(), MessageInterface::TYPE_ERROR); + $this->assertSessionMessages($this->isEmpty(), MessageInterface::TYPE_SUCCESS); + $this->assertRedirect($this->stringContains('wishlist/')); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testUpdateNotExistingItem(): void + { + $this->customerSession->setCustomerId(1); + $this->performUpdateWishListItemRequest(['product' => 989]); + $message = $this->escaper->escapeHtml("We can't specify a product."); + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_ERROR); + $this->assertRedirect($this->stringContains('wishlist/')); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Catalog/_files/multiple_products.php + * + * @return void + */ + public function testUpdateOutOfStockItem(): void + { + $product = $this->productRepository->get('simple3'); + $this->customerSession->setCustomerId(1); + $this->performUpdateWishListItemRequest(['product' => $product->getId()]); + $message = $this->escaper->escapeHtml("We can't specify a product."); + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_ERROR); + $this->assertRedirect($this->stringContains('wishlist/')); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute.php + * + * @return void + */ + public function testUpdateItemNotSpecifyAsWishListItem(): void + { + $product = $this->productRepository->get('simple_ms_out_of_stock'); + $this->customerSession->setCustomerId(1); + $this->performUpdateWishListItemRequest(['product' => $product->getId()]); + $message = $this->escaper->escapeHtml("We can't specify a wish list item."); + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_ERROR); + $this->assertRedirect($this->stringContains('wishlist/index/index/wishlist_id/')); + } + + /** + * Perform request update wish list item. + * + * @param array $params + * @return void + */ + private function performUpdateWishListItemRequest(array $params): void + { + $this->getRequest()->setParams($params)->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('wishlist/index/updateItemOptions'); + } + + /** + * Assert updated item in wish list. + * + * @param Item $item + * @param array $expectedData + * @return void + */ + private function assertUpdatedItem(Item $item, array $expectedData): void + { + $this->assertEquals($expectedData['qty'], $item->getQty()); + $buyRequestOption = $this->json->unserialize($item->getOptionByCode('info_buyRequest')->getValue()); + foreach ($expectedData as $key => $value) { + $this->assertEquals($value, $buyRequestOption[$key]); + } + } + + /** + * Perform configurable option to select. + * + * @param ProductInterface $product + * @return array + */ + private function performConfigurableOption(ProductInterface $product): array + { + $configurableOptions = $product->getTypeInstance()->getConfigurableOptions($product); + $attributeId = key($configurableOptions); + $option = reset($configurableOptions[$attributeId]); + + return [$attributeId => $option['value_index']]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/UpdateTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/UpdateTest.php new file mode 100644 index 0000000000000..8126ec71eb7db --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/Index/UpdateTest.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Controller\Index; + +use Magento\Customer\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\TestCase\AbstractController; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; + +/** + * Test for update wish list item. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + */ +class UpdateTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** @var GetWishlistByCustomerId */ + private $getWishlistByCustomerId; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + $this->getWishlistByCustomerId = $this->_objectManager->get(GetWishlistByCustomerId::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->customerSession->setCustomerId(null); + + parent::tearDown(); + } + + /** + * @return void + */ + public function testUpdateWishListItem(): void + { + $this->customerSession->setCustomerId(1); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple'); + $this->assertNotNull($item); + $params = ['description' => [$item->getId() => 'Some description.'], 'qty' => [$item->getId() => 5]]; + $this->performUpdateWishListItemRequest($params); + $message = sprintf("%s has been updated in your Wish List.", $item->getProduct()->getName()); + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_SUCCESS); + $this->assertRedirect($this->stringContains('wishlist/index/index/wishlist_id/' . $item->getWishlistId())); + $updatedItem = $this->getWishlistByCustomerId->getItemBySku(1, 'simple'); + $this->assertNotNull($updatedItem); + $this->assertEquals(5, $updatedItem->getQty()); + $this->assertEquals('Some description.', $updatedItem->getDescription()); + } + + /** + * @return void + */ + public function testUpdateWishListItemZeroQty(): void + { + $this->customerSession->setCustomerId(1); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple'); + $this->assertNotNull($item); + $params = ['description' => [$item->getId() => ''], 'qty' => [$item->getId() => 0]]; + $this->performUpdateWishListItemRequest($params); + $message = sprintf("%s has been updated in your Wish List.", $item->getProduct()->getName()); + $this->assertSessionMessages($this->equalTo([(string)__($message)]), MessageInterface::TYPE_SUCCESS); + $this->assertRedirect($this->stringContains('wishlist/index/index/wishlist_id/' . $item->getWishlistId())); + $this->assertCount(0, $this->getWishlistByCustomerId->execute(1)->getItemCollection()); + } + + /** + * Perform update wish list item request. + * + * @param array $params + * @return void + */ + private function performUpdateWishListItemRequest(array $params): void + { + $this->getRequest()->setPostValue($params)->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('wishlist/index/update'); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php deleted file mode 100644 index d225b40dc39da..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php +++ /dev/null @@ -1,190 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Wishlist\Controller; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class IndexTest extends \Magento\TestFramework\TestCase\AbstractController -{ - /** - * @var \Magento\Customer\Model\Session - */ - protected $_customerSession; - - /** - * @var \Magento\Framework\Message\ManagerInterface - */ - protected $_messages; - - /** - * @var \Magento\Customer\Helper\View - */ - protected $_customerViewHelper; - - protected function setUp() - { - parent::setUp(); - $logger = $this->createMock(\Psr\Log\LoggerInterface::class); - $this->_customerSession = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Customer\Model\Session::class, - [$logger] - ); - /** @var \Magento\Customer\Api\AccountManagementInterface $service */ - $service = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Customer\Api\AccountManagementInterface::class - ); - $customer = $service->authenticate('customer@example.com', 'password'); - $this->_customerSession->setCustomerDataAsLoggedIn($customer); - - $this->_customerViewHelper = $this->_objectManager->create(\Magento\Customer\Helper\View::class); - - $this->_messages = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Framework\Message\ManagerInterface::class - ); - } - - protected function tearDown() - { - $this->_customerSession->logout(); - $this->_customerSession = null; - parent::tearDown(); - } - - /** - * Verify wishlist view action - * - * The following is verified: - * - \Magento\Wishlist\Model\ResourceModel\Item\Collection - * - \Magento\Wishlist\Block\Customer\Wishlist - * - \Magento\Wishlist\Block\Customer\Wishlist\Items - * - \Magento\Wishlist\Block\Customer\Wishlist\Item\Column - * - \Magento\Wishlist\Block\Customer\Wishlist\Item\Column\Cart - * - \Magento\Wishlist\Block\Customer\Wishlist\Item\Column\Comment - * - \Magento\Wishlist\Block\Customer\Wishlist\Button - * - that \Magento\Wishlist\Block\Customer\Wishlist\Item\Options doesn't throw a fatal error - * - * @magentoDataFixture Magento/Wishlist/_files/wishlist.php - */ - public function testItemColumnBlock() - { - $this->dispatch('wishlist/index/index'); - $body = $this->getResponse()->getBody(); - $this->assertEquals( - 1, - \Magento\TestFramework\Helper\Xpath::getElementsCountForXpath( - '//img[contains(@src, "small_image.jpg") and @alt = "Simple Product"]', - $body - ) - ); - $this->assertEquals( - 1, - \Magento\TestFramework\Helper\Xpath::getElementsCountForXpath( - '//textarea[contains(@name, "description")]', - $body - ) - ); - } - - /** - * @magentoDataFixture Magento/Catalog/_files/product_simple_xss.php - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoAppArea frontend - */ - public function testAddActionProductNameXss() - { - /** @var \Magento\Framework\Data\Form\FormKey $formKey */ - $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); - $this->getRequest()->setMethod('POST'); - $this->getRequest()->setPostValue( - [ - 'form_key' => $formKey->getFormKey(), - ] - ); - - /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ - $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); - - $product = $productRepository->get('product-with-xss'); - - $this->dispatch('wishlist/index/add/product/' . $product->getId() . '?nocookie=1'); - - $this->assertSessionMessages( - $this->equalTo( - [ - "\n<script>alert("xss");</script> has been added to your Wish List. " - . 'Click <a href="http://localhost/index.php/">here</a> to continue shopping.', - ] - ), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - } - - /** - * @magentoDbIsolation disabled - * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_product_qty_increments.php - */ - public function testAllcartAction() - { - $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class)->getFormKey(); - $this->getRequest()->setMethod('POST'); - $this->getRequest()->setParam('form_key', $formKey); - $this->dispatch('wishlist/index/allcart'); - - /** @var \Magento\Checkout\Model\Cart $cart */ - $cart = $this->_objectManager->get(\Magento\Checkout\Model\Cart::class); - $quoteCount = $cart->getQuote()->getItemsCollection()->count(); - - $this->assertEquals(0, $quoteCount); - $this->assertSessionMessages( - $this->contains( - htmlspecialchars( - 'You can buy this product only in quantities of 5 at a time for "Simple Product".' - ) - ), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - } - - /** - * @magentoDataFixture Magento/Wishlist/_files/wishlist.php - */ - public function testSendAction() - { - \Magento\TestFramework\Helper\Bootstrap::getInstance() - ->loadArea(\Magento\Framework\App\Area::AREA_FRONTEND); - - $request = [ - 'form_key' => $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class)->getFormKey(), - 'emails' => 'test@tosend.com', - 'message' => 'message', - 'rss_url' => null, // no rss - ]; - - $this->getRequest()->setPostValue($request); - $this->getRequest()->setMethod('POST'); - - $this->_objectManager->get(\Magento\Framework\Registry::class)->register( - 'wishlist', - $this->_objectManager->get(\Magento\Wishlist\Model\Wishlist::class)->loadByCustomerId(1) - ); - $this->dispatch('wishlist/index/send'); - - /** @var \Magento\TestFramework\Mail\Template\TransportBuilderMock $transportBuilder */ - $transportBuilder = $this->_objectManager->get( - \Magento\TestFramework\Mail\Template\TransportBuilderMock::class - ); - - $actualResult = $transportBuilder->getSentMessage()->getBody()->getParts()[0]->getRawContent(); - - $this->assertStringMatchesFormat( - '%A' . $this->_customerViewHelper->getCustomerName($this->_customerSession->getCustomerDataObject()) - . ' wants to share this Wish List%A', - $actualResult - ); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Model/ItemTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Model/ItemTest.php index 896b59c7983fe..afd15929ae685 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Model/ItemTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Model/ItemTest.php @@ -3,57 +3,94 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Wishlist\Model; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Exception as ProductException; +use Magento\Checkout\Model\CartFactory; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObjectFactory; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; +use Magento\Wishlist\Model\Item\OptionFactory; +use PHPUnit\Framework\TestCase; + /** - * Item test class. + * Tests for wish list item model. + * + * @magentoDbIsolation enabled + * @magentoAppIsolation disabled */ -class ItemTest extends \PHPUnit\Framework\TestCase +class ItemTest extends TestCase { - /** - * @var \Magento\Framework\App\ObjectManager - */ + /** @var ObjectManager */ private $objectManager; - /** - * @var \Magento\Wishlist\Model\Item - */ + /** @var Item */ private $model; + /** @var DataObjectFactory */ + private $dataObjectFactory; + + /** @var OptionFactory */ + private $optionFactory; + + /** @var GetWishlistByCustomerId */ + private $getWishlistByCustomerId; + + /** @var CartFactory */ + private $cartFactory; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var ItemFactory */ + private $itemFactory; + /** - * {@inheritDoc} + * @inheritdoc */ public function setUp() { - $this->objectManager = \Magento\Framework\App\ObjectManager::getInstance(); - $this->model = $this->objectManager->get(\Magento\Wishlist\Model\Item::class); + parent::setUp(); + + $this->objectManager = ObjectManager::getInstance(); + $this->dataObjectFactory = $this->objectManager->get(DataObjectFactory::class); + $this->model = $this->objectManager->get(Item::class); + $this->itemFactory = $this->objectManager->get(ItemFactory::class); + $this->optionFactory = $this->objectManager->get(OptionFactory::class); + $this->getWishlistByCustomerId = $this->objectManager->get(GetWishlistByCustomerId::class); + $this->cartFactory = $this->objectManager->get(CartFactory::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); } /** - * @magentoDataFixture Magento/Catalog/_files/product_simple.php - * @magentoAppIsolation enabled - * @magentoDbIsolation enabled + * @inheritdoc */ - public function testBuyRequest() + protected function tearDown() { - /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $product = $productRepository->getById(1); + $this->cartFactory->create()->truncate(); - /** @var \Magento\Wishlist\Model\Item\Option $option */ - $option = $this->objectManager->create( - \Magento\Wishlist\Model\Item\Option::class, + parent::tearDown(); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * + * @return void + */ + public function testBuyRequest(): void + { + $product = $this->productRepository->get('simple'); + $option = $this->optionFactory->create( ['data' => ['code' => 'info_buyRequest', 'value' => '{"qty":23}']] ); $option->setProduct($product); $this->model->addOption($option); - - // Assert getBuyRequest method $buyRequest = $this->model->getBuyRequest(); $this->assertEquals($buyRequest->getOriginalQty(), 23); - - // Assert mergeBuyRequest method $this->model->mergeBuyRequest(['qty' => 11, 'additional_data' => 'some value']); $buyRequest = $this->model->getBuyRequest(); $this->assertEquals( @@ -62,18 +99,87 @@ public function testBuyRequest() ); } - public function testSetBuyRequest() + /** + * @return void + */ + public function testSetBuyRequest(): void { - $buyRequest = $this->objectManager->create( - \Magento\Framework\DataObject::class, + $buyRequest = $this->dataObjectFactory->create( ['data' => ['field_1' => 'some data', 'field_2' => 234]] ); - $this->model->setBuyRequest($buyRequest); - - $this->assertEquals( + $this->assertJsonStringEqualsJsonString( '{"field_1":"some data","field_2":234,"id":null}', $this->model->getData('buy_request') ); } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_simple_product.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testAddItemToCart(): void + { + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple-1'); + $this->assertNotNull($item); + $cart = $this->cartFactory->create(); + $this->assertTrue($item->addToCart($cart)); + $this->assertCount(1, $cart->getQuote()->getItemsCollection()); + $this->assertCount(1, $this->getWishlistByCustomerId->execute(1)->getItemCollection()); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist_with_simple_product.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testAddItemToCartAndDeleteFromWishList(): void + { + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple-1'); + $this->assertNotNull($item); + $cart = $this->cartFactory->create(); + $item->addToCart($cart, true); + $this->assertCount(1, $cart->getQuote()->getItemsCollection()); + $this->assertCount(0, $this->getWishlistByCustomerId->execute(1)->getItemCollection()); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php + * + * @return void + */ + public function testAddOutOfStockItemToCart(): void + { + $product = $this->productRepository->get('simple-out-of-stock'); + $item = $this->itemFactory->create()->setProduct($product); + $this->expectExceptionObject(new ProductException(__('Product is not salable.'))); + $item->addToCart($this->cartFactory->create()); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/multiple_products.php + * + * @return void + */ + public function testAddDisabledItemToCart(): void + { + $product = $this->productRepository->get('simple3'); + $item = $this->itemFactory->create()->setProduct($product); + $this->assertFalse($item->addToCart($this->cartFactory->create())); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/simple_products_not_visible_individually.php + * + * @return void + */ + public function testAddNotVisibleItemToCart(): void + { + $product = $this->productRepository->get('simple_not_visible_1'); + $item = $this->itemFactory->create()->setProduct($product)->setStoreId($product->getStoreId()); + $this->assertFalse($item->addToCart($this->cartFactory->create())); + } } diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Model/WishlistTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Model/WishlistTest.php index b684da05dd254..f12299b32945d 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Model/WishlistTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Model/WishlistTest.php @@ -3,63 +3,86 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Wishlist\Model; +use Magento\Bundle\Model\Product\OptionList; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus; use Magento\Framework\App\ObjectManager; -use Magento\Framework\DataObject; +use Magento\Framework\DataObjectFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\TestFramework\Wishlist\Model\GetWishlistByCustomerId; +use PHPUnit\Framework\TestCase; -class WishlistTest extends \PHPUnit\Framework\TestCase +/** + * Tests for wish list model. + * + * @magentoDbIsolation enabled + * @magentoAppIsolation disabled + */ +class WishlistTest extends TestCase { - /** - * @var ObjectManager - */ + /** @var ObjectManager */ private $objectManager; - /** - * @var Wishlist - */ - private $wishlist; + /** @var WishlistFactory */ + private $wishlistFactory; + + /** @var GetWishlistByCustomerId */ + private $getWishlistByCustomerId; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var DataObjectFactory */ + private $dataObjectFactory; + + /** @var SerializerInterface */ + private $json; /** - * {@inheritDoc} + * @inheritdoc */ protected function setUp() { $this->objectManager = ObjectManager::getInstance(); - $this->wishlist = $this->objectManager->get(Wishlist::class); + $this->wishlistFactory = $this->objectManager->get(WishlistFactory::class); + $this->getWishlistByCustomerId = $this->objectManager->get(GetWishlistByCustomerId::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + $this->dataObjectFactory = $this->objectManager->get(DataObjectFactory::class); + $this->json = $this->objectManager->get(SerializerInterface::class); } /** * @magentoDataFixture Magento/Catalog/_files/product_simple.php * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoAppIsolation enabled - * @magentoDbIsolation enabled + * + * @return void */ - public function testAddNewItem() + public function testAddNewItem(): void { $productSku = 'simple'; $customerId = 1; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku); - $this->wishlist->loadByCustomerId($customerId, true); - $this->wishlist->addNewItem( + $product = $this->productRepository->get($productSku); + $wishlist = $this->getWishlistByCustomerId->execute($customerId); + $wishlist->addNewItem( $product, '{"qty":2}' ); - $this->wishlist->addNewItem( + $wishlist->addNewItem( $product, ['qty' => 3] ); - $this->wishlist->addNewItem( + $wishlist->addNewItem( $product, - new DataObject(['qty' => 4]) + $this->dataObjectFactory->create(['data' => ['qty' => 4]]) ); - $this->wishlist->addNewItem($product); - /** @var Item $wishlistItem */ - $wishlistItem = $this->wishlist->getItemCollection()->getFirstItem(); + $wishlist->addNewItem($product); + $wishlistItem = $this->getWishlistByCustomerId->getItemBySku(1, $productSku); $this->assertInstanceOf(Item::class, $wishlistItem); $this->assertEquals($wishlistItem->getQty(), 10); } @@ -67,58 +90,169 @@ public function testAddNewItem() /** * @magentoDataFixture Magento/Catalog/_files/product_simple.php * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoAppIsolation enabled - * @magentoDbIsolation enabled - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Invalid wishlist item configuration. + * + * @return void */ - public function testAddNewItemInvalidWishlistItemConfiguration() + public function testAddNewItemInvalidWishlistItemConfiguration(): void { $productSku = 'simple'; $customerId = 1; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku); - $this->wishlist->loadByCustomerId($customerId, true); - $this->wishlist->addNewItem( - $product, - '{"qty":2' - ); - $this->wishlist->addNewItem($product); + $product = $this->productRepository->get($productSku); + $wishlist = $this->getWishlistByCustomerId->execute($customerId); + $this->expectExceptionObject(new \InvalidArgumentException('Invalid wishlist item configuration.')); + $wishlist->addNewItem($product, '{"qty":2'); + } + + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + * + * @return void + */ + public function testGetItemCollection(): void + { + $productSku = 'simple'; + $item = $this->getWishlistByCustomerId->getItemBySku(1, $productSku); + $this->assertNotNull($item); } /** - * @magentoDbIsolation disabled * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + * + * @return void */ - public function testGetItemCollection() + public function testGetItemCollectionWithDisabledProduct(): void { $productSku = 'simple'; $customerId = 1; + $product = $this->productRepository->get($productSku); + $product->setStatus(ProductStatus::STATUS_DISABLED); + $this->productRepository->save($product); + $this->assertEmpty($this->getWishlistByCustomerId->execute($customerId)->getItemCollection()->getItems()); + } - $this->wishlist->loadByCustomerId($customerId, true); - $itemCollection = $this->wishlist->getItemCollection(); - /** @var \Magento\Wishlist\Model\Item $item */ - $item = $itemCollection->getFirstItem(); - $this->assertEquals($productSku, $item->getProduct()->getSku()); + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDbIsolation disabled + * + * @return void + */ + public function testAddConfigurableProductToWishList(): void + { + $configurableProduct = $this->productRepository->get('Configurable product'); + $configurableOptions = $configurableProduct->getTypeInstance()->getConfigurableOptions($configurableProduct); + $attributeId = key($configurableOptions); + $option = reset($configurableOptions[$attributeId]); + $buyRequest = ['super_attribute' => [$attributeId => $option['value_index']]]; + $wishlist = $this->getWishlistByCustomerId->execute(1); + $wishlist->addNewItem($configurableProduct, $buyRequest); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'Configurable product'); + $this->assertNotNull($item); + $this->assertWishListItem($item, $option['sku'], $buyRequest); } /** + * @magentoDataFixture Magento/Bundle/_files/fixed_bundle_product_without_discounts.php + * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDbIsolation disabled + * + * @return void + */ + public function testAddBundleProductToWishList(): void + { + $bundleProduct = $this->productRepository->get('fixed_bundle_product_without_discounts'); + $bundleOptionList = $this->objectManager->create(OptionList::class); + $bundleOptions = $bundleOptionList->getItems($bundleProduct); + $option = reset($bundleOptions); + $productLinks = $option->getProductLinks(); + $this->assertNotNull($productLinks[0]); + $buyRequest = ['bundle_option' => [$option->getOptionId() => $productLinks[0]->getId()]]; + $skuWithChosenOption = implode('-', [$bundleProduct->getSku(), $productLinks[0]->getSku()]); + $wishlist = $this->getWishlistByCustomerId->execute(1); + $wishlist->addNewItem($bundleProduct, $buyRequest); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'fixed_bundle_product_without_discounts'); + $this->assertNotNull($item); + $this->assertWishListItem($item, $skuWithChosenOption, $buyRequest); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testAddNotExistingItemToWishList(): void + { + $wishlist = $this->getWishlistByCustomerId->execute(1); + $this->expectExceptionObject(new LocalizedException(__('Cannot specify product.'))); + $wishlist->addNewItem(989); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Catalog/_files/product_out_of_stock_with_multiselect_attribute.php + * + * @return void + */ + public function testAddOutOfStockItemToWishList(): void + { + $product = $this->productRepository->get('simple_ms_out_of_stock'); + $wishlist = $this->getWishlistByCustomerId->execute(1); + $this->expectExceptionObject(new LocalizedException(__('Cannot add product without stock to wishlist.'))); + $wishlist->addNewItem($product); + } + + /** * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + * + * @return void */ - public function testGetItemCollectionWithDisabledProduct() + public function testUpdateItemQtyInWishList(): void { - $productSku = 'simple'; - $customerId = 1; + $wishlist = $this->getWishlistByCustomerId->execute(1); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple'); + $this->assertNotNull($item); + $buyRequest = $this->dataObjectFactory->create(['data' => ['qty' => 55]]); + $wishlist->updateItem($item->getId(), $buyRequest); + $updatedItem = $this->getWishlistByCustomerId->getItemBySku(1, 'simple'); + $this->assertEquals(55, $updatedItem->getQty()); + } - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku); - $product->setStatus(ProductStatus::STATUS_DISABLED); - $productRepository->save($product); + /** + * @return void + */ + public function testUpdateNotExistingItemInWishList(): void + { + $this->expectExceptionObject(new LocalizedException(__('We can\'t specify a wish list item.'))); + $this->wishlistFactory->create()->updateItem(989, []); + } - $this->wishlist->loadByCustomerId($customerId, true); - $itemCollection = $this->wishlist->getItemCollection(); - $this->assertEmpty($itemCollection->getItems()); + /** + * @magentoDataFixture Magento/Wishlist/_files/wishlist.php + * + * @return void + */ + public function testUpdateNotExistingProductInWishList(): void + { + $wishlist = $this->getWishlistByCustomerId->execute(1); + $item = $this->getWishlistByCustomerId->getItemBySku(1, 'simple'); + $this->assertNotNull($item); + $item->getProduct()->setId(null); + $this->expectExceptionObject(new LocalizedException(__('The product does not exist.'))); + $wishlist->updateItem($item, []); + } + + /** + * Assert item in wish list. + * + * @param Item $item + * @param string $itemSku + * @param array $buyRequest + * @return void + */ + private function assertWishListItem(Item $item, string $itemSku, array $buyRequest): void + { + $this->assertEquals($itemSku, $item->getProduct()->getSku()); + $buyRequestOption = $item->getOptionByCode('info_buyRequest'); + $this->assertEquals($buyRequest, $this->json->unserialize($buyRequestOption->getValue())); } } diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_shared_rollback.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_shared_rollback.php new file mode 100644 index 0000000000000..24bbccd5739f4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_shared_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_rollback.php'; +require __DIR__ . '/../../../Magento/Customer/_files/customer_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_configurable_product.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_configurable_product.php new file mode 100644 index 0000000000000..c67c3e32f24bc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_configurable_product.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Wishlist\Model\WishlistFactory; + +require __DIR__ . '/../../../Magento/Customer/_files/customer.php'; +require __DIR__ . '/../../../Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products.php'; + +$wishlistFactory = $objectManager->get(WishlistFactory::class); +$wishlist = $wishlistFactory->create(); +$wishlist->loadByCustomerId($customer->getId(), true); +$product = $productRepository->get('Configurable product'); +$wishlist->addNewItem($product); diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_configurable_product_rollback.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_configurable_product_rollback.php new file mode 100644 index 0000000000000..776d17137db30 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_configurable_product_rollback.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Customer/_files/customer_rollback.php'; +require __DIR__ + . '/../../../Magento/ConfigurableProduct/_files/configurable_product_with_two_child_products_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_simple_product.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_simple_product.php new file mode 100644 index 0000000000000..61448c54dd7ab --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_simple_product.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Wishlist\Model\WishlistFactory; + +require __DIR__ . '/../../../Magento/Customer/_files/customer.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_duplicated.php'; + +$wishlistFactory = $objectManager->get(WishlistFactory::class); +$wishlist = $wishlistFactory->create(); +$wishlist->loadByCustomerId($customer->getId(), true); +$wishlist->addNewItem($product); diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_simple_product_rollback.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_simple_product_rollback.php new file mode 100644 index 0000000000000..ffa99feba6652 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_with_simple_product_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_duplicated_rollback.php'; +require __DIR__ . '/../../../Magento/Customer/_files/customer_rollback.php'; From ce7924aae8e48831b278a0778022b8eb17ca4003 Mon Sep 17 00:00:00 2001 From: Yurii Sapiha <yurasapiga93@gmail.com> Date: Mon, 16 Mar 2020 15:09:29 +0200 Subject: [PATCH 311/369] MC-32124: Admin: Create/update bundle product --- .../Product/AbstractBundleProductSaveTest.php | 252 ++++++++++++++++ .../Product/DynamicBundleProductTest.php | 269 ++++++++++++++++++ .../Product/FixedBundleProductTest.php | 226 +++++++++++++++ 3 files changed, 747 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/AbstractBundleProductSaveTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/DynamicBundleProductTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/FixedBundleProductTest.php diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/AbstractBundleProductSaveTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/AbstractBundleProductSaveTest.php new file mode 100644 index 0000000000000..3ce3181b9c803 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/AbstractBundleProductSaveTest.php @@ -0,0 +1,252 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Controller\Adminhtml\Product; + +use Magento\Bundle\Model\Product\Type; +use Magento\Bundle\Model\ResourceModel\Option\Collection as OptionCollection; +use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Product as ProductResource; +use Magento\Eav\Model\Config; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Class determine basic logic for bundle product save tests + */ +abstract class AbstractBundleProductSaveTest extends AbstractBackendController +{ + /** @var string */ + protected $productToDelete; + + /** @var ProductRepositoryInterface */ + protected $productRepository; + + /** @var Config */ + private $eavConfig; + + /** @var ProductResource */ + private $productResource; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->eavConfig = $this->_objectManager->get(Config::class); + $this->productResource = $this->_objectManager->get(ProductResource::class); + $this->productToDelete = $this->getStaticProductData()['sku']; + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + if ($this->productToDelete) { + $this->productRepository->deleteById($this->productToDelete); + } + + parent::tearDown(); + } + + /** + * Retrieve default product attribute set id. + * + * @return int + */ + protected function getDefaultAttributeSetId(): int + { + return (int)$this->eavConfig->getEntityType(ProductAttributeInterface::ENTITY_TYPE_CODE) + ->getDefaultAttributeSetId(); + } + + /** + * Prepare request + * + * @param array $post + * @param int|null $id + * @return array + */ + protected function prepareRequestData(array $post, ?int $id = null): array + { + $post = $this->preparePostParams($post); + $this->setRequestparams($post, $id); + + return $post; + } + + /** + * Prepare and assert bundle options + * + * @param array $bundleOptions + * @return void + */ + protected function assertBundleOptions(array $bundleOptions): void + { + $mainProduct = $this->productRepository->get($this->getStaticProductData()['sku'], false, null, true); + $optionsCollection = $mainProduct->getTypeInstance()->getOptionsCollection($mainProduct); + $selectionCollection = $mainProduct->getTypeInstance() + ->getSelectionsCollection($optionsCollection->getAllIds(), $mainProduct); + $this->assertOptionsData($bundleOptions, $optionsCollection, $selectionCollection); + } + + /** + * Prepare post params before dispatch + * + * @param array $post + * @return array + */ + private function preparePostParams(array $post): array + { + $post['product'] = $this->getStaticProductData(); + foreach ($post['bundle_options']['bundle_options'] as &$bundleOption) { + $bundleOption = $this->prepareOptionByType($bundleOption['type'], $bundleOption); + $productIdsBySkus = $this->productResource->getProductsIdsBySkus( + array_column($bundleOption['bundle_selections'], 'sku') + ); + foreach ($bundleOption['bundle_selections'] as &$bundleSelection) { + $bundleSelection = $this->prepareSelection($productIdsBySkus, $bundleSelection); + } + } + + return $post; + } + + /** + * Prepare option params + * + * @param string $type + * @param array $option + * @return array + */ + private function prepareOptionByType(string $type, array $option): array + { + $option['required'] = '1'; + $option['delete'] = ''; + $option['title'] = $option['title'] ?? $type . ' Option Title'; + + return $option; + } + + /** + * Prepare selection params + * + * @param array $productIdsBySkus + * @param array $selection + * @return array + */ + private function prepareSelection(array $productIdsBySkus, array $selection): array + { + $staticData = [ + 'price' => '10', + 'selection_qty' => '5', + 'selection_can_change_qty' => '0' + ]; + $selection['product_id'] = $productIdsBySkus[$selection['sku']]; + $selection = array_merge($selection, $staticData); + + return $selection; + } + + /** + * Assert bundle options data + * + * @param array $expectedOptions + * @param OptionCollection $actualOptions + * @param SelectionCollection $selectionCollection + * @return void + */ + private function assertOptionsData( + array $expectedOptions, + OptionCollection $actualOptions, + SelectionCollection $selectionCollection + ): void { + $this->assertCount(count($expectedOptions['bundle_options']), $actualOptions); + foreach ($expectedOptions['bundle_options'] as $expectedOption) { + $optionToCheck = $actualOptions->getItemByColumnValue('title', $expectedOption['title']); + $this->assertNotNull($optionToCheck->getId()); + $selectionToCheck = $selectionCollection->getItemsByColumnValue('option_id', $optionToCheck->getId()); + $this->assertCount(count($expectedOption['bundle_selections']), $selectionToCheck); + $this->assertSelections($expectedOption['bundle_selections'], $selectionToCheck); + unset($expectedOption['delete'], $expectedOption['bundle_selections']); + foreach ($expectedOption as $key => $value) { + $this->assertEquals($value, $optionToCheck->getData($key)); + } + } + } + + /** + * Assert selections data + * + * @param array $expectedSelections + * @param array $actualSelections + * @return void + */ + private function assertSelections(array $expectedSelections, array $actualSelections): void + { + foreach ($expectedSelections as $expectedSelection) { + $actualSelectionToCheck = $this->getSelectionByProductSku($expectedSelection['sku'], $actualSelections); + $this->assertNotNull($actualSelectionToCheck); + foreach ($expectedSelection as $key => $value) { + $this->assertEquals($value, $actualSelectionToCheck->getData($key)); + } + } + } + + /** + * Get selection by product sku + * + * @param string $sku + * @param array $actualSelections + * @return ProductInterface + */ + private function getSelectionByProductSku(string $sku, array $actualSelections): ProductInterface + { + $item = null; + foreach ($actualSelections as $selection) { + if ($selection->getSku() === $sku) { + $item = $selection; + break; + } + } + + return $item; + } + + /** + * Set request parameters + * + * @param array $post + * @param int|null $id + * @return void + */ + private function setRequestParams(array $post, ?int $id): void + { + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $params = ['type' => Type::TYPE_CODE, 'set' => $this->getDefaultAttributeSetId()]; + if ($id) { + $params['id'] = $id; + } + $this->getRequest()->setParams($params); + $this->getRequest()->setPostValue('product', $post['product']); + $this->getRequest()->setPostValue('bundle_options', $post['bundle_options']); + } + + /** + * Get main product data + * + * @return array + */ + abstract protected function getStaticProductData(): array; +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/DynamicBundleProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/DynamicBundleProductTest.php new file mode 100644 index 0000000000000..988baf91981bc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/DynamicBundleProductTest.php @@ -0,0 +1,269 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Controller\Adminhtml\Product; + +use Magento\Bundle\Model\Product\Price; +use Magento\Catalog\Model\Product\Type\AbstractType; + +/** + * Class checks dynamic bundle product save behavior + * + * @magentoAppArea adminhtml + */ +class DynamicBundleProductTest extends AbstractBundleProductSaveTest +{ + /** + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @dataProvider bundleProductDataProvider + * + * @param array $post + * @return void + */ + public function testBundleProductSave(array $post): void + { + $post = $this->prepareRequestData($post); + $this->dispatch('backend/catalog/product/save'); + $this->assertBundleOptions($post['bundle_options']); + } + + /** + * @return array + */ + public function bundleProductDataProvider(): array + { + return [ + 'with_dropdown_option' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'select', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + 'with_radio_buttons_option' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'radio', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + 'with_checkbox_option' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'checkbox', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + 'with_multiselect_option' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'multi', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_duplicated.php + * + * @dataProvider multiOptionsDataProvider + * + * @param array $post + * @return void + */ + public function testBundleProductSaveMultiOptions(array $post): void + { + $post = $this->prepareRequestData($post); + $this->dispatch('backend/catalog/product/save'); + $this->assertBundleOptions($post['bundle_options']); + } + + /** + * @return array + */ + public function multiOptionsDataProvider(): array + { + return [ + 'with_two_options_few_selections' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'select', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + [ + 'name' => 'Simple Product', + 'sku' => 'simple-1', + ], + ], + ], + [ + 'type' => 'checkbox', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product', + 'sku' => 'simple-1', + ], + ], + ], + ], + ], + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @dataProvider emptyOptionTitleDataProvider + * + * @param array $post + * @return void + */ + public function testProductSaveMissedOptionTitle(array $post): void + { + $this->productToDelete = null; + $post = $this->prepareRequestData($post); + $this->dispatch('backend/catalog/product/save'); + $this->assertSessionMessages($this->equalTo(["The option couldn't be saved."])); + } + + /** + * @return array + */ + public function emptyOptionTitleDataProvider(): array + { + return [ + 'empty_option_title' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'title' => '', + 'type' => 'multi', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Bundle/_files/bundle_product_checkbox_options.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @dataProvider updateProductDataProvider + * + * @param array $post + * @return void + */ + public function testUpdateProduct(array $post): void + { + $id = $this->productRepository->get('bundle-product-checkbox-options')->getId(); + $post = $this->prepareRequestData($post, (int)$id); + $this->dispatch('backend/catalog/product/save'); + $this->assertBundleOptions($post['bundle_options']); + } + + /** + * @return array + */ + public function updateProductDataProvider(): array + { + return [ + 'update_existing_product' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'multi', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + ]; + } + + /** + * @inheritdoc + */ + protected function getStaticProductData(): array + { + return [ + 'sku' => 'bundle-test-product', + 'name' => 'test-bundle', + 'price' => '', + 'sku_type' => '0', + 'price_type' => Price::PRICE_TYPE_DYNAMIC, + 'weight_type' => '0', + 'shipment_type' => AbstractType::SHIPMENT_TOGETHER, + 'attribute_set_id' => $this->getDefaultAttributeSetId(), + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/FixedBundleProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/FixedBundleProductTest.php new file mode 100644 index 0000000000000..908a96368992d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Controller/Adminhtml/Product/FixedBundleProductTest.php @@ -0,0 +1,226 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Controller\Adminhtml\Product; + +use Magento\Bundle\Model\Product\Price; +use Magento\Catalog\Model\Product\Type\AbstractType; + +/** + * Class checks fixed bundle product save behavior + * + * @magentoAppArea adminhtml + */ +class FixedBundleProductTest extends AbstractBundleProductSaveTest +{ + /** + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @dataProvider fixedBundleProductDataProvider + * + * @param array $post + * @return void + */ + public function testBundleProductSave(array $post): void + { + $post = $this->prepareRequestData($post); + $this->dispatch('backend/catalog/product/save'); + $this->assertBundleOptions($post['bundle_options']); + } + + /** + * @return array + */ + public function fixedBundleProductDataProvider(): array + { + return [ + 'with_dropdown_option' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'select', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + 'with_radio_buttons_option' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'radio', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + 'with_checkbox_option' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'checkbox', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + 'with_multiselect_option' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'multi', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_duplicated.php + * + * @dataProvider multiOptionsDataProvider + * + * @param array $post + * @return void + */ + public function testBundleProductSaveMultiOptions(array $post): void + { + $post = $this->prepareRequestData($post); + $this->dispatch('backend/catalog/product/save'); + $this->assertBundleOptions($post['bundle_options']); + } + + /** + * @return array + */ + public function multiOptionsDataProvider(): array + { + return [ + 'with_two_options_few_selections' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'select', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + [ + 'name' => 'Simple Product', + 'sku' => 'simple-1', + ], + ], + ], + [ + 'type' => 'checkbox', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product', + 'sku' => 'simple-1', + ], + ], + ], + ], + ], + ], + ], + ]; + } + + /** + * @magentoDataFixture Magento/Bundle/_files/bundle_product_checkbox_options.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @dataProvider updateProductDataProvider + * + * @param array $post + * @return void + */ + public function testUpdateProduct(array $post): void + { + $id = $this->productRepository->get('bundle-product-checkbox-options')->getId(); + $post = $this->prepareRequestData($post, (int)$id); + $this->dispatch('backend/catalog/product/save'); + $this->assertBundleOptions($post['bundle_options']); + } + + /** + * @return array + */ + public function updateProductDataProvider(): array + { + return [ + 'update_existing_product' => [ + 'post' => [ + 'bundle_options' => [ + 'bundle_options' => [ + [ + 'type' => 'multi', + 'bundle_selections' => [ + [ + 'name' => 'Simple Product2', + 'sku' => 'simple2', + ], + ], + ], + ], + ], + ], + ], + ]; + } + + /** + * @inheritdoc + */ + protected function getStaticProductData(): array + { + return [ + 'sku' => 'bundle-test-product', + 'name' => 'test-bundle', + 'price' => '150', + 'sku_type' => '1', + 'price_type' => Price::PRICE_TYPE_FIXED, + 'weight_type' => '1', + 'shipment_type' => AbstractType::SHIPMENT_TOGETHER, + 'attribute_set_id' => $this->getDefaultAttributeSetId(), + ]; + } +} From 04ec5edcedb30b9a74e6811c7fcb478265817e7a Mon Sep 17 00:00:00 2001 From: Yurii Sapiha <yurasapiga93@gmail.com> Date: Mon, 16 Mar 2020 15:50:53 +0200 Subject: [PATCH 312/369] MC-32136: Email product to friend --- .../SendFriend/Model/DeleteLogRowsByIp.php | 40 +++ .../SendFriend/Block/ProductViewTest.php | 104 ++++++++ .../Magento/SendFriend/Block/SendTest.php | 124 +++++++--- .../SendFriend/Controller/SendTest.php | 161 +++++++++++++ .../SendFriend/Controller/SendmailTest.php | 190 ++++++++------- .../SendFriend/Model/SendFriendTest.php | 227 ++++++++++++++++++ ...sendfriend_log_record_half_hour_before.php | 20 ++ ...d_log_record_half_hour_before_rollback.php | 13 + 8 files changed, 767 insertions(+), 112 deletions(-) create mode 100644 dev/tests/integration/framework/Magento/TestFramework/SendFriend/Model/DeleteLogRowsByIp.php create mode 100644 dev/tests/integration/testsuite/Magento/SendFriend/Block/ProductViewTest.php create mode 100644 dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendTest.php create mode 100644 dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php create mode 100644 dev/tests/integration/testsuite/Magento/SendFriend/_files/sendfriend_log_record_half_hour_before.php create mode 100644 dev/tests/integration/testsuite/Magento/SendFriend/_files/sendfriend_log_record_half_hour_before_rollback.php diff --git a/dev/tests/integration/framework/Magento/TestFramework/SendFriend/Model/DeleteLogRowsByIp.php b/dev/tests/integration/framework/Magento/TestFramework/SendFriend/Model/DeleteLogRowsByIp.php new file mode 100644 index 0000000000000..aecf40b575957 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/SendFriend/Model/DeleteLogRowsByIp.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\SendFriend\Model; + +use Magento\SendFriend\Model\ResourceModel\SendFriend as SendFriendResource; + +/** + * Delete log rows by ip address + */ +class DeleteLogRowsByIp +{ + /** @var SendFriendResource */ + private $sendFriendResource; + + /** + * @param SendFriendResource $sendFriendResource + */ + public function __construct(SendFriendResource $sendFriendResource) + { + $this->sendFriendResource = $sendFriendResource; + } + + /** + * Delete rows from sendfriend_log table by ip address + * + * @param string $ipAddress + * @return void + */ + public function execute(string $ipAddress): void + { + $connection = $this->sendFriendResource->getConnection(); + $condition = $connection->quoteInto('ip = ?', ip2long($ipAddress)); + $connection->delete($this->sendFriendResource->getMainTable(), $condition); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Block/ProductViewTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Block/ProductViewTest.php new file mode 100644 index 0000000000000..daa7b0bab84e3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Block/ProductViewTest.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SendFriend\Block; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Block\Product\View; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Framework\View\LayoutInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Class checks send friend link visibility + * + * @magentoAppArea frontend + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + */ +class ProductViewTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var Registry */ + private $registry; + + /** @var LayoutInterface */ + private $layout; + + /** @var View */ + private $block; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->block = $this->layout->createBlock(View::class); + $this->block->setTemplate('Magento_Catalog::product/view/mailto.phtml'); + $this->registry = $this->objectManager->get(Registry::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->productRepository->cleanCache(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + + $this->registry->unregister('product'); + } + + /** + * @return void + */ + public function testSendFriendLinkDisabled(): void + { + $this->registerProduct('simple2'); + $this->assertEmpty($this->block->toHtml()); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/enabled 1 + * + * @return void + */ + public function testSendFriendLinkEnabled(): void + { + $product = $this->registerProduct('simple2'); + $html = $this->block->toHtml(); + $this->assertContains('sendfriend/product/send/id/' . $product->getId(), $html); + $this->assertEquals('Email', trim(strip_tags($html))); + } + + /** + * Register product by sku + * + * @param string $sku + * @return ProductInterface + */ + private function registerProduct(string $sku): ProductInterface + { + $product = $this->productRepository->get($sku); + $this->registry->unregister('product'); + $this->registry->register('product', $product); + + return $product; + } +} diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Block/SendTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Block/SendTest.php index 1c6bfe29f876d..539293480d5bb 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Block/SendTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Block/SendTest.php @@ -3,40 +3,94 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\SendFriend\Block; +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Model\Session; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\View\LayoutInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; +use PHPUnit\Framework\TestCase; -class SendTest extends \PHPUnit\Framework\TestCase +/** + * Class checks send friend email block + * + * @see \Magento\SendFriend\Block\Send + * + * @magentoAppArea frontend + */ +class SendTest extends TestCase { + /** @var array */ + private $elementsXpath = [ + 'sender name field' => "//input[@name='sender[name]']", + 'sender email field' => "//input[@name='sender[email]']", + 'sender message field' => "//textarea[@name='sender[message]']", + 'recipient name field' => "//input[contains(@name, 'recipients[name]')]", + 'recipient email field' => "//input[contains(@name, 'recipients[email]')]", + 'submit button' => "//button[@type='submit']/span[contains(text(), 'Send Email')]", + 'notice massage' => "//div[@id='max-recipient-message']" + . "/span[contains(text(), 'Maximum 1 email addresses allowed.')]" + ]; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var LayoutInterface */ + private $layout; + + /** @var Send */ + private $block; + + /** @var Session */ + private $session; + + /** @var AccountManagementInterface */ + private $accountManagement; + /** - * @var \Magento\SendFriend\Block\Send + * @inheritdoc */ - protected $_block; - protected function setUp() { - $this->_block = Bootstrap::getObjectManager()->create(\Magento\SendFriend\Block\Send::class); + $this->objectManager = Bootstrap::getObjectManager(); + $this->layout = $this->objectManager->get(LayoutInterface::class); + $this->block = $this->layout->createBlock(Send::class); + $this->session = $this->objectManager->get(Session::class); + $this->accountManagement = $this->objectManager->get(AccountManagementInterface::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + + $this->session->logout(); } /** + * @dataProvider formDataProvider + * * @param string $field * @param string $value - * @dataProvider formDataProvider - * @covers \Magento\SendFriend\Block\Send::getUserName - * @covers \Magento\SendFriend\Block\Send::getEmail + * @return void */ - public function testGetCustomerFieldFromFormData($field, $value) + public function testGetCustomerFieldFromFormData(string $field, string $value): void { $formData = ['sender' => [$field => $value]]; - $this->_block->setFormData($formData); + $this->block->setFormData($formData); $this->assertEquals(trim($value), $this->_callBlockMethod($field)); } /** * @return array */ - public function formDataProvider() + public function formDataProvider(): array { return [ ['name', 'Customer Form Name'], @@ -45,29 +99,27 @@ public function formDataProvider() } /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @dataProvider customerSessionDataProvider + * + * @magentoAppIsolation enabled + * * @param string $field * @param string $value - * @dataProvider customerSessionDataProvider - * @covers \Magento\SendFriend\Block\Send::getUserName - * @covers \Magento\SendFriend\Block\Send::getEmail - * @magentoDataFixture Magento/Customer/_files/customer.php + * @return void */ - public function testGetCustomerFieldFromSession($field, $value) + public function testGetCustomerFieldFromSession(string $field, string $value): void { - $logger = $this->createMock(\Psr\Log\LoggerInterface::class); - /** @var $session \Magento\Customer\Model\Session */ - $session = Bootstrap::getObjectManager()->create(\Magento\Customer\Model\Session::class, [$logger]); - /** @var \Magento\Customer\Api\AccountManagementInterface $service */ - $service = Bootstrap::getObjectManager()->create(\Magento\Customer\Api\AccountManagementInterface::class); - $customer = $service->authenticate('customer@example.com', 'password'); - $session->setCustomerDataAsLoggedIn($customer); + $customer = $this->accountManagement->authenticate('customer@example.com', 'password'); + $this->session->setCustomerDataAsLoggedIn($customer); $this->assertEquals($value, $this->_callBlockMethod($field)); } /** * @return array */ - public function customerSessionDataProvider() + public function customerSessionDataProvider(): array { return [ ['name', 'John Smith'], @@ -75,19 +127,37 @@ public function customerSessionDataProvider() ]; } + /** + * @magentoConfigFixture current_store sendfriend/email/max_recipients 1 + * + * @return void + */ + public function testBlockAppearance(): void + { + $this->block->setTemplate('Magento_SendFriend::send.phtml'); + $html = preg_replace('#<script(.*?)>#i', '', $this->block->toHtml()); + foreach ($this->elementsXpath as $key => $xpath) { + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath($xpath, $html), + sprintf('The %s field is not found on the page', $key) + ); + } + } + /** * Call block method based on form field * * @param string $field * @return null|string */ - protected function _callBlockMethod($field) + protected function _callBlockMethod(string $field): ?string { switch ($field) { case 'name': - return $this->_block->getUserName(); + return $this->block->getUserName(); case 'email': - return $this->_block->getEmail(); + return $this->block->getEmail(); default: return null; } diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendTest.php new file mode 100644 index 0000000000000..eccf4ee4c5ef9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendTest.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SendFriend\Controller; + +use Magento\Customer\Model\Session; +use Magento\SendFriend\Model\SendFriend; +use Magento\TestFramework\Response; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Class for send friend send action + * + * @magentoAppArea frontend + * + * @see \Magento\SendFriend\Controller\Product\Send + */ +class SendTest extends AbstractController +{ + /** @var Session */ + private $customerSession; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->customerSession = $this->_objectManager->get(Session::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + + $this->customerSession->logout(); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/enabled 0 + * + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testSendMailNotAllowed(): void + { + $this->dispatchWithProductIdParam(6); + $this->assert404NotFound(); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/enabled 1 + * @magentoConfigFixture current_store sendfriend/email/allow_guest 0 + * + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testGuestSendMailNotAllowed(): void + { + $this->dispatchWithProductIdParam(6); + $this->assertRedirect($this->stringContains('customer/account/login')); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/enabled 1 + * @magentoConfigFixture current_store sendfriend/email/allow_guest 1 + * + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testGuestSendMailAllowed(): void + { + $this->dispatchWithProductIdParam(6); + $this->assertEquals(Response::STATUS_CODE_200, $this->getResponse()->getHttpResponseCode()); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/enabled 1 + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testLoggedInCustomer(): void + { + $this->customerSession->loginById(1); + $this->dispatchWithProductIdParam(6); + $this->assertEquals(Response::STATUS_CODE_200, $this->getResponse()->getHttpResponseCode()); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/enabled 1 + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * + * @return void + */ + public function testWithoutProductId(): void + { + $this->customerSession->loginById(1); + $this->dispatch('sendfriend/product/send/'); + $this->assert404NotFound(); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/enabled 1 + * @magentoConfigFixture current_store sendfriend/email/max_per_hour 1 + * + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Catalog/_files/second_product_simple.php + * + * @return void + */ + public function testToMachSendRequests(): void + { + $this->createSendFriendMock(); + $this->customerSession->loginById(1); + $this->dispatchWithProductIdParam(6); + $this->assertSessionMessages( + $this->equalTo([(string)__('You can\'t send messages more than 5 times an hour.')]) + ); + $this->assertEquals(Response::STATUS_CODE_200, $this->getResponse()->getHttpResponseCode()); + } + + /** + * Set product id parameter and dispatch controller + * + * @param int $productId + * @return void + */ + private function dispatchWithProductIdParam(int $productId): void + { + $this->getRequest()->setParam('id', $productId); + $this->dispatch('sendfriend/product/send/'); + } + + /** + * Create mock to imitate to mach send requests + * + * @return void + */ + private function createSendFriendMock(): void + { + $mock = $this->createMock(SendFriend::class); + $mock->method('isExceedLimit')->willReturn(true); + $mock->method('getMaxSendsToFriend')->willReturn(5); + $this->_objectManager->addSharedInstance($mock, SendFriend::class); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendmailTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendmailTest.php index 1d6adf52466d2..850225bcee1b2 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendmailTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/SendmailTest.php @@ -10,79 +10,128 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Customer\Model\Session; -use Magento\Framework\Data\Form\FormKey; -use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\Message\MessageInterface; +use Magento\TestFramework\Helper\Xpath; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; use Magento\TestFramework\Request; use Magento\TestFramework\TestCase\AbstractController; /** - * Class SendmailTest + * Class checks send mail action + * + * @see \Magento\SendFriend\Controller\Product\Sendmail + * + * @magentoAppArea frontend + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled */ class SendmailTest extends AbstractController { + private const MESSAGE_PRODUCT_LINK_XPATH = "//a[contains(@href, '%s') and contains(text(), '%s')]"; + + /** @var Session */ + private $session; + + /** @var ProductRepositoryInterface */ + private $productRepository; + + /** @var TransportBuilderMock */ + private $transportBuilder; + + /** @var array */ + private $staticData = [ + 'sender' => [ + 'name' => 'Test', + 'email' => 'test@example.com', + 'message' => 'Message', + ], + 'recipients' => [ + 'name' => [ + 'Recipient 1', + 'Recipient 2' + ], + 'email' => [ + 'r1@example.com', + 'r2@example.com' + ] + ], + ]; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->session = $this->_objectManager->get(Session::class); + $this->productRepository = $this->_objectManager->get(ProductRepositoryInterface::class); + $this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + + $this->session->logout(); + } + /** * Share the product to friend as logged in customer * - * @magentoAppArea frontend - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled * @magentoConfigFixture default_store sendfriend/email/allow_guest 0 * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Catalog/_files/products.php + * + * @return void */ - public function testSendActionAsLoggedIn() + public function testSendActionAsLoggedIn(): void { - $product = $this->getProduct(); - $this->login(1); + $product = $this->productRepository->get('custom-design-simple-product'); + $this->session->loginById(1); $this->prepareRequestData(); - $this->dispatch('sendfriend/product/sendmail/id/' . $product->getId()); - $this->assertSessionMessages( - $this->equalTo(['The link to a friend was sent.']), - MessageInterface::TYPE_SUCCESS - ); + $this->checkSuccess($product); } /** * Share the product to friend as guest customer * - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/allow_guest 1 * @magentoDataFixture Magento/Catalog/_files/products.php + * + * @return void */ - public function testSendActionAsGuest() + public function testSendActionAsGuest(): void { - $product = $this->getProduct(); + $product = $this->productRepository->get('custom-design-simple-product'); $this->prepareRequestData(); - $this->dispatch('sendfriend/product/sendmail/id/' . $product->getId()); - $this->assertSessionMessages( - $this->equalTo(['The link to a friend was sent.']), - MessageInterface::TYPE_SUCCESS - ); + $this->checkSuccess($product); } /** * Share the product to friend as guest customer with invalid post data * - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/allow_guest 1 * @magentoDataFixture Magento/Catalog/_files/products.php + * + * @return void */ - public function testSendActionAsGuestWithInvalidData() + public function testSendActionAsGuestWithInvalidData(): void { - $product = $this->getProduct(); - $this->prepareRequestData(true); - + $product = $this->productRepository->get('custom-design-simple-product'); + unset($this->staticData['sender']['email']); + $this->prepareRequestData(); $this->dispatch('sendfriend/product/sendmail/id/' . $product->getId()); $this->assertSessionMessages( - $this->equalTo(['Invalid Sender Email']), + $this->equalTo([(string)__('Invalid Sender Email')]), MessageInterface::TYPE_ERROR ); } @@ -90,82 +139,53 @@ public function testSendActionAsGuestWithInvalidData() /** * Share the product invisible in catalog to friend as guest customer * - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled * @magentoConfigFixture default_store sendfriend/email/enabled 1 * @magentoConfigFixture default_store sendfriend/email/allow_guest 1 * @magentoDataFixture Magento/Catalog/_files/simple_products_not_visible_individually.php + * + * @return void */ - public function testSendInvisibleProduct() + public function testSendInvisibleProduct(): void { - $product = $this->getInvisibleProduct(); + $product = $this->productRepository->get('simple_not_visible_1'); $this->prepareRequestData(); - $this->dispatch('sendfriend/product/sendmail/id/' . $product->getId()); $this->assert404NotFound(); } /** - * @return ProductInterface - */ - private function getProduct() - { - return $this->_objectManager->get(ProductRepositoryInterface::class)->get('custom-design-simple-product'); - } - - /** - * @return ProductInterface - */ - private function getInvisibleProduct() - { - return $this->_objectManager->get(ProductRepositoryInterface::class)->get('simple_not_visible_1'); - } - - /** - * Login the user + * Check success session message and email content * - * @param string $customerId Customer to mark as logged in for the session + * @param ProductInterface $product * @return void */ - protected function login($customerId) + private function checkSuccess(ProductInterface $product): void { - /** @var Session $session */ - $session = Bootstrap::getObjectManager() - ->get(Session::class); - $session->loginById($customerId); + $this->assertSessionMessages( + $this->equalTo([(string)__('The link to a friend was sent.')]), + MessageInterface::TYPE_SUCCESS + ); + $message = $this->transportBuilder->getSentMessage(); + $this->assertNotNull($message, 'The message was not sent'); + $content = $message->getBody()->getParts()[0]->getRawContent(); + $this->assertEquals( + 1, + Xpath::getElementsCountForXpath( + sprintf(self::MESSAGE_PRODUCT_LINK_XPATH, $product->getUrlKey(), $product->getName()), + $content + ), + 'Sent message does not contain product link' + ); } /** - * @param bool $invalidData + * Prepare request before dispatch + * * @return void */ - private function prepareRequestData($invalidData = false) + private function prepareRequestData(): void { - /** @var FormKey $formKey */ - $formKey = $this->_objectManager->get(FormKey::class); - $post = [ - 'sender' => [ - 'name' => 'Test', - 'email' => 'test@example.com', - 'message' => 'Message', - ], - 'recipients' => [ - 'name' => [ - 'Recipient 1', - 'Recipient 2' - ], - 'email' => [ - 'r1@example.com', - 'r2@example.com' - ] - ], - 'form_key' => $formKey->getFormKey(), - ]; - if ($invalidData) { - unset($post['sender']['email']); - } - $this->getRequest()->setMethod(Request::METHOD_POST); - $this->getRequest()->setPostValue($post); + $this->getRequest()->setPostValue($this->staticData); } } diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php new file mode 100644 index 0000000000000..52b2ed05baf9e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php @@ -0,0 +1,227 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SendFriend\Model; + +use Laminas\Stdlib\Parameters; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Stdlib\CookieManagerInterface; +use Magento\SendFriend\Helper\Data as SendFriendHelper; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Class checks send friend model behavior + * + * @see \Magento\SendFriend\Model\SendFriend + */ +class SendFriendTest extends TestCase +{ + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var SendFriend */ + private $sendFriend; + + /** @var CookieManagerInterface */ + private $cookieManager; + + /** @var RequestInterface */ + private $request; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->objectManager = Bootstrap::getObjectManager(); + $this->sendFriend = $this->objectManager->get(SendFriendFactory::class)->create(); + $this->cookieManager = $this->objectManager->get(CookieManagerInterface::class); + $this->request = $this->objectManager->get(RequestInterface::class); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/max_recipients 1 + * + * @dataProvider validateDataProvider + * + * @param array $sender + * @param array $recipients + * @param string|bool $expectedResult + * @return void + */ + public function testValidate(array $sender, array $recipients, $expectedResult): void + { + $this->prepareData($sender, $recipients); + $this->checkResult($expectedResult, $this->sendFriend->validate()); + } + + /** + * @return array + */ + public function validateDataProvider(): array + { + return [ + 'valid_data' => [ + 'sender' => [ + 'name' => 'Sender Name', + 'email' => 'm1111ytest@mail.com', + 'message' => 'test message', + ], + 'recipients' => [ + 'name' => [ + 'recipient_name', + ], + 'email' => [ + 'recipient_email@example.com', + ], + ], + 'expected_result' => true, + ], + 'empty_message' => [ + 'sender' => [ + 'name' => 'Sender Name', + 'email' => 'm1111ytest@mail.com', + 'message' => '', + ], + 'recipients' => [ + 'name' => [ + 'recipient name', + ], + 'email' => [ + 'recipient_email@example.com', + ], + ], + 'expected_result' => 'Please enter a message.', + ], + 'empty_sender_name' => [ + 'sender' => [ + 'name' => '', + 'email' => 'customer_email@example.com', + 'message' => 'test message', + ], + 'recipients' => [ + 'name' => [ + 'recipient name', + ], + 'email' => [ + 'recipient_email@example.com', + ], + ], + 'expected_result' => 'Please enter a sender name.', + ], + 'empty_recipients' => [ + 'sender' => [ + 'name' => 'Sender Name', + 'email' => 'm1111ytest@mail.com', + 'message' => 'test message', + ], + 'recipients' => [ + 'name' => [], + 'email' => [], + ], + 'expected_result' => 'Please specify at least one recipient.', + ], + 'wrong_recipient_email' => [ + 'sender' => [ + 'name' => 'Sender Name', + 'email' => 'm1111ytest@mail.com', + 'message' => 'test message', + ], + 'recipients' => [ + 'name' => [ + 'recipient name', + ], + 'email' => [ + '123123', + ], + ], + 'expected_result' => 'Please enter a correct recipient email address.', + ], + 'to_much_recipients' => [ + 'sender' => [ + 'name' => 'Sender Name', + 'email' => 'm1111ytest@mail.com', + 'message' => 'test message', + ], + 'recipients' => [ + 'name' => [ + 'recipient name', + 'second name', + ], + 'email' => [ + 'recipient_email@example.com', + 'recipient2_email@example.com', + ], + ], + 'expected_result' => 'No more than 1 emails can be sent at a time.', + ], + ]; + } + + /** + * @magentoConfigFixture current_store sendfriend/email/check_by 0 + * @magentoConfigFixture current_store sendfriend/email/max_per_hour 1 + * + * @return void + */ + public function testisExceedLimitByCookies(): void + { + $this->cookieManager->setPublicCookie(SendFriendHelper::COOKIE_NAME, (string)time()); + $this->assertTrue($this->sendFriend->isExceedLimit()); + } + + /** + * @magentoConfigFixture current_store sendfriend/email/check_by 1 + * @magentoConfigFixture current_store sendfriend/email/max_per_hour 1 + * + * @magentoDataFixture Magento/SendFriend/_files/sendfriend_log_record_half_hour_before.php + * + * @magentoDbIsolation disabled + * @return void + */ + public function testisExceedLimitByIp(): void + { + $this->markTestSkipped('Blocked by MC-31968'); + $parameters = $this->objectManager->create(Parameters::class); + $parameters->set('REMOTE_ADDR', '127.0.0.1'); + $this->request->setServer($parameters); + $this->assertTrue($this->sendFriend->isExceedLimit()); + } + + /** + * Check result + * + * @param array|bool $expectedResult + * @param array|bool $result + * @return void + */ + private function checkResult($expectedResult, $result): void + { + if ($expectedResult === true) { + $this->assertTrue($result); + } else { + $this->assertEquals($expectedResult, (string)reset($result) ?? ''); + } + } + + /** + * Prepare sender and recipient data + * + * @param array $sender + * @param array $recipients + * @return void + */ + private function prepareData(array $sender, array $recipients): void + { + $this->sendFriend->setSender($sender); + $this->sendFriend->setRecipients($recipients); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/_files/sendfriend_log_record_half_hour_before.php b/dev/tests/integration/testsuite/Magento/SendFriend/_files/sendfriend_log_record_half_hour_before.php new file mode 100644 index 0000000000000..132cbe97d43ee --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SendFriend/_files/sendfriend_log_record_half_hour_before.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\SendFriend\Model\ResourceModel\SendFriend as SendFriendResource; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$baseWebsiteId = $websiteRepository->get('base')->getId(); +$ip = ip2long('127.0.0.1'); +$updateDatetime = new \DateTime('-0.5 hours'); +/** @var SendFriendResource $sendFriendResource */ +$sendFriendResource = $objectManager->get(SendFriendResource::class); +$sendFriendResource->addSendItem($ip, $updateDatetime->getTimestamp(), $baseWebsiteId); diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/_files/sendfriend_log_record_half_hour_before_rollback.php b/dev/tests/integration/testsuite/Magento/SendFriend/_files/sendfriend_log_record_half_hour_before_rollback.php new file mode 100644 index 0000000000000..9a700f20bf92c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SendFriend/_files/sendfriend_log_record_half_hour_before_rollback.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\SendFriend\Model\DeleteLogRowsByIp; + +/** @var DeleteLogRowsByIp $deleteLogRowsByIp */ +$deleteLogRowsByIp = Bootstrap::getObjectManager()->get(DeleteLogRowsByIp::class); +$deleteLogRowsByIp->execute('127.0.0.1'); From 4da150c13e2540a886ddf1cfaf74ba9e2b167eea Mon Sep 17 00:00:00 2001 From: Vaha <vaha@atwix.com> Date: Mon, 16 Mar 2020 16:01:31 +0200 Subject: [PATCH 313/369] Added improvements to category repository (save method) to fix level issue --- .../Catalog/Model/CategoryRepository.php | 1 + ...ntChildCategoryTreeElementsActionGroup.xml | 22 ++++++++++ .../AdminExpandCategoryTreeActionGroup.xml | 19 ++++++++ .../AdminOpenCategoryPageActionGroup.xml | 19 ++++++++ ...yLevelByParentCategoryLevelActionGroup.xml | 22 ++++++++++ .../Catalog/Test/Mftf/Data/CategoryData.xml | 6 +++ .../AdminCategorySidebarTreeSection.xml | 1 + ...inCheckNewCategoryLevelAddedViaApiTest.xml | 44 +++++++++++++++++++ .../Unit/Model/CategoryRepositoryTest.php | 2 +- 9 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertParentChildCategoryTreeElementsActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandCategoryTreeActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenCategoryPageActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategoryLevelByParentCategoryLevelActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckNewCategoryLevelAddedViaApiTest.xml diff --git a/app/code/Magento/Catalog/Model/CategoryRepository.php b/app/code/Magento/Catalog/Model/CategoryRepository.php index a8636306f5e5b..0ce52b966c32c 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository.php @@ -108,6 +108,7 @@ public function save(\Magento\Catalog\Api\Data\CategoryInterface $category) $parentCategory = $this->get($parentId, $storeId); $existingData['path'] = $parentCategory->getPath(); $existingData['parent_id'] = $parentId; + $existingData['level'] = null; } $category->addData($existingData); try { diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertParentChildCategoryTreeElementsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertParentChildCategoryTreeElementsActionGroup.xml new file mode 100644 index 0000000000000..b1ed08db05b9a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertParentChildCategoryTreeElementsActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertParentChildCategoryTreeElementsActionGroup"> + <annotations> + <description>Checks category tree, parent category has child category element.</description> + </annotations> + <arguments> + <argument name="parentCategoryName" type="string" defaultValue="parent"/> + <argument name="childCategoryName" type="string" defaultValue="child"/> + </arguments> + + <seeElement selector="{{AdminCategorySidebarTreeSection.childCategoryUnderParent(parentCategoryName, childCategoryName)}}" stepKey="seeSubcategoryIsUnderParent"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandCategoryTreeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandCategoryTreeActionGroup.xml new file mode 100644 index 0000000000000..f2cce9b9a42fe --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandCategoryTreeActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminExpandCategoryTreeActionGroup"> + <annotations> + <description>Expands category tree.</description> + </annotations> + + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <waitForPageLoad stepKey="waitForCategoryToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenCategoryPageActionGroup.xml new file mode 100644 index 0000000000000..14c4f5234ba67 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenCategoryPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenCategoryPageActionGroup"> + <annotations> + <description>Navigates to category page.</description> + </annotations> + + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategoryLevelByParentCategoryLevelActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategoryLevelByParentCategoryLevelActionGroup.xml new file mode 100644 index 0000000000000..1830a6abc992e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminCategoryLevelByParentCategoryLevelActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminCategoryLevelByParentCategoryLevelActionGroup"> + <annotations> + <description>Checks category level by parent category level.</description> + </annotations> + <arguments> + <argument name="parentCategoryLevel" type="string" defaultValue="2"/> + <argument name="categoryLevel" type="string" defaultValue="3"/> + </arguments> + + <assertEquals expected="{{parentCategoryLevel}} + 1" actual="{{categoryLevel}}" message="wrongCategoryLevel" stepKey="compareCategoryLevel"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index f54ce9af83e88..976ed5a3ed17a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -243,4 +243,10 @@ <data key="include_in_menu">true</data> <var key="parent_id" entityType="category" entityKey="id"/> </entity> + <entity name="ApiSubCategoryWithLevelZero" type="category"> + <data key="name" unique="suffix">cat with level 1</data> + <data key="is_active">true</data> + <data key="level">0</data> + <var key="parent_id" entityType="category" entityKey="id"/> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index 304c34b404ea5..c35e775152ac9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -16,6 +16,7 @@ <element name="categoryTreeRoot" type="text" selector="div.x-tree-root-node>li.x-tree-node:first-of-type>div.x-tree-node-el:first-of-type" timeout="30"/> <element name="categoryInTree" type="text" selector="//a/span[contains(text(), '{{name}}')]" parameterized="true" timeout="30"/> <element name="categoryInTreeUnderRoot" type="text" selector="//li/ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{name}}')]" parameterized="true"/> + <element name="childCategoryUnderParent" type="text" selector="//li/ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{parentCategoryName}}')]/../../../ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{childCategoryName}}')]" parameterized="true"/> <element name="lastCreatedCategory" type="block" selector=".x-tree-root-ct li li:last-child" /> <element name="treeContainer" type="block" selector=".tree-holder" /> <element name="expandRootCategory" type="text" selector="img.x-tree-elbow-end-plus"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckNewCategoryLevelAddedViaApiTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckNewCategoryLevelAddedViaApiTest.xml new file mode 100644 index 0000000000000..427ef6551ce9b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckNewCategoryLevelAddedViaApiTest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCheckNewCategoryLevelAddedViaApiTest"> + <annotations> + <stories value="Add parent and child categories via API"/> + <title value="Add parent and child categories via API"/> + <description value="Login as admin, create parent and child categories via API. + Check category level for child category entity based on parent level. + Check category tree: parent element has child element. "/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginToAdminPanel"/> + <createData entity="ApiCategoryWithChildren" stepKey="createCategoryWithChildrenBlank"/> + <createData entity="ApiSubCategoryWithLevelZero" stepKey="createSubCategoryWithLevelZero"> + <requiredEntity createDataKey="createCategoryWithChildrenBlank"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategoryWithChildrenBlank" stepKey="deleteCategoryWithChildrenBlank"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="AssertAdminCategoryLevelByParentCategoryLevelActionGroup" stepKey="assertCategoryLevelByParentCategory"> + <argument name="parentCategoryLevel" value="$createCategoryWithChildrenBlank.level$"/> + <argument name="categoryLevel" value="$createSubCategoryWithLevelZero.level$"/> + </actionGroup> + + <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="openCategoryPage"/> + <actionGroup ref="AdminExpandCategoryTreeActionGroup" stepKey="expandCategoryTree"/> + <actionGroup ref="AdminAssertParentChildCategoryTreeElementsActionGroup" stepKey="assertParentChildCategoryTreeElements"> + <argument name="parentCategoryName" value="$createCategoryWithChildrenBlank.name$"/> + <argument name="childCategoryName" value="$createSubCategoryWithLevelZero.name$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php index 864b91b20d017..3799e6e5fa4aa 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryRepositoryTest.php @@ -204,7 +204,7 @@ public function testCreateNewCategory() $parentCategoryId = 15; $newCategoryId = 25; $categoryData = ['level' => '1', 'path' => '1/2', 'parent_id' => 1, 'name' => 'category']; - $dataForSave = ['store_id' => 1, 'name' => 'category', 'path' => 'path', 'parent_id' => 15]; + $dataForSave = ['store_id' => 1, 'name' => 'category', 'path' => 'path', 'parent_id' => 15, 'level' => null]; $this->extensibleDataObjectConverterMock ->expects($this->once()) ->method('toNestedArray') From dc27742d408aa144c85b8ef49318113228c43f1c Mon Sep 17 00:00:00 2001 From: Yurii Sapiha <yurasapiga93@gmail.com> Date: Mon, 16 Mar 2020 17:15:43 +0200 Subject: [PATCH 314/369] MC-32136: Email product to friend --- .../testsuite/Magento/SendFriend/Model/SendFriendTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php index 52b2ed05baf9e..9a9ef6440261a 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Model/SendFriendTest.php @@ -7,13 +7,13 @@ namespace Magento\SendFriend\Model; -use Laminas\Stdlib\Parameters; use Magento\Framework\App\RequestInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Stdlib\CookieManagerInterface; use Magento\SendFriend\Helper\Data as SendFriendHelper; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +use Zend\Stdlib\Parameters; /** * Class checks send friend model behavior From 4ddb85cc52fa0c2dca3c6548a799b52f02ce0579 Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Mon, 16 Mar 2020 12:18:40 -0500 Subject: [PATCH 315/369] MC-13825: [2.4.x] Migrate ZF2 components to Laminas -- fix merge conflict --- .../Test/Unit/Controller/RouterTest.php | 324 +++++++++++------- 1 file changed, 199 insertions(+), 125 deletions(-) diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php index cd2725f218aae..a41d9d38bdcb3 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Controller/RouterTest.php @@ -6,27 +6,35 @@ namespace Magento\UrlRewrite\Test\Unit\Controller; use Magento\Framework\App\Action\Forward; +use Magento\Framework\App\Action\Redirect; +use Magento\Framework\App\ActionFactory; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\ResponseInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\UrlInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\UrlRewrite\Controller\Router; +use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\Store\Model\Store; -use PHPUnit\Framework\MockObject\MockObject; use Laminas\Stdlib\ParametersInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * Test class for UrlRewrite Controller Router * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class RouterTest extends \PHPUnit\Framework\TestCase +class RouterTest extends TestCase { /** - * @var \Magento\UrlRewrite\Controller\Router + * @var Router */ private $router; /** - * @var \Magento\Framework\App\ActionFactory|MockObject + * @var ActionFactory|MockObject */ private $actionFactory; @@ -36,7 +44,7 @@ class RouterTest extends \PHPUnit\Framework\TestCase private $url; /** - * @var \Magento\Store\Model\StoreManagerInterface|MockObject + * @var StoreManagerInterface|MockObject */ private $storeManager; @@ -46,12 +54,12 @@ class RouterTest extends \PHPUnit\Framework\TestCase private $store; /** - * @var \Magento\Framework\App\ResponseInterface|MockObject + * @var ResponseInterface|MockObject */ private $response; /** - * @var \Magento\Framework\App\RequestInterface|MockObject + * @var RequestInterface|MockObject */ private $request; @@ -61,7 +69,7 @@ class RouterTest extends \PHPUnit\Framework\TestCase private $requestQuery; /** - * @var \Magento\UrlRewrite\Model\UrlFinderInterface|MockObject + * @var UrlFinderInterface|MockObject */ private $urlFinder; @@ -71,24 +79,24 @@ class RouterTest extends \PHPUnit\Framework\TestCase protected function setUp() { $objectManager = new ObjectManager($this); - $this->actionFactory = $this->createMock(\Magento\Framework\App\ActionFactory::class); + $this->actionFactory = $this->createMock(ActionFactory::class); $this->url = $this->createMock(UrlInterface::class); - $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); $this->response = $this->createPartialMock( - \Magento\Framework\App\ResponseInterface::class, + ResponseInterface::class, ['setRedirect', 'sendResponse'] ); $this->requestQuery = $this->createMock(ParametersInterface::class); $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) ->disableOriginalConstructor()->getMock(); $this->request->method('getQuery')->willReturn($this->requestQuery); - $this->urlFinder = $this->createMock(\Magento\UrlRewrite\Model\UrlFinderInterface::class); + $this->urlFinder = $this->createMock(UrlFinderInterface::class); $this->store = $this->getMockBuilder( Store::class )->disableOriginalConstructor()->getMock(); $this->router = $objectManager->getObject( - \Magento\UrlRewrite\Controller\Router::class, + Router::class, [ 'actionFactory' => $this->actionFactory, 'url' => $this->url, @@ -104,9 +112,16 @@ protected function setUp() */ public function testNoRewriteExist() { - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue(null)); - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); - $this->store->expects($this->any())->method('getId')->will($this->returnValue('current-store-id')); + $this->request->method('getPathInfo') + ->willReturn(''); + $this->request->method('getRequestString') + ->willReturn(''); + $this->urlFinder->method('findOneByData') + ->willReturn(null); + $this->storeManager->method('getStore') + ->willReturn($this->store); + $this->store->method('getId') + ->willReturn(1); $this->assertNull($this->router->match($this->request)); } @@ -118,55 +133,45 @@ public function testRewriteAfterStoreSwitcher() { $initialRequestPath = 'request-path'; $newRequestPath = 'new-request-path'; + $newTargetPath = 'new-target-path'; $oldStoreAlias = 'old-store'; $oldStoreId = 'old-store-id'; $currentStoreId = 'current-store-id'; $rewriteEntityType = 'entity-type'; $rewriteEntityId = 42; - $this->request - ->expects($this->any()) - ->method('getParam') + $this->request->method('getParam') ->with('___from_store') ->willReturn($oldStoreAlias); - $this->request - ->expects($this->any()) - ->method('getPathInfo') + $this->request->method('getPathInfo') + ->willReturn($initialRequestPath); + $this->request->method('getRequestString') ->willReturn($initialRequestPath); $oldStore = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->getMock(); - $oldStore->expects($this->any()) - ->method('getId') + $oldStore->method('getId') ->willReturn($oldStoreId); - $this->store - ->expects($this->any()) - ->method('getId') + $this->store->method('getId') ->willReturn($currentStoreId); - $this->storeManager - ->expects($this->any()) - ->method('getStore') + $this->storeManager->method('getStore') ->willReturnMap([[$oldStoreAlias, $oldStore], [null, $this->store]]); $oldUrlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor() ->getMock(); - $oldUrlRewrite->expects($this->any()) - ->method('getEntityType') + $oldUrlRewrite->method('getEntityType') ->willReturn($rewriteEntityType); - $oldUrlRewrite->expects($this->any()) - ->method('getEntityId') + $oldUrlRewrite->method('getEntityId') ->willReturn($rewriteEntityId); - $oldUrlRewrite->expects($this->any()) - ->method('getRedirectType') + $oldUrlRewrite->method('getRedirectType') ->willReturn(0); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor() ->getMock(); - $urlRewrite->expects($this->any()) - ->method('getRequestPath') + $urlRewrite->method('getRequestPath') ->willReturn($newRequestPath); - $this->urlFinder - ->expects($this->any()) - ->method('findOneByData') + $urlRewrite->method('getTargetPath') + ->willReturn($newTargetPath); + $this->urlFinder->method('findOneByData') ->willReturnMap( [ [ @@ -190,22 +195,23 @@ public function testRewriteAfterStoreSwitcher() */ public function testNoRewriteAfterStoreSwitcherWhenNoOldRewrite() { - $this->request->expects($this->any())->method('getPathInfo')->will($this->returnValue('request-path')); - $this->request->expects($this->any())->method('getParam')->with('___from_store') - ->will($this->returnValue('old-store')); + $this->request->method('getPathInfo')->willReturn('request-path'); + $this->request->method('getRequestString')->willReturn('request-path'); + $this->request->method('getParam')->with('___from_store') + ->willReturn('old-store'); $oldStore = $this->getMockBuilder(Store::class)->disableOriginalConstructor()->getMock(); - $this->storeManager->expects($this->any())->method('getStore') - ->will($this->returnValueMap([['old-store', $oldStore], [null, $this->store]])); - $oldStore->expects($this->any())->method('getId')->will($this->returnValue('old-store-id')); - $this->store->expects($this->any())->method('getId')->will($this->returnValue('current-store-id')); + $this->storeManager->method('getStore') + ->willReturnMap([['old-store', $oldStore], [null, $this->store]]); + $oldStore->method('getId')->willReturn('old-store-id'); + $this->store->method('getId')->willReturn('current-store-id'); $oldUrlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $oldUrlRewrite->expects($this->any())->method('getEntityType')->will($this->returnValue('entity-type')); - $oldUrlRewrite->expects($this->any())->method('getEntityId')->will($this->returnValue('entity-id')); - $oldUrlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('request-path')); + $oldUrlRewrite->method('getEntityType')->willReturn('entity-type'); + $oldUrlRewrite->method('getEntityId')->willReturn('entity-id'); + $oldUrlRewrite->method('getRequestPath')->willReturn('request-path'); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('request-path')); + $urlRewrite->method('getRequestPath')->willReturn('request-path'); $this->assertNull($this->router->match($this->request)); } @@ -215,41 +221,40 @@ public function testNoRewriteAfterStoreSwitcherWhenNoOldRewrite() */ public function testNoRewriteAfterStoreSwitcherWhenOldRewriteEqualsToNewOne() { - $this->request->expects($this->any())->method('getPathInfo')->will($this->returnValue('request-path')); - $this->request->expects($this->any())->method('getParam')->with('___from_store') - ->will($this->returnValue('old-store')); + $this->request->method('getPathInfo')->willReturn('request-path'); + $this->request->method('getRequestString')->willReturn('request-path'); + $this->request->method('getParam')->with('___from_store') + ->willReturn('old-store'); $oldStore = $this->getMockBuilder(Store::class)->disableOriginalConstructor()->getMock(); - $this->storeManager->expects($this->any())->method('getStore') - ->will($this->returnValueMap([['old-store', $oldStore], [null, $this->store]])); - $oldStore->expects($this->any())->method('getId')->will($this->returnValue('old-store-id')); - $this->store->expects($this->any())->method('getId')->will($this->returnValue('current-store-id')); + $this->storeManager->method('getStore') + ->willReturnMap([['old-store', $oldStore], [null, $this->store]]); + $oldStore->method('getId')->willReturn('old-store-id'); + $this->store->method('getId')->willReturn('current-store-id'); $oldUrlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $oldUrlRewrite->expects($this->any())->method('getEntityType')->will($this->returnValue('entity-type')); - $oldUrlRewrite->expects($this->any())->method('getEntityId')->will($this->returnValue('entity-id')); - $oldUrlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('old-request-path')); + $oldUrlRewrite->method('getEntityType')->willReturn('entity-type'); + $oldUrlRewrite->method('getEntityId')->willReturn('entity-id'); + $oldUrlRewrite->method('getRequestPath')->willReturn('old-request-path'); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('old-request-path')); + $urlRewrite->method('getRequestPath')->willReturn('old-request-path'); - $this->urlFinder->expects($this->any())->method('findOneByData')->will( - $this->returnValueMap( + $this->urlFinder->method('findOneByData')->willReturnMap( + [ + [ + [UrlRewrite::REQUEST_PATH => 'request-path', UrlRewrite::STORE_ID => 'old-store-id'], + $oldUrlRewrite, + ], [ [ - [UrlRewrite::REQUEST_PATH => 'request-path', UrlRewrite::STORE_ID => 'old-store-id'], - $oldUrlRewrite, - ], - [ - [ - UrlRewrite::ENTITY_TYPE => 'entity-type', - UrlRewrite::ENTITY_ID => 'entity-id', - UrlRewrite::STORE_ID => 'current-store-id', - UrlRewrite::IS_AUTOGENERATED => 1, - ], - $urlRewrite + UrlRewrite::ENTITY_TYPE => 'entity-type', + UrlRewrite::ENTITY_ID => 'entity-id', + UrlRewrite::STORE_ID => 'current-store-id', + UrlRewrite::IS_AUTOGENERATED => 1, ], - ] - ) + $urlRewrite + ], + ] ); $this->assertNull($this->router->match($this->request)); @@ -261,51 +266,107 @@ public function testNoRewriteAfterStoreSwitcherWhenOldRewriteEqualsToNewOne() public function testMatchWithRedirect() { $queryParams = []; - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); + $redirectType = 'redirect-code'; + $requestPath = 'request-path'; + $targetPath = 'target-path'; + $newTargetPath = 'new-target-path'; + $this->storeManager->method('getStore') + ->willReturn($this->store); + $this->request->method('getPathInfo') + ->willReturn($requestPath); + $this->request->method('getRequestString') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) - ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getRedirectType')->will($this->returnValue('redirect-code')); - $urlRewrite->expects($this->any())->method('getTargetPath')->will($this->returnValue('target-path')); - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue($urlRewrite)); - $this->response->expects($this->once())->method('setRedirect') - ->with('new-target-path', 'redirect-code'); - $this->request->expects($this->once())->method('getParams')->willReturn($queryParams); - $this->url->expects($this->once())->method('getUrl')->with( - '', - ['_direct' => 'target-path', '_query' => $queryParams] - ) - ->will($this->returnValue('new-target-path')); - $this->request->expects($this->once())->method('setDispatched')->with(true); - $this->actionFactory->expects($this->once())->method('create') - ->with(\Magento\Framework\App\Action\Redirect::class); + ->disableOriginalConstructor() + ->getMock(); + $urlRewrite->method('getRedirectType')->willReturn($redirectType); + $urlRewrite->method('getRequestPath')->willReturn($requestPath); + $urlRewrite->method('getTargetPath')->willReturn($targetPath); + $this->urlFinder->method('findOneByData')->willReturn($urlRewrite); + $this->response->expects($this->once()) + ->method('setRedirect') + ->with($newTargetPath, $redirectType); + $this->request->expects($this->once()) + ->method('getParams') + ->willReturn($queryParams); + $this->url->expects($this->once()) + ->method('getUrl') + ->with( + '', + ['_direct' => $targetPath, '_query' => $queryParams] + ) + ->willReturn($newTargetPath); + $this->request->expects($this->once()) + ->method('setDispatched') + ->with(true); + $this->actionFactory->expects($this->once()) + ->method('create') + ->with(Redirect::class); $this->router->match($this->request); } /** - * @return void + * @param string $requestPath + * @param string $targetPath + * @param bool $shouldRedirect + * @dataProvider customInternalRedirectDataProvider */ - public function testMatchWithCustomInternalRedirect() + public function testMatchWithCustomInternalRedirect($requestPath, $targetPath, $shouldRedirect) { $queryParams = []; - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); + $redirectType = 'redirect-code'; + $this->storeManager->method('getStore') + ->willReturn($this->store); + $this->request->method('getPathInfo') + ->willReturn($requestPath); + $this->request->method('getRequestString') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) - ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getEntityType')->will($this->returnValue('custom')); - $urlRewrite->expects($this->any())->method('getRedirectType')->will($this->returnValue('redirect-code')); - $urlRewrite->expects($this->any())->method('getTargetPath')->will($this->returnValue('target-path')); - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue($urlRewrite)); - $this->request->expects($this->any())->method('getParams')->willReturn($queryParams); - $this->response->expects($this->once())->method('setRedirect')->with('a', 'redirect-code'); - $this->url->expects($this->once())->method('getUrl')->with( - '', - ['_direct' => 'target-path', '_query' => $queryParams] - )->willReturn('a'); - $this->request->expects($this->once())->method('setDispatched')->with(true); - $this->actionFactory->expects($this->once())->method('create') - ->with(\Magento\Framework\App\Action\Redirect::class); + ->disableOriginalConstructor() + ->getMock(); + $urlRewrite->method('getEntityType')->willReturn('custom'); + $urlRewrite->method('getRedirectType')->willReturn($redirectType); + $urlRewrite->method('getRequestPath')->willReturn($requestPath); + $urlRewrite->method('getTargetPath')->willReturn($targetPath); + $this->urlFinder->method('findOneByData')->willReturn($urlRewrite); - $this->router->match($this->request); + if ($shouldRedirect) { + $this->request->method('getParams')->willReturn($queryParams); + $this->response->expects($this->once()) + ->method('setRedirect') + ->with('a', $redirectType); + $this->url->expects($this->once()) + ->method('getUrl') + ->with( + '', + ['_direct' => $targetPath, '_query' => $queryParams] + ) + ->willReturn('a'); + $this->request->expects($this->once()) + ->method('setDispatched') + ->with(true); + $this->actionFactory->expects($this->once()) + ->method('create') + ->with(Redirect::class); + } + + $routerResult = $this->router->match($this->request); + + if (!$shouldRedirect) { + $this->assertNull($routerResult); + } + } + + /** + * @return array + */ + public function customInternalRedirectDataProvider() + { + return [ + ['request-path', 'target-path', true], + ['/', '/', false], + ]; } /** @@ -314,19 +375,27 @@ public function testMatchWithCustomInternalRedirect() */ public function testMatchWithCustomExternalRedirect($targetPath) { - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); + $requestPath = 'request-path'; + $this->storeManager->method('getStore')->willReturn($this->store); + $this->request->method('getPathInfo') + ->willReturn($requestPath); + $this->request->method('getRequestString') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getEntityType')->will($this->returnValue('custom')); - $urlRewrite->expects($this->any())->method('getRedirectType')->will($this->returnValue('redirect-code')); - $urlRewrite->expects($this->any())->method('getTargetPath')->will($this->returnValue($targetPath)); - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue($urlRewrite)); - $this->response->expects($this->once())->method('setRedirect')->with($targetPath, 'redirect-code'); + $urlRewrite->method('getEntityType')->willReturn('custom'); + $urlRewrite->method('getRedirectType')->willReturn('redirect-code'); + $urlRewrite->method('getRequestPath')->willReturn($requestPath); + $urlRewrite->method('getTargetPath')->willReturn($targetPath); + $this->urlFinder->method('findOneByData')->willReturn($urlRewrite); + $this->response->expects($this->once()) + ->method('setRedirect') + ->with($targetPath, 'redirect-code'); $this->request->expects($this->never())->method('getParams'); $this->url->expects($this->never())->method('getUrl'); $this->request->expects($this->once())->method('setDispatched')->with(true); $this->actionFactory->expects($this->once())->method('create') - ->with(\Magento\Framework\App\Action\Redirect::class); + ->with(Redirect::class); $this->router->match($this->request); } @@ -347,18 +416,23 @@ public function externalRedirectTargetPathDataProvider() */ public function testMatch() { - $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($this->store)); + $requestPath = 'request-path'; + $this->storeManager->method('getStore')->willReturn($this->store); + $this->request->method('getPathInfo') + ->willReturn($requestPath); + $this->request->method('getRequestString') + ->willReturn($requestPath); $urlRewrite = $this->getMockBuilder(UrlRewrite::class) ->disableOriginalConstructor()->getMock(); - $urlRewrite->expects($this->any())->method('getRedirectType')->will($this->returnValue(0)); - $urlRewrite->expects($this->any())->method('getTargetPath')->will($this->returnValue('target-path')); - $urlRewrite->expects($this->any())->method('getRequestPath')->will($this->returnValue('request-path')); - $this->urlFinder->expects($this->any())->method('findOneByData')->will($this->returnValue($urlRewrite)); + $urlRewrite->method('getRedirectType')->willReturn(0); + $urlRewrite->method('getRequestPath')->willReturn($requestPath); + $urlRewrite->method('getTargetPath')->willReturn('target-path'); + $this->urlFinder->method('findOneByData')->willReturn($urlRewrite); $this->request->expects($this->once())->method('setPathInfo')->with('/target-path'); $this->request->expects($this->once())->method('setAlias') ->with(UrlInterface::REWRITE_REQUEST_PATH_ALIAS, 'request-path'); $this->actionFactory->expects($this->once())->method('create') - ->with(\Magento\Framework\App\Action\Forward::class); + ->with(Forward::class); $this->router->match($this->request); } From a5cfbaa7f7446b3f74691f1a4124b9c7943b75f0 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Mon, 16 Mar 2020 12:51:19 -0500 Subject: [PATCH 316/369] MC-31987: Varnish graphql cache has to skip authenticated requests - fix static --- .../GraphQl/SendFriend/StoreConfigTest.php | 30 +++++++++++++++---- .../HTTP/PhpEnvironment/Response.php | 5 ---- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php index e1c475d2ea059..9d87afb2ddec9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/StoreConfigTest.php @@ -76,12 +76,30 @@ public function testSendFriendEnabledGuestEnabled() */ private function assertResponse(array $expectedValues, array $response) { - $this->assertArrayNotHasKey('errors', $response); - $this->assertArrayHasKey('send_friend', $response['storeConfig']); - $this->assertArrayHasKey('enabled_for_customers', $response['storeConfig']['send_friend']); - $this->assertArrayHasKey('enabled_for_guests', $response['storeConfig']['send_friend']); - $this->assertEquals($expectedValues['enabled_for_customers'], $response['storeConfig']['send_friend']['enabled_for_customers']); - $this->assertEquals($expectedValues['enabled_for_guests'], $response['storeConfig']['send_friend']['enabled_for_guests']); + $this->assertArrayNotHasKey( + 'errors', + $response + ); + $this->assertArrayHasKey( + 'send_friend', + $response['storeConfig'] + ); + $this->assertArrayHasKey( + 'enabled_for_customers', + $response['storeConfig']['send_friend'] + ); + $this->assertArrayHasKey( + 'enabled_for_guests', + $response['storeConfig']['send_friend'] + ); + $this->assertEquals( + $expectedValues['enabled_for_customers'], + $response['storeConfig']['send_friend']['enabled_for_customers'] + ); + $this->assertEquals( + $expectedValues['enabled_for_guests'], + $response['storeConfig']['send_friend']['enabled_for_guests'] + ); } /** diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php index 42405256daa62..dfc68cf975d50 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php @@ -19,11 +19,6 @@ class Response extends \Zend\Http\PhpEnvironment\Response implements \Magento\Fr */ protected $isRedirect = false; - /** - * @var bool - */ - private $headersSent; - /** * @inheritdoc */ From 1b014f8272390816c9a1c7edc038c80c11bbe5f7 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 16 Mar 2020 19:24:11 +0100 Subject: [PATCH 317/369] Integration Tests for Authentication feature on Customer Account --- .../Controller/AuthenticationTest.php | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php new file mode 100644 index 0000000000000..3ccd415a16c4f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Controller; + +use Magento\Customer\Controller\Plugin\Account as AccountPlugin; +use Magento\TestFramework\TestCase\AbstractController; + +/** + * Set of Tests to verify that Authentication methods work properly + */ +class AuthenticationTest extends AbstractController +{ + /** + * Make sure that customized AccountPlugin was reverted. + */ + protected function tearDown() + { + $this->resetAllowedActions(); + parent::tearDown(); + } + + /** + * After changes to `di.xml` and overriding list of allowed actions, unallowed ones should cause redirect. + */ + public function testExpectRedirectResponseWhenDispatchNotAllowedAction() + { + $this->overrideAllowedActions(['notExistingRoute']); + + $this->dispatch('customer/account/create'); + $this->assertRedirect($this->stringContains('customer/account/login')); + } + + /** + * Allowed actions should be displayed + */ + public function testExpectPageDispatchWhenAllowedAction() + { + $this->overrideAllowedActions(['create']); + + $this->dispatch('customer/account/create'); + $this->assertEquals(200, $this->getResponse()->getStatusCode()); + } + + /** + * Overrides list of `allowedActions` for Authorization Plugin + * + * @param string[] $allowedActions + * @see \Magento\Customer\Controller\Plugin\Account + */ + private function overrideAllowedActions(array $allowedActions): void + { + $allowedActions = array_combine($allowedActions, $allowedActions); + $pluginMock = $this->_objectManager->create(AccountPlugin::class, ['allowedActions' => $allowedActions]); + $this->_objectManager->addSharedInstance($pluginMock, AccountPlugin::class); + } + + /** + * Removes all the customizations applied to `allowedActions` + * @see \Magento\Customer\Controller\Plugin\Account + */ + private function resetAllowedActions() + { + $this->_objectManager->removeSharedInstance(AccountPlugin::class); + } +} From 65ac7b743f8e02105bedecc5a2ff7048e53cca0f Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 16 Mar 2020 19:27:30 +0100 Subject: [PATCH 318/369] Invalid name (was `Mock` instead of `Fake`) --- .../Magento/Customer/Controller/AuthenticationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php index 3ccd415a16c4f..83f30e730e683 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php @@ -55,8 +55,8 @@ public function testExpectPageDispatchWhenAllowedAction() private function overrideAllowedActions(array $allowedActions): void { $allowedActions = array_combine($allowedActions, $allowedActions); - $pluginMock = $this->_objectManager->create(AccountPlugin::class, ['allowedActions' => $allowedActions]); - $this->_objectManager->addSharedInstance($pluginMock, AccountPlugin::class); + $pluginFake = $this->_objectManager->create(AccountPlugin::class, ['allowedActions' => $allowedActions]); + $this->_objectManager->addSharedInstance($pluginFake, AccountPlugin::class); } /** From fe21a4348816c95397887ea13690579f39b734c2 Mon Sep 17 00:00:00 2001 From: Lukasz Bajsarowicz <lukasz.bajsarowicz@gmail.com> Date: Mon, 16 Mar 2020 19:30:23 +0100 Subject: [PATCH 319/369] Rename Test to be more meaningful --- .../Magento/Customer/Controller/AuthenticationTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php index 83f30e730e683..822b3ab8f35d1 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AuthenticationTest.php @@ -36,9 +36,9 @@ public function testExpectRedirectResponseWhenDispatchNotAllowedAction() } /** - * Allowed actions should be displayed + * Allowed actions should be rendered normally */ - public function testExpectPageDispatchWhenAllowedAction() + public function testExpectPageResponseWhenAllowedAction() { $this->overrideAllowedActions(['create']); From de842be659583d7ae050738f5966f9300874a97b Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Mon, 16 Mar 2020 13:37:42 -0500 Subject: [PATCH 320/369] MC-13825: [2.4.x] Migrate ZF2 components to Laminas --- .../Setup/Patch/Data/AddDownloadableHostsConfig.php | 2 ++ .../Test/Legacy/_files/blacklist/obsolete_mage.php | 3 ++- .../Magento/Test/Legacy/_files/obsolete_namespaces.php | 1 + .../Code/Test/Unit/Generator/ClassGeneratorTest.php | 9 ++++++--- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php b/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php index b962e3af6e0aa..48a0eebf0205b 100644 --- a/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php +++ b/app/code/Magento/Downloadable/Setup/Patch/Data/AddDownloadableHostsConfig.php @@ -142,6 +142,8 @@ public function apply() } $this->domainManager->addDomains($this->whitelist); + + return $this; } /** diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php index 62816cd4e4f76..e4d5407a341b9 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/blacklist/obsolete_mage.php @@ -11,5 +11,6 @@ 'lib/internal/Magento/Framework/ObjectManager/Test/Unit/Factory/CompiledTest.php', 'dev/tests/integration/testsuite/Magento/Indexer/Model/Config/_files/result.php', 'lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php', - 'lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php' + 'lib/internal/Magento/Framework/Encryption/Test/Unit/CryptTest.php', + 'setup/src/Zend/Mvc/Controller/LazyControllerAbstractFactory.php', ]; diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_namespaces.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_namespaces.php index 0d9da334ea3ab..f2411cdad86de 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_namespaces.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_namespaces.php @@ -200,4 +200,5 @@ ['Magento\BraintreeTwo', 'Magento\Braintree'], ['Magento\MysqlMq\Model\Resource', 'Magento\MysqlMq\Model\ResourceModel'], ['Magento\BulkOperations', 'Magento\AsynchronousOperations'], + ['Zend', 'Laminas'], ]; diff --git a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php index bdcd834527c91..3887744e45126 100644 --- a/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php +++ b/lib/internal/Magento/Framework/Code/Test/Unit/Generator/ClassGeneratorTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Code\Test\Unit\Generator; +/** + * Test for Magento\Framework\Code\Generator\ClassGenerator + */ class ClassGeneratorTest extends \PHPUnit\Framework\TestCase { /**#@+ @@ -333,9 +336,9 @@ public function testNamespaceName($actualNamespace, $expectedNamespace) public function providerNamespaces() { return [ - ['Zend', 'Zend'], - ['\Zend', 'Zend'], - ['\Zend\SomeClass', 'Zend\SomeClass'], + ['Laminas', 'Laminas'], + ['\Laminas', 'Laminas'], + ['\Laminas\SomeClass', 'Laminas\SomeClass'], ['', null], ]; } From cf51d98466e3ab423c78f2a45cc0e49ef098fd59 Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Mon, 16 Mar 2020 14:16:30 -0500 Subject: [PATCH 321/369] MC-32086: [AR] Report account is unavailable after URL change --- app/code/Magento/Analytics/Cron/SignUp.php | 11 +- app/code/Magento/Analytics/Cron/Update.php | 28 +- .../Patch/Data/ActivateDataCollection.php | 95 +++++++ .../Setup/Patch/Data/PrepareInitialConfig.php | 58 ++--- .../Analytics/Test/Unit/Cron/UpdateTest.php | 15 +- .../Analytics/etc/adminhtml/system.xml | 9 +- app/code/Magento/Analytics/etc/di.xml | 2 +- .../Magento/Analytics/Cron/UpdateTest.php | 239 ++++++++++++++++++ .../Model/Config/Backend/EnabledTest.php | 163 ++++++++++++ ...nabled_subscription_with_invalid_token.php | 19 +- ...bscription_with_invalid_token_rollback.php | 23 +- 11 files changed, 594 insertions(+), 68 deletions(-) create mode 100644 app/code/Magento/Analytics/Setup/Patch/Data/ActivateDataCollection.php create mode 100644 dev/tests/integration/testsuite/Magento/Analytics/Cron/UpdateTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Analytics/Model/Config/Backend/EnabledTest.php diff --git a/app/code/Magento/Analytics/Cron/SignUp.php b/app/code/Magento/Analytics/Cron/SignUp.php index 8f97b839ec8ee..2588b87e84c1c 100644 --- a/app/code/Magento/Analytics/Cron/SignUp.php +++ b/app/code/Magento/Analytics/Cron/SignUp.php @@ -7,6 +7,7 @@ use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; use Magento\Analytics\Model\Connector; +use Magento\Framework\Exception\NotFoundException; use Magento\Framework\FlagManager; use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\App\Config\Storage\WriterInterface; @@ -57,22 +58,24 @@ public function __construct( } /** - * Execute scheduled subscription operation + * Execute scheduled subscription operation. + * * In case of failure writes message to notifications inbox * * @return bool + * @throws NotFoundException */ public function execute() { - $attemptsCount = $this->flagManager->getFlagData(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); + $attemptsCount = (int)$this->flagManager->getFlagData(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); - if (($attemptsCount === null) || ($attemptsCount <= 0)) { + if ($attemptsCount <= 0) { $this->deleteAnalyticsCronExpr(); $this->flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); return false; } - $attemptsCount -= 1; + $attemptsCount--; $this->flagManager->saveFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, $attemptsCount); $signUpResult = $this->connector->execute('signUp'); if ($signUpResult === false) { diff --git a/app/code/Magento/Analytics/Cron/Update.php b/app/code/Magento/Analytics/Cron/Update.php index 9062a7bac7551..b5e4b82a0777e 100644 --- a/app/code/Magento/Analytics/Cron/Update.php +++ b/app/code/Magento/Analytics/Cron/Update.php @@ -8,6 +8,7 @@ use Magento\Analytics\Model\AnalyticsToken; use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; use Magento\Analytics\Model\Connector; +use Magento\Framework\Exception\NotFoundException; use Magento\Framework\FlagManager; use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\App\Config\Storage\WriterInterface; @@ -67,26 +68,37 @@ public function __construct( * Execute scheduled update operation * * @return bool + * @throws NotFoundException */ public function execute() { $result = false; - $attemptsCount = $this->flagManager + $attemptsCount = (int)$this->flagManager ->getFlagData(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); - if ($attemptsCount) { - $attemptsCount -= 1; + if (($attemptsCount > 0) && $this->analyticsToken->isTokenExist()) { + $attemptsCount--; + $this->flagManager + ->saveFlag(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $attemptsCount); $result = $this->connector->execute('update'); } if ($result || ($attemptsCount <= 0) || (!$this->analyticsToken->isTokenExist())) { - $this->flagManager - ->deleteFlag(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); - $this->flagManager->deleteFlag(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE); - $this->configWriter->delete(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH); - $this->reinitableConfig->reinit(); + $this->exitFromUpdateProcess(); } return $result; } + + /** + * Clean-up flags and refresh configuration + */ + private function exitFromUpdateProcess(): void + { + $this->flagManager + ->deleteFlag(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); + $this->flagManager->deleteFlag(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE); + $this->configWriter->delete(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH); + $this->reinitableConfig->reinit(); + } } diff --git a/app/code/Magento/Analytics/Setup/Patch/Data/ActivateDataCollection.php b/app/code/Magento/Analytics/Setup/Patch/Data/ActivateDataCollection.php new file mode 100644 index 0000000000000..dd60d74b53d09 --- /dev/null +++ b/app/code/Magento/Analytics/Setup/Patch/Data/ActivateDataCollection.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Analytics\Setup\Patch\Data; + +use Magento\Analytics\Model\Config\Backend\CollectionTime; +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +/** + * Activate data collection mechanism + */ +class ActivateDataCollection implements DataPatchInterface +{ + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var SubscriptionStatusProvider + */ + private $subscriptionStatusProvider; + + /** + * @var string + */ + private $analyticsCollectionTimeConfigPath = 'analytics/general/collection_time'; + + /** + * @var CollectionTime + */ + private $collectionTimeBackendModel; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param SubscriptionStatusProvider $subscriptionStatusProvider + * @param CollectionTime $collectionTimeBackendModel + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + SubscriptionStatusProvider $subscriptionStatusProvider, + CollectionTime $collectionTimeBackendModel + ) { + $this->scopeConfig = $scopeConfig; + $this->subscriptionStatusProvider = $subscriptionStatusProvider; + $this->collectionTimeBackendModel = $collectionTimeBackendModel; + } + + /** + * @inheritDoc + * + * @throws LocalizedException + */ + public function apply() + { + $subscriptionStatus = $this->subscriptionStatusProvider->getStatus(); + $isCollectionProcessActivated = $this->scopeConfig->getValue(CollectionTime::CRON_SCHEDULE_PATH); + if ($subscriptionStatus !== $this->subscriptionStatusProvider->getStatusForDisabledSubscription() + && !$isCollectionProcessActivated + ) { + $this->collectionTimeBackendModel + ->setValue($this->scopeConfig->getValue($this->analyticsCollectionTimeConfigPath)); + $this->collectionTimeBackendModel->setPath($this->analyticsCollectionTimeConfigPath); + $this->collectionTimeBackendModel->afterSave(); + } + + return $this; + } + + /** + * @inheritDoc + */ + public function getAliases() + { + return []; + } + + /** + * @inheritDoc + */ + public static function getDependencies() + { + return [ + PrepareInitialConfig::class, + ]; + } +} diff --git a/app/code/Magento/Analytics/Setup/Patch/Data/PrepareInitialConfig.php b/app/code/Magento/Analytics/Setup/Patch/Data/PrepareInitialConfig.php index a352854a8b77b..97ac340f9d491 100644 --- a/app/code/Magento/Analytics/Setup/Patch/Data/PrepareInitialConfig.php +++ b/app/code/Magento/Analytics/Setup/Patch/Data/PrepareInitialConfig.php @@ -4,17 +4,18 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Analytics\Setup\Patch\Data; use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Config\Model\Config\Source\Enabledisable; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** - * Initial patch. - * - * @package Magento\Analytics\Setup\Patch + * Active subscription process for Advanced Reporting */ class PrepareInitialConfig implements DataPatchInterface, PatchVersionInterface { @@ -24,50 +25,47 @@ class PrepareInitialConfig implements DataPatchInterface, PatchVersionInterface private $moduleDataSetup; /** - * PrepareInitialConfig constructor. + * @var SubscriptionHandler + */ + private $subscriptionHandler; + + /** + * @var string + */ + private $subscriptionEnabledConfigPath = 'analytics/subscription/enabled'; + + /** * @param ModuleDataSetupInterface $moduleDataSetup + * @param SubscriptionHandler $subscriptionHandler */ public function __construct( - ModuleDataSetupInterface $moduleDataSetup + ModuleDataSetupInterface $moduleDataSetup, + SubscriptionHandler $subscriptionHandler ) { $this->moduleDataSetup = $moduleDataSetup; + $this->subscriptionHandler = $subscriptionHandler; } /** - * {@inheritdoc} + * @inheritDoc */ public function apply() { - $this->moduleDataSetup->getConnection()->insertMultiple( + $this->moduleDataSetup->getConnection()->insert( $this->moduleDataSetup->getTable('core_config_data'), [ - [ - 'scope' => 'default', - 'scope_id' => 0, - 'path' => 'analytics/subscription/enabled', - 'value' => 1 - ], - [ - 'scope' => 'default', - 'scope_id' => 0, - 'path' => SubscriptionHandler::CRON_STRING_PATH, - 'value' => join(' ', SubscriptionHandler::CRON_EXPR_ARRAY) - ] + 'path' => $this->subscriptionEnabledConfigPath, + 'value' => Enabledisable::ENABLE_VALUE, ] ); - $this->moduleDataSetup->getConnection()->insert( - $this->moduleDataSetup->getTable('flag'), - [ - 'flag_code' => SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, - 'state' => 0, - 'flag_data' => 24, - ] - ); + $this->subscriptionHandler->processEnabled(); + + return $this; } /** - * {@inheritdoc} + * @inheritDoc */ public static function getDependencies() { @@ -75,7 +73,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritDoc */ public static function getVersion() { @@ -83,7 +81,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritDoc */ public function getAliases() { diff --git a/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php b/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php index aa3011ffc94f6..fa007268474c4 100644 --- a/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php +++ b/app/code/Magento/Analytics/Test/Unit/Cron/UpdateTest.php @@ -11,6 +11,7 @@ use Magento\Analytics\Model\Connector; use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\Exception\NotFoundException; use Magento\Framework\FlagManager; class UpdateTest extends \PHPUnit\Framework\TestCase @@ -45,6 +46,9 @@ class UpdateTest extends \PHPUnit\Framework\TestCase */ private $update; + /** + * @inheritDoc + */ protected function setUp() { $this->connectorMock = $this->getMockBuilder(Connector::class) @@ -74,6 +78,7 @@ protected function setUp() /** * @return void + * @throws NotFoundException */ public function testExecuteWithoutToken() { @@ -82,12 +87,12 @@ public function testExecuteWithoutToken() ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE) ->willReturn(10); $this->connectorMock - ->expects($this->once()) + ->expects($this->never()) ->method('execute') ->with('update') ->willReturn(false); $this->analyticsTokenMock - ->expects($this->once()) + ->expects($this->any()) ->method('isTokenExist') ->willReturn(false); $this->addFinalOutputAsserts(); @@ -120,6 +125,7 @@ private function addFinalOutputAsserts(bool $isExecuted = true) * @param $counterData * @return void * @dataProvider executeWithEmptyReverseCounterDataProvider + * @throws NotFoundException */ public function testExecuteWithEmptyReverseCounter($counterData) { @@ -159,6 +165,7 @@ public function executeWithEmptyReverseCounterDataProvider() * @param bool $functionResult * @return void * @dataProvider executeRegularScenarioDataProvider + * @throws NotFoundException */ public function testExecuteRegularScenario( int $reverseCount, @@ -170,6 +177,10 @@ public function testExecuteRegularScenario( ->method('getFlagData') ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE) ->willReturn($reverseCount); + $this->flagManagerMock + ->expects($this->once()) + ->method('saveFlag') + ->with(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $reverseCount - 1); $this->connectorMock ->expects($this->once()) ->method('execute') diff --git a/app/code/Magento/Analytics/etc/adminhtml/system.xml b/app/code/Magento/Analytics/etc/adminhtml/system.xml index 999d565353329..0aba6e4dd00ed 100644 --- a/app/code/Magento/Analytics/etc/adminhtml/system.xml +++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml @@ -28,6 +28,9 @@ <label>Time of day to send data</label> <frontend_model>Magento\Analytics\Block\Adminhtml\System\Config\CollectionTimeLabel</frontend_model> <backend_model>Magento\Analytics\Model\Config\Backend\CollectionTime</backend_model> + <depends> + <field id="analytics/general/enabled">1</field> + </depends> </field> <field id="vertical" translate="hint label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="1"> <hint>Industry Data</hint> @@ -36,9 +39,9 @@ <source_model>Magento\Analytics\Model\Config\Source\Vertical</source_model> <backend_model>Magento\Analytics\Model\Config\Backend\Vertical</backend_model> <frontend_model>Magento\Analytics\Block\Adminhtml\System\Config\Vertical</frontend_model> - <depends> - <field id="analytics/general/enabled">1</field> - </depends> + <depends> + <field id="analytics/general/enabled">1</field> + </depends> </field> <field id="additional_comment" translate="label comment" type="label" sortOrder="40" showInDefault="1"> <label><![CDATA[<strong>Get more insights from Magento Business Intelligence</strong>]]></label> diff --git a/app/code/Magento/Analytics/etc/di.xml b/app/code/Magento/Analytics/etc/di.xml index b9bb9cc9ff00c..cdb42f18718b7 100644 --- a/app/code/Magento/Analytics/etc/di.xml +++ b/app/code/Magento/Analytics/etc/di.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <preference for="Magento\Analytics\ReportXML\ConfigInterface" type="Magento\Analytics\ReportXML\Config" /> + <preference for="Magento\Analytics\ReportXml\ConfigInterface" type="Magento\Analytics\ReportXml\Config" /> <preference for="Magento\Analytics\Model\ConfigInterface" type="Magento\Analytics\Model\Config" /> <preference for="Magento\Analytics\Model\ReportWriterInterface" type="Magento\Analytics\Model\ReportWriter" /> <preference for="Magento\Analytics\Api\LinkProviderInterface" type="Magento\Analytics\Model\LinkProvider" /> diff --git a/dev/tests/integration/testsuite/Magento/Analytics/Cron/UpdateTest.php b/dev/tests/integration/testsuite/Magento/Analytics/Cron/UpdateTest.php new file mode 100644 index 0000000000000..c8ce98d34074b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Analytics/Cron/UpdateTest.php @@ -0,0 +1,239 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Analytics\Cron; + +use Magento\Analytics\Model\Config\Backend\Baseurl\SubscriptionUpdateHandler; +use Magento\Analytics\Model\Connector\Http\Client\Curl as CurlClient; +use Magento\Analytics\Model\Connector\Http\ClientInterface; +use Magento\Config\Model\PreparedValueFactory; +use Magento\Config\Model\ResourceModel\Config\Data as ConfigDataResource; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\FlagManager; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** + * @magentoAppArea adminhtml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class UpdateTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var PreparedValueFactory + */ + private $preparedValueFactory; + + /** + * @var ConfigDataResource + */ + private $configValueResourceModel; + + /** + * @var Update + */ + private $updateCron; + + /** + * @var ClientInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $httpClient; + + /** + * @var FlagManager + */ + private $flagManager; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->httpClient = $this->getMockBuilder(ClientInterface::class) + ->getMockForAbstractClass(); + $this->objectManager->addSharedInstance($this->httpClient, CurlClient::class); + $this->preparedValueFactory = $this->objectManager->get(PreparedValueFactory::class); + $this->configValueResourceModel = $this->objectManager->get(ConfigDataResource::class); + $this->updateCron = $this->objectManager->get(Update::class); + $this->scopeConfig = $this->objectManager->get(ScopeConfigInterface::class); + $this->flagManager = $this->objectManager->get(FlagManager::class); + } + + /** + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Analytics/_files/enabled_subscription_with_invalid_token.php + * + */ + public function testSuccessfulAttemptExecute() + { + $this->saveConfigValue( + Store::XML_PATH_SECURE_BASE_URL, + 'http://store.com/' + ); + + $this->mockRequestCall(201, 'URL has been changed'); + + $this->updateCron->execute(); + $this->assertEmpty($this->getUpdateCounterFlag()); + $this->assertEmpty($this->getPreviousBaseUrlFlag()); + $this->assertEmpty($this->getConfigValue(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH)); + } + + /** + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Analytics/_files/enabled_subscription_with_invalid_token.php + * + */ + public function testUnsuccessfulAttemptExecute() + { + $this->saveConfigValue( + Store::XML_PATH_SECURE_BASE_URL, + 'http://store.com/' + ); + + $reverseCounter = $this->getUpdateCounterFlag(); + $this->mockRequestCall(500, 'Unauthorized access'); + + $this->updateCron->execute(); + $this->assertEquals($reverseCounter - 1, $this->getUpdateCounterFlag()); + } + + /** + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Analytics/_files/enabled_subscription_with_invalid_token.php + * + */ + public function testLastUnsuccessfulAttemptExecute() + { + $this->saveConfigValue( + Store::XML_PATH_SECURE_BASE_URL, + 'http://store.com/' + ); + + $this->setUpdateCounterValue(1); + $this->mockRequestCall(500, 'Unauthorized access'); + + $this->updateCron->execute(); + $this->assertEmpty($this->getUpdateCounterFlag()); + $this->assertEmpty($this->getPreviousBaseUrlFlag()); + $this->assertEmpty($this->getConfigValue(SubscriptionUpdateHandler::UPDATE_CRON_STRING_PATH)); + } + + /** + * Save configuration value + * + * @param string $path The configuration path in format section/group/field_name + * @param string $value The configuration value + * @param string $scope The configuration scope (default, website, or store) + * @return void + * @throws \Magento\Framework\Exception\RuntimeException + * @throws \Magento\Framework\Exception\AlreadyExistsException + */ + private function saveConfigValue( + string $path, + string $value, + string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ) { + $configValue = $this->preparedValueFactory->create( + $path, + $value, + $scope + ); + $this->configValueResourceModel->save($configValue); + } + + /** + * Get configuration value + * + * @param string $path + * @param string $scopeType + * @return mixed + */ + private function getConfigValue( + string $path, + string $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ) { + return $this->scopeConfig->getValue( + $path, + $scopeType + ); + } + + /** + * Get update counter flag value + * + * @return int|null + */ + private function getUpdateCounterFlag(): ?int + { + return $this->flagManager + ->getFlagData(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE); + } + + /** + * Get previous URL flag value + * + * @return string|null + */ + private function getPreviousBaseUrlFlag(): ?string + { + return $this->flagManager + ->getFlagData(SubscriptionUpdateHandler::PREVIOUS_BASE_URL_FLAG_CODE); + } + + /** + * Set response mock for the HTTP client + * + * @param int $responseCode + * @param string $responseMessage + */ + private function mockRequestCall(int $responseCode, string $responseMessage): void + { + $response = $this->objectManager->create( + \Zend_Http_Response::class, + [ + 'code' => $responseCode, + 'headers' => [ + 'Content-Type' => 'application/json' + ], + 'body' => json_encode(['message' => $responseMessage]) + ] + ); + + $this->httpClient + ->expects($this->once()) + ->method('request') + ->willReturn($response); + } + + /** + * Set value for update counter flag + * + * @param int $value + */ + private function setUpdateCounterValue(int $value): void + { + $this->flagManager + ->saveFlag(SubscriptionUpdateHandler::SUBSCRIPTION_UPDATE_REVERSE_COUNTER_FLAG_CODE, $value); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Analytics/Model/Config/Backend/EnabledTest.php b/dev/tests/integration/testsuite/Magento/Analytics/Model/Config/Backend/EnabledTest.php new file mode 100644 index 0000000000000..61cc8b56af949 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Analytics/Model/Config/Backend/EnabledTest.php @@ -0,0 +1,163 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Analytics\Model\Config\Backend; + +use Magento\Analytics\Model\SubscriptionStatusProvider; +use Magento\Config\Model\Config\Source\Enabledisable; +use Magento\Config\Model\PreparedValueFactory; +use Magento\Config\Model\ResourceModel\Config\Data as ConfigDataResource; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * @magentoAppArea adminhtml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class EnabledTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var SubscriptionStatusProvider + */ + private $subscriptionStatusProvider; + + /** + * @var PreparedValueFactory + */ + private $preparedValueFactory; + + /** + * @var ConfigDataResource + */ + private $configValueResourceModel; + + /** + * @var ReinitableConfigInterface + */ + private $reinitableConfig; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->scopeConfig = $this->objectManager->get(ScopeConfigInterface::class); + $this->subscriptionStatusProvider = $this->objectManager->get(SubscriptionStatusProvider::class); + $this->preparedValueFactory = $this->objectManager->get(PreparedValueFactory::class); + $this->configValueResourceModel = $this->objectManager->get(ConfigDataResource::class); + $this->reinitableConfig = $this->objectManager->get(ReinitableConfigInterface::class); + } + + /** + * @magentoDbIsolation enabled + */ + public function testDisable() + { + $this->checkInitialStatus(); + $this->saveConfigValue(Enabled::XML_ENABLED_CONFIG_STRUCTURE_PATH, (string)Enabledisable::DISABLE_VALUE); + $this->reinitableConfig->reinit(); + + $this->checkDisabledStatus(); + } + + /** + * @depends testDisable + * @magentoDbIsolation enabled + */ + public function testReEnable() + { + $this->checkDisabledStatus(); + $this->saveConfigValue(Enabled::XML_ENABLED_CONFIG_STRUCTURE_PATH, (string)Enabledisable::ENABLE_VALUE); + $this->checkReEnabledStatus(); + } + + /** + * Get configuration value + * + * @param string $path + * @param string $scopeType + * @return mixed + */ + private function getConfigValue( + string $path, + string $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ) { + return $this->scopeConfig->getValue( + $path, + $scopeType + ); + } + + /** + * Save configuration value + * + * @param string $path The configuration path in format section/group/field_name + * @param string $value The configuration value + * @param string $scope The configuration scope (default, website, or store) + * @return void + * @throws \Magento\Framework\Exception\RuntimeException + * @throws \Magento\Framework\Exception\AlreadyExistsException + */ + private function saveConfigValue( + string $path, + string $value, + string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ) { + $configValue = $this->preparedValueFactory->create( + $path, + $value, + $scope + ); + $this->configValueResourceModel->save($configValue); + } + + /** + * Check the instance status after installation + */ + private function checkInitialStatus() + { + $this->assertNotSame(SubscriptionStatusProvider::DISABLED, $this->subscriptionStatusProvider->getStatus()); + $this->assertNotEmpty($this->getConfigValue(CollectionTime::CRON_SCHEDULE_PATH)); + } + + /** + * Check the instance status after disabling AR synchronisation + */ + private function checkDisabledStatus() + { + $this->assertSame(SubscriptionStatusProvider::DISABLED, $this->subscriptionStatusProvider->getStatus()); + $this->assertEmpty($this->getConfigValue(CollectionTime::CRON_SCHEDULE_PATH)); + } + + /** + * Check the instance status after re-activation AR synchronisation + */ + private function checkReEnabledStatus() + { + $this->assertContains( + $this->subscriptionStatusProvider->getStatus(), + [ + SubscriptionStatusProvider::ENABLED, + SubscriptionStatusProvider::PENDING, + ] + ); + $this->assertNotEmpty($this->getConfigValue(CollectionTime::CRON_SCHEDULE_PATH)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token.php b/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token.php index 0106bf6f1bdac..c52de227deae8 100644 --- a/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token.php +++ b/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token.php @@ -3,27 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +use Magento\Analytics\Model\AnalyticsToken; use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\FlagManager; $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** - * @var $configWriter \Magento\Framework\App\Config\Storage\WriterInterface + * @var $configWriter WriterInterface */ -$configWriter = $objectManager->get(\Magento\Framework\App\Config\Storage\WriterInterface::class); - +$configWriter = $objectManager->get(WriterInterface::class); $configWriter->delete(SubscriptionHandler::CRON_STRING_PATH); -$configWriter->save('analytics/subscription/enabled', 1); /** - * @var $analyticsToken \Magento\Analytics\Model\AnalyticsToken + * @var $analyticsToken AnalyticsToken */ -$analyticsToken = $objectManager->get(\Magento\Analytics\Model\AnalyticsToken::class); +$analyticsToken = $objectManager->get(AnalyticsToken::class); $analyticsToken->storeToken('42'); /** - * @var $flagManager \Magento\Framework\FlagManager + * @var $flagManager FlagManager */ -$flagManager = $objectManager->get(\Magento\Framework\FlagManager::class); - +$flagManager = $objectManager->get(FlagManager::class); $flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); diff --git a/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token_rollback.php b/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token_rollback.php index 3fd3e21e282e0..a47a4b3475c9d 100644 --- a/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Analytics/_files/enabled_subscription_with_invalid_token_rollback.php @@ -3,27 +3,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +use Magento\Analytics\Model\AnalyticsToken; use Magento\Analytics\Model\Config\Backend\Enabled\SubscriptionHandler; +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\Framework\FlagManager; $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** - * @var $configWriter \Magento\Framework\App\Config\Storage\WriterInterface + * @var $configWriter WriterInterface */ -$configWriter = $objectManager->get(\Magento\Framework\App\Config\Storage\WriterInterface::class); - -$configWriter->delete(SubscriptionHandler::CRON_STRING_PATH); -$configWriter->save('analytics/subscription/enabled', 0); +$configWriter = $objectManager->get(WriterInterface::class); +$configWriter->save(SubscriptionHandler::CRON_STRING_PATH, join(' ', SubscriptionHandler::CRON_EXPR_ARRAY)); /** - * @var $analyticsToken \Magento\Analytics\Model\AnalyticsToken + * @var $analyticsToken AnalyticsToken */ -$analyticsToken = $objectManager->get(\Magento\Analytics\Model\AnalyticsToken::class); +$analyticsToken = $objectManager->get(AnalyticsToken::class); $analyticsToken->storeToken(null); /** - * @var $flagManager \Magento\Framework\FlagManager + * @var $flagManager FlagManager */ -$flagManager = $objectManager->get(\Magento\Framework\FlagManager::class); - -$flagManager->deleteFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE); +$flagManager = $objectManager->get(FlagManager::class); +$flagManager->saveFlag(SubscriptionHandler::ATTEMPTS_REVERSE_COUNTER_FLAG_CODE, 24); From 509629bb64676841873ebd72dc2ea7d0dcbee9ec Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Mon, 16 Mar 2020 14:28:37 -0500 Subject: [PATCH 322/369] MC-32378: Implement control over minimum_should_match for elasticsearch queries --- .../FieldMapper/AddDefaultSearchField.php | 2 +- .../CopySearchableFieldsToSearchField.php | 2 +- .../FieldMapper/AddDefaultSearchFieldTest.php | 4 +-- .../CopySearchableFieldsToSearchFieldTest.php | 4 +-- app/code/Magento/Elasticsearch/i18n/en_US.csv | 1 + .../Unit/Model/Client/ElasticsearchTest.php | 2 +- app/code/Magento/Elasticsearch6/etc/di.xml | 4 +-- .../Model/Client/Elasticsearch.php | 33 +++++++++++++++---- .../Unit/Model/Client/ElasticsearchTest.php | 4 ++- .../Elasticsearch7/etc/adminhtml/system.xml | 8 +++++ .../Magento/Elasticsearch7/etc/config.xml | 1 + app/code/Magento/Elasticsearch7/etc/di.xml | 8 +++++ 12 files changed, 56 insertions(+), 17 deletions(-) rename app/code/Magento/{Elasticsearch6 => Elasticsearch}/Model/Adapter/FieldMapper/AddDefaultSearchField.php (92%) rename app/code/Magento/{Elasticsearch6 => Elasticsearch}/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php (95%) rename app/code/Magento/{Elasticsearch6 => Elasticsearch}/Test/Unit/Model/Adapter/FieldMapper/AddDefaultSearchFieldTest.php (93%) rename app/code/Magento/{Elasticsearch6 => Elasticsearch}/Test/Unit/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchFieldTest.php (95%) diff --git a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/AddDefaultSearchField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/AddDefaultSearchField.php similarity index 92% rename from app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/AddDefaultSearchField.php rename to app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/AddDefaultSearchField.php index 27767f6567d96..b503dbbc91678 100644 --- a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/AddDefaultSearchField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/AddDefaultSearchField.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\Elasticsearch6\Model\Adapter\FieldMapper; +namespace Magento\Elasticsearch\Model\Adapter\FieldMapper; use Magento\Elasticsearch\Model\Adapter\FieldsMappingPreprocessorInterface; diff --git a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php similarity index 95% rename from app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php rename to app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php index 6179eacba5ad7..248adc9ed4e1f 100644 --- a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\Elasticsearch6\Model\Adapter\FieldMapper; +namespace Magento\Elasticsearch\Model\Adapter\FieldMapper; use Magento\Elasticsearch\Model\Adapter\FieldsMappingPreprocessorInterface; diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/AddDefaultSearchFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/AddDefaultSearchFieldTest.php similarity index 93% rename from app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/AddDefaultSearchFieldTest.php rename to app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/AddDefaultSearchFieldTest.php index 1a1e7f4e0d643..a0e2837ba8496 100644 --- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/AddDefaultSearchFieldTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/AddDefaultSearchFieldTest.php @@ -5,9 +5,9 @@ */ declare(strict_types=1); -namespace Magento\Elasticsearch6\Test\Unit\Model\Adapter\FieldMapper; +namespace Magento\Elasticsearch\Test\Unit\Model\Adapter\FieldMapper; -use Magento\Elasticsearch6\Model\Adapter\FieldMapper\AddDefaultSearchField; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\AddDefaultSearchField; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit\Framework\TestCase; diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchFieldTest.php similarity index 95% rename from app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchFieldTest.php rename to app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchFieldTest.php index cfe8b71095d21..33a772950a111 100644 --- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchFieldTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchFieldTest.php @@ -5,9 +5,9 @@ */ declare(strict_types=1); -namespace Magento\Elasticsearch6\Test\Unit\Model\Adapter\FieldMapper; +namespace Magento\Elasticsearch\Test\Unit\Model\Adapter\FieldMapper; -use Magento\Elasticsearch6\Model\Adapter\FieldMapper\CopySearchableFieldsToSearchField; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\CopySearchableFieldsToSearchField; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit\Framework\TestCase; diff --git a/app/code/Magento/Elasticsearch/i18n/en_US.csv b/app/code/Magento/Elasticsearch/i18n/en_US.csv index 85c9aefdb9f25..1c49a7f97c5b5 100644 --- a/app/code/Magento/Elasticsearch/i18n/en_US.csv +++ b/app/code/Magento/Elasticsearch/i18n/en_US.csv @@ -10,3 +10,4 @@ "Elasticsearch HTTP Password","Elasticsearch HTTP Password" "Elasticsearch Server Timeout","Elasticsearch Server Timeout" "Test Connection","Test Connection" +"Minimum terms to match","Minimum terms to match" diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php index e3bcc3d219538..446a3471976a0 100644 --- a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php @@ -7,7 +7,7 @@ namespace Magento\Elasticsearch6\Test\Unit\Model\Client; use Magento\AdvancedSearch\Model\Client\ClientInterface as ElasticsearchClient; -use Magento\Elasticsearch6\Model\Adapter\FieldMapper\AddDefaultSearchField; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\AddDefaultSearchField; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Elasticsearch6\Model\Client\Elasticsearch; diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml index 641fbd069b627..e0974dda1d998 100644 --- a/app/code/Magento/Elasticsearch6/etc/di.xml +++ b/app/code/Magento/Elasticsearch6/etc/di.xml @@ -97,8 +97,8 @@ <type name="Magento\Elasticsearch6\Model\Client\Elasticsearch"> <arguments> <argument name="fieldsMappingPreprocessors" xsi:type="array"> - <item name="elasticsearch6_copy_searchable_fields_to_search_field" xsi:type="object">Magento\Elasticsearch6\Model\Adapter\FieldMapper\CopySearchableFieldsToSearchField</item> - <item name="elasticsearch6_add_default_search_field" xsi:type="object">Magento\Elasticsearch6\Model\Adapter\FieldMapper\AddDefaultSearchField</item> + <item name="elasticsearch6_copy_searchable_fields_to_search_field" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\CopySearchableFieldsToSearchField</item> + <item name="elasticsearch6_add_default_search_field" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\AddDefaultSearchField</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php b/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php index c6c85b0edc338..feacca8d62804 100644 --- a/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php +++ b/app/code/Magento/Elasticsearch7/Model/Client/Elasticsearch.php @@ -7,6 +7,7 @@ namespace Magento\Elasticsearch7\Model\Client; +use Magento\Elasticsearch\Model\Adapter\FieldsMappingPreprocessorInterface; use Magento\Framework\Exception\LocalizedException; use Magento\AdvancedSearch\Model\Client\ClientInterface; @@ -32,16 +33,23 @@ class Elasticsearch implements ClientInterface */ private $pingResult; + /** + * @var FieldsMappingPreprocessorInterface[] + */ + private $fieldsMappingPreprocessors; + /** * Initialize Elasticsearch 7 Client * * @param array $options * @param \Elasticsearch\Client|null $elasticsearchClient + * @param array $fieldsMappingPreprocessors * @throws LocalizedException */ public function __construct( $options = [], - $elasticsearchClient = null + $elasticsearchClient = null, + $fieldsMappingPreprocessors = [] ) { if (empty($options['hostname']) || ((!empty($options['enableAuth']) && ($options['enableAuth'] == 1)) && (empty($options['username']) || empty($options['password'])))) { @@ -54,6 +62,7 @@ public function __construct( $this->client[getmypid()] = $elasticsearchClient; } $this->clientOptions = $options; + $this->fieldsMappingPreprocessors = $fieldsMappingPreprocessors; } /** @@ -273,11 +282,7 @@ public function addFieldsMapping(array $fields, string $index, string $entityTyp 'include_type_name' => true, 'body' => [ $entityType => [ - 'properties' => [ - '_search' => [ - 'type' => 'text' - ], - ], + 'properties' => [], 'dynamic_templates' => [ [ 'price_mapping' => [ @@ -315,7 +320,7 @@ public function addFieldsMapping(array $fields, string $index, string $entityTyp ], ]; - foreach ($fields as $field => $fieldInfo) { + foreach ($this->applyFieldsMappingPreprocessors($fields) as $field => $fieldInfo) { $params['body'][$entityType]['properties'][$field] = $fieldInfo; } @@ -349,4 +354,18 @@ public function deleteMapping(string $index, string $entityType) ] ); } + + /** + * Apply fields mapping preprocessors + * + * @param array $properties + * @return array + */ + private function applyFieldsMappingPreprocessors(array $properties): array + { + foreach ($this->fieldsMappingPreprocessors as $preprocessor) { + $properties = $preprocessor->process($properties); + } + return $properties; + } } diff --git a/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php index cb07cfb7bf83d..f4be22150bba7 100644 --- a/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php +++ b/app/code/Magento/Elasticsearch7/Test/Unit/Model/Client/ElasticsearchTest.php @@ -11,6 +11,7 @@ use Magento\AdvancedSearch\Model\Client\ClientInterface as ElasticsearchClient; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Elasticsearch7\Model\Client\Elasticsearch; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\AddDefaultSearchField; /** * Class ElasticsearchTest to test Elasticsearch 7 @@ -90,7 +91,8 @@ protected function setUp() Elasticsearch::class, [ 'options' => $this->getOptions(), - 'elasticsearchClient' => $this->elasticsearchClientMock + 'elasticsearchClient' => $this->elasticsearchClientMock, + 'fieldsMappingPreprocessors' => [new AddDefaultSearchField()] ] ); } diff --git a/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml index cb7cdc2a5b531..1703f1af47ee5 100644 --- a/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml +++ b/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml @@ -79,6 +79,14 @@ <field id="engine">elasticsearch7</field> </depends> </field> + <field id="elasticsearch7_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1"> + <label>Minimum Terms to Match</label> + <depends> + <field id="engine">elasticsearch7</field> + </depends> + <comment><![CDATA[<a href="https://docs.magento.com/m2/ce/user_guide/catalog/search-elasticsearch.html">Learn more</a> about valid syntax.]]></comment> + <backend_model>Magento\Elasticsearch\Model\Config\Backend\MinimumShouldMatch</backend_model> + </field> </group> </section> </system> diff --git a/app/code/Magento/Elasticsearch7/etc/config.xml b/app/code/Magento/Elasticsearch7/etc/config.xml index 63d832c8445f5..c13586bb9e357 100644 --- a/app/code/Magento/Elasticsearch7/etc/config.xml +++ b/app/code/Magento/Elasticsearch7/etc/config.xml @@ -14,6 +14,7 @@ <elasticsearch7_index_prefix>magento2</elasticsearch7_index_prefix> <elasticsearch7_enable_auth>0</elasticsearch7_enable_auth> <elasticsearch7_server_timeout>15</elasticsearch7_server_timeout> + <elasticsearch7_minimum_should_match></elasticsearch7_minimum_should_match> </search> </catalog> </default> diff --git a/app/code/Magento/Elasticsearch7/etc/di.xml b/app/code/Magento/Elasticsearch7/etc/di.xml index 4f8129f8209f4..a54b92a8b6f66 100644 --- a/app/code/Magento/Elasticsearch7/etc/di.xml +++ b/app/code/Magento/Elasticsearch7/etc/di.xml @@ -213,4 +213,12 @@ </argument> </arguments> </type> + <type name="Magento\Elasticsearch7\Model\Client\Elasticsearch"> + <arguments> + <argument name="fieldsMappingPreprocessors" xsi:type="array"> + <item name="elasticsearch7_copy_searchable_fields_to_search_field" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\CopySearchableFieldsToSearchField</item> + <item name="elasticsearch7_add_default_search_field" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\AddDefaultSearchField</item> + </argument> + </arguments> + </type> </config> From 6428b8c0f56dda13313171f1b39b2c4dbf36d0cc Mon Sep 17 00:00:00 2001 From: engcom-Echo <engcom-vendorworker-echo@adobe.com> Date: Tue, 17 Mar 2020 12:49:27 +0200 Subject: [PATCH 323/369] Fix static, test coverage --- .../Setup/Patch/Data/AddDataForItaly.php | 4 +- .../Magento/Directory/Model/RegionTest.php | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 dev/tests/integration/testsuite/Magento/Directory/Model/RegionTest.php diff --git a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php index 44288c9f2a276..a5d4bcdc0b8be 100644 --- a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php +++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForItaly.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Directory\Setup\Patch\Data; @@ -51,12 +50,15 @@ public function apply() $this->moduleDataSetup->getConnection(), $this->getDataForItaly() ); + + return $this; } /** * Italy states data. * * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ private function getDataForItaly() { diff --git a/dev/tests/integration/testsuite/Magento/Directory/Model/RegionTest.php b/dev/tests/integration/testsuite/Magento/Directory/Model/RegionTest.php new file mode 100644 index 0000000000000..0b47f7b0719c6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Directory/Model/RegionTest.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Directory\Model\Country\Postcode\Config; + +use Magento\Directory\Model\Country; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +class RegionTest extends TestCase +{ + /** + * @var Country + */ + protected $country; + + /** + * @inheritDoc + */ + public function setUp() + { + $this->country = Bootstrap::getObjectManager()->create(Country::class); + } + + /** + * Verify country has regions. + * + * @var string $countryId + * @dataProvider getCountryIdDataProvider + */ + public function testCountryHasRegions($countryId) + { + $country = $this->country->loadByCode($countryId); + $region = $country->getRegions()->getItems(); + + $this->assertTrue(!empty($region), 'Country ' . $countryId . ' not have regions'); + } + + /** + * Data provider for testCountryHasRegions + * + * @return array + */ + public function getCountryIdDataProvider():array + { + return [ + ['countryId' => 'US'], + ['countryId' => 'CA'], + ['countryId' => 'CN'], + ['countryId' => 'IN'], + ['countryId' => 'AU'], + ['countryId' => 'BE'], + ['countryId' => 'CO'], + ['countryId' => 'MX'], + ['countryId' => 'PL'], + ['countryId' => 'IT'] + ]; + } +} From f3a84a94e8c402df05217706e75d7d646287f1b7 Mon Sep 17 00:00:00 2001 From: Andrii Kalinich <51681435+engcom-Echo@users.noreply.github.com> Date: Tue, 17 Mar 2020 15:20:15 +0200 Subject: [PATCH 324/369] Update RegionTest.php --- .../testsuite/Magento/Directory/Model/RegionTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Directory/Model/RegionTest.php b/dev/tests/integration/testsuite/Magento/Directory/Model/RegionTest.php index 0b47f7b0719c6..e0e790b265572 100644 --- a/dev/tests/integration/testsuite/Magento/Directory/Model/RegionTest.php +++ b/dev/tests/integration/testsuite/Magento/Directory/Model/RegionTest.php @@ -5,9 +5,8 @@ */ declare(strict_types=1); -namespace Magento\Directory\Model\Country\Postcode\Config; +namespace Magento\Directory\Model; -use Magento\Directory\Model\Country; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; From 5cd3e75a4ec878853a251f3276f148489a105a06 Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Tue, 17 Mar 2020 12:14:00 -0500 Subject: [PATCH 325/369] MC-31986: Add support for ES 7 to 2.4-develop --- .../Model/Adapter/FieldType.php | 69 ------------- .../Model/Adapter/Container/Attribute.php | 99 ------------------- .../Model/Adapter/DataMapperInterface.php | 24 ----- .../Adapter/Index/IndexNameResolverTest.php | 2 +- app/code/Magento/Elasticsearch7/composer.json | 10 +- 5 files changed, 6 insertions(+), 198 deletions(-) delete mode 100644 app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldType.php delete mode 100644 app/code/Magento/Elasticsearch/Model/Adapter/Container/Attribute.php delete mode 100644 app/code/Magento/Elasticsearch/Model/Adapter/DataMapperInterface.php rename app/code/Magento/Elasticsearch/Test/Unit/{ => Elasticsearch5}/Model/Adapter/Index/IndexNameResolverTest.php (99%) diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldType.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldType.php deleted file mode 100644 index 6891b8c693624..0000000000000 --- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldType.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Elasticsearch5\Model\Adapter; - -use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; -use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface; - -/** - * Class FieldType - * - * @api - * @since 100.1.0 - * - * @deprecated This class provide not full data about field type. Only basic rules apply on this class. - * @see ResolverInterface - */ -class FieldType -{ - /**#@+ - * @deprecated - * @see \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface - * - * Text flags for Elasticsearch field types - */ - const ES_DATA_TYPE_TEXT = 'text'; - const ES_DATA_TYPE_KEYWORD = 'keyword'; - const ES_DATA_TYPE_FLOAT = 'float'; - const ES_DATA_TYPE_INT = 'integer'; - const ES_DATA_TYPE_DATE = 'date'; - - /** @deprecated */ - const ES_DATA_TYPE_ARRAY = 'array'; - /**#@-*/ - - /** - * Get field type. - * - * @deprecated - * @see ResolverInterface::getFieldType - * - * @param AbstractAttribute $attribute - * @return string - * @since 100.1.0 - */ - public function getFieldType($attribute) - { - trigger_error('Class is deprecated', E_USER_DEPRECATED); - $backendType = $attribute->getBackendType(); - $frontendInput = $attribute->getFrontendInput(); - - if ($backendType === 'timestamp') { - $fieldType = self::ES_DATA_TYPE_DATE; - } elseif ((in_array($backendType, ['int', 'smallint'], true) - || (in_array($frontendInput, ['select', 'boolean'], true) && $backendType !== 'varchar')) - && !$attribute->getIsUserDefined() - ) { - $fieldType = self::ES_DATA_TYPE_INT; - } elseif ($backendType === 'decimal') { - $fieldType = self::ES_DATA_TYPE_FLOAT; - } else { - $fieldType = self::ES_DATA_TYPE_TEXT; - } - - return $fieldType; - } -} diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/Container/Attribute.php b/app/code/Magento/Elasticsearch/Model/Adapter/Container/Attribute.php deleted file mode 100644 index 12ca1e1a741ff..0000000000000 --- a/app/code/Magento/Elasticsearch/Model/Adapter/Container/Attribute.php +++ /dev/null @@ -1,99 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Model\Adapter\Container; - -use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection; -use Magento\Catalog\Model\ResourceModel\Eav\Attribute as EavAttribute; - -/** - * @deprecated 100.2.0 - * This class is used only in deprecated \Magento\Elasticsearch\Model\Adapter\DataMapper\ProductDataMapper - * and must not be used for new code - */ -class Attribute -{ - /** - * @var string[] - */ - private $idToCodeMap = []; - - /** - * @var Collection - */ - private $attributeCollection; - - /** - * @var EavAttribute[] - */ - private $attributes = []; - - /** - * @param Collection $attributeCollection - */ - public function __construct(Collection $attributeCollection) - { - $this->attributeCollection = $attributeCollection; - } - - /** - * @param int $attributeId - * @return string - */ - public function getAttributeCodeById($attributeId) - { - if (!array_key_exists($attributeId, $this->idToCodeMap)) { - $code = $attributeId === 'options' - ? 'options' - : $this->attributeCollection->getItemById($attributeId)->getAttributeCode(); - $this->idToCodeMap[$attributeId] = $code; - } - return $this->idToCodeMap[$attributeId]; - } - - /** - * @param string $attributeCode - * @return int - */ - public function getAttributeIdByCode($attributeCode) - { - if (!array_key_exists($attributeCode, array_flip($this->idToCodeMap))) { - $attributeId = $attributeCode === 'options' - ? 'options' - : $this->attributeCollection->getItemByColumnValue('attribute_code', $attributeCode)->getId(); - $this->idToCodeMap[$attributeId] = $attributeCode; - } - $codeToIdMap = array_flip($this->idToCodeMap); - return $codeToIdMap[$attributeCode]; - } - - /** - * @param string $attributeCode - * @return EavAttribute|null - */ - public function getAttribute($attributeCode) - { - $searchableAttributes = $this->getAttributes(); - return array_key_exists($attributeCode, $searchableAttributes) - ? $searchableAttributes[$attributeCode] - : null; - } - - /** - * @return EavAttribute[] - */ - public function getAttributes() - { - if (0 === count($this->attributes)) { - /** @var Collection $attributesCollection */ - $attributesCollection = $this->attributeCollection; - foreach ($attributesCollection as $attribute) { - /** @var EavAttribute $attribute */ - $this->attributes[$attribute->getAttributeCode()] = $attribute; - } - } - return $this->attributes; - } -} diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/DataMapperInterface.php b/app/code/Magento/Elasticsearch/Model/Adapter/DataMapperInterface.php deleted file mode 100644 index e43a4da065314..0000000000000 --- a/app/code/Magento/Elasticsearch/Model/Adapter/DataMapperInterface.php +++ /dev/null @@ -1,24 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Elasticsearch\Model\Adapter; - -/** - * @deprecated 100.2.0 - * @see \Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface - */ -interface DataMapperInterface -{ - /** - * Prepare index data for using in search engine metadata - * - * @param int $entityId - * @param array $entityIndexData - * @param int $storeId - * @param array $context - * @return array - */ - public function map($entityId, array $entityIndexData, $storeId, $context = []); -} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Index/IndexNameResolverTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Adapter/Index/IndexNameResolverTest.php similarity index 99% rename from app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Index/IndexNameResolverTest.php rename to app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Adapter/Index/IndexNameResolverTest.php index 0491517fbe979..e7e4faadd9815 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/Index/IndexNameResolverTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Elasticsearch5/Model/Adapter/Index/IndexNameResolverTest.php @@ -3,7 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Elasticsearch\Test\Unit\Model\Adapter\Index; +namespace Magento\Elasticsearch\Test\Unit\Elasticsearch5\Model\Adapter\Index; use Magento\Elasticsearch\Model\Adapter\Index\IndexNameResolver; use Psr\Log\LoggerInterface; diff --git a/app/code/Magento/Elasticsearch7/composer.json b/app/code/Magento/Elasticsearch7/composer.json index c6af833231be1..f5ca96990ee18 100644 --- a/app/code/Magento/Elasticsearch7/composer.json +++ b/app/code/Magento/Elasticsearch7/composer.json @@ -4,14 +4,14 @@ "require": { "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "*", - "magento/module-advanced-search": "*", - "magento/module-catalog-search": "*", - "magento/module-search": "*", "magento/module-elasticsearch": "*", - "elasticsearch/elasticsearch": "~7.6" + "elasticsearch/elasticsearch": "~7.6", + "magento/module-advanced-search": "*", + "magento/module-catalog-search": "*" }, "suggest": { - "magento/module-config": "*" + "magento/module-config": "*", + "magento/module-search": "*" }, "type": "magento2-module", "license": [ From 2d63d4ff3a8e506361f69657887f4a5fef194bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Tue, 17 Mar 2020 22:17:59 +0100 Subject: [PATCH 326/369] Exclude chart.js from js bundling --- app/design/frontend/Magento/blank/etc/view.xml | 1 + app/design/frontend/Magento/luma/etc/view.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/design/frontend/Magento/blank/etc/view.xml b/app/design/frontend/Magento/blank/etc/view.xml index 5884699af15cd..39ca6c1c7f8e3 100644 --- a/app/design/frontend/Magento/blank/etc/view.xml +++ b/app/design/frontend/Magento/blank/etc/view.xml @@ -259,6 +259,7 @@ <var name="bundle_size">1MB</var> </vars> <exclude> + <item type="file">Lib::chartjs/Chart.min.js</item> <item type="file">Lib::jquery/jquery.min.js</item> <item type="file">Lib::jquery/jquery-ui-1.9.2.js</item> <item type="file">Lib::jquery/jquery.details.js</item> diff --git a/app/design/frontend/Magento/luma/etc/view.xml b/app/design/frontend/Magento/luma/etc/view.xml index a2802b7e374f3..beb74c6f2cd20 100644 --- a/app/design/frontend/Magento/luma/etc/view.xml +++ b/app/design/frontend/Magento/luma/etc/view.xml @@ -270,6 +270,7 @@ <var name="bundle_size">1MB</var> </vars> <exclude> + <item type="file">Lib::chartjs/Chart.min.js</item> <item type="file">Lib::jquery/jquery.min.js</item> <item type="file">Lib::jquery/jquery-ui-1.9.2.js</item> <item type="file">Lib::jquery/jquery.details.js</item> From d634cac36720f4bc7d41d2f690bef7205e9452af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Wed, 18 Mar 2020 00:14:30 +0100 Subject: [PATCH 327/369] Cleanup ObjectManager usage - Magento_Catalog ViewModel,Plugin --- .../Plugin/Model/ResourceModel/Config.php | 43 +++--- .../Plugin/Model/ResourceModel/ConfigTest.php | 134 ++++++++++-------- .../ViewModel/Product/BreadcrumbsTest.php | 69 +++++---- .../Catalog/ViewModel/Product/Breadcrumbs.php | 45 +++--- 4 files changed, 155 insertions(+), 136 deletions(-) diff --git a/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php b/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php index b942f5570f57d..a3e7a417e6e47 100644 --- a/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php +++ b/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php @@ -3,9 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Plugin\Model\ResourceModel; -use Magento\Framework\App\ObjectManager; +use Magento\Eav\Model\Cache\Type; +use Magento\Eav\Model\Entity\Attribute; +use Magento\Framework\App\Cache\StateInterface; +use Magento\Framework\App\CacheInterface; use Magento\Framework\Serialize\SerializerInterface; /** @@ -21,12 +26,12 @@ class Config /**#@-*/ /**#@-*/ - protected $cache; + private $cache; /** - * @var bool|null + * @var bool */ - protected $isCacheEnabled = null; + private $isCacheEnabled; /** * @var SerializerInterface @@ -34,30 +39,30 @@ class Config private $serializer; /** - * @param \Magento\Framework\App\CacheInterface $cache - * @param \Magento\Framework\App\Cache\StateInterface $cacheState + * @param CacheInterface $cache + * @param StateInterface $cacheState * @param SerializerInterface $serializer */ public function __construct( - \Magento\Framework\App\CacheInterface $cache, - \Magento\Framework\App\Cache\StateInterface $cacheState, - SerializerInterface $serializer = null + CacheInterface $cache, + StateInterface $cacheState, + SerializerInterface $serializer ) { $this->cache = $cache; - $this->isCacheEnabled = $cacheState->isEnabled(\Magento\Eav\Model\Cache\Type::TYPE_IDENTIFIER); - $this->serializer = $serializer ?: ObjectManager::getInstance()->get(SerializerInterface::class); + $this->isCacheEnabled = $cacheState->isEnabled(Type::TYPE_IDENTIFIER); + $this->serializer = $serializer; } /** * Cache attribute used in listing. * * @param \Magento\Catalog\Model\ResourceModel\Config $config - * @param \Closure $proceed + * @param callable $proceed * @return array */ public function aroundGetAttributesUsedInListing( \Magento\Catalog\Model\ResourceModel\Config $config, - \Closure $proceed + callable $proceed ) { $cacheId = self::PRODUCT_LISTING_ATTRIBUTES_CACHE_ID . $config->getEntityTypeId() . '_' . $config->getStoreId(); if ($this->isCacheEnabled && ($attributes = $this->cache->load($cacheId))) { @@ -69,8 +74,8 @@ public function aroundGetAttributesUsedInListing( $this->serializer->serialize($attributes), $cacheId, [ - \Magento\Eav\Model\Cache\Type::CACHE_TAG, - \Magento\Eav\Model\Entity\Attribute::CACHE_TAG + Type::CACHE_TAG, + Attribute::CACHE_TAG ] ); } @@ -81,12 +86,12 @@ public function aroundGetAttributesUsedInListing( * Cache attributes used for sorting. * * @param \Magento\Catalog\Model\ResourceModel\Config $config - * @param \Closure $proceed + * @param callable $proceed * @return array */ public function aroundGetAttributesUsedForSortBy( \Magento\Catalog\Model\ResourceModel\Config $config, - \Closure $proceed + callable $proceed ) { $cacheId = self::PRODUCT_LISTING_SORT_BY_ATTRIBUTES_CACHE_ID . $config->getEntityTypeId() . '_' . $config->getStoreId(); @@ -99,8 +104,8 @@ public function aroundGetAttributesUsedForSortBy( $this->serializer->serialize($attributes), $cacheId, [ - \Magento\Eav\Model\Cache\Type::CACHE_TAG, - \Magento\Eav\Model\Entity\Attribute::CACHE_TAG + Type::CACHE_TAG, + Attribute::CACHE_TAG ] ); } diff --git a/app/code/Magento/Catalog/Test/Unit/Plugin/Model/ResourceModel/ConfigTest.php b/app/code/Magento/Catalog/Test/Unit/Plugin/Model/ResourceModel/ConfigTest.php index f36c934ca9acf..142de8fd1c5df 100644 --- a/app/code/Magento/Catalog/Test/Unit/Plugin/Model/ResourceModel/ConfigTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Plugin/Model/ResourceModel/ConfigTest.php @@ -3,43 +3,59 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Test\Unit\Plugin\Model\ResourceModel; +use Magento\Catalog\Model\ResourceModel\Config as ConfigResourceModel; use Magento\Catalog\Plugin\Model\ResourceModel\Config; +use Magento\Eav\Model\Cache\Type; +use Magento\Eav\Model\Entity\Attribute; +use Magento\Framework\App\Cache\StateInterface; +use Magento\Framework\App\CacheInterface; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; -class ConfigTest extends \PHPUnit\Framework\TestCase +class ConfigTest extends TestCase { - /** @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $cache; + /** + * @var CacheInterface|MockObject + */ + private $cacheMock; - /** @var \Magento\Framework\App\Cache\StateInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $cacheState; + /** + * @var StateInterface|MockObject + */ + private $cacheStateMock; - /** @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $serializer; + /** + * @var SerializerInterface|MockObject + */ + private $serializerMock; - /** @var \Magento\Catalog\Model\ResourceModel\Config|\PHPUnit_Framework_MockObject_MockObject */ - private $subject; + /** + * @var ConfigResourceModel|MockObject + */ + private $configResourceModelMock; protected function setUp() { - $this->cache = $this->createMock(\Magento\Framework\App\CacheInterface::class); - $this->cacheState = $this->createMock(\Magento\Framework\App\Cache\StateInterface::class); - $this->serializer = $this->createMock(SerializerInterface::class); - $this->subject = $this->createMock(\Magento\Catalog\Model\ResourceModel\Config::class); + $this->cacheMock = $this->createMock(CacheInterface::class); + $this->cacheStateMock = $this->createMock(StateInterface::class); + $this->serializerMock = $this->createMock(SerializerInterface::class); + $this->configResourceModelMock = $this->createMock(ConfigResourceModel::class); } public function testGetAttributesUsedInListingOnCacheDisabled() { - $this->cache->expects($this->never())->method('load'); + $this->cacheMock->expects($this->never())->method('load'); $this->assertEquals( ['attributes'], $this->getConfig(false)->aroundGetAttributesUsedInListing( - $this->subject, + $this->configResourceModelMock, $this->mockPluginProceed(['attributes']) ) ); @@ -51,13 +67,13 @@ public function testGetAttributesUsedInListingFromCache() $storeId = 'store'; $attributes = ['attributes']; $serializedAttributes = '["attributes"]'; - $this->subject->expects($this->any())->method('getEntityTypeId')->willReturn($entityTypeId); - $this->subject->expects($this->any())->method('getStoreId')->willReturn($storeId); - $cacheId = \Magento\Catalog\Plugin\Model\ResourceModel\Config::PRODUCT_LISTING_ATTRIBUTES_CACHE_ID + $this->configResourceModelMock->method('getEntityTypeId')->willReturn($entityTypeId); + $this->configResourceModelMock->method('getStoreId')->willReturn($storeId); + $cacheId = Config::PRODUCT_LISTING_ATTRIBUTES_CACHE_ID . $entityTypeId . '_' . $storeId; - $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn($serializedAttributes); - $this->serializer->expects($this->once()) + $this->cacheMock->method('load')->with($cacheId)->willReturn($serializedAttributes); + $this->serializerMock->expects($this->once()) ->method('unserialize') ->with($serializedAttributes) ->willReturn($attributes); @@ -65,7 +81,7 @@ public function testGetAttributesUsedInListingFromCache() $this->assertEquals( $attributes, $this->getConfig(true)->aroundGetAttributesUsedInListing( - $this->subject, + $this->configResourceModelMock, $this->mockPluginProceed() ) ); @@ -77,31 +93,31 @@ public function testGetAttributesUsedInListingWithCacheSave() $storeId = 'store'; $attributes = ['attributes']; $serializedAttributes = '["attributes"]'; - $this->subject->expects($this->any())->method('getEntityTypeId')->willReturn($entityTypeId); - $this->subject->expects($this->any())->method('getStoreId')->willReturn($storeId); - $cacheId = \Magento\Catalog\Plugin\Model\ResourceModel\Config::PRODUCT_LISTING_ATTRIBUTES_CACHE_ID + $this->configResourceModelMock->method('getEntityTypeId')->willReturn($entityTypeId); + $this->configResourceModelMock->method('getStoreId')->willReturn($storeId); + $cacheId = Config::PRODUCT_LISTING_ATTRIBUTES_CACHE_ID . $entityTypeId . '_' . $storeId; - $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn(false); - $this->serializer->expects($this->never()) + $this->cacheMock->method('load')->with($cacheId)->willReturn(false); + $this->serializerMock->expects($this->never()) ->method('unserialize'); - $this->serializer->expects($this->once()) + $this->serializerMock->expects($this->once()) ->method('serialize') ->with($attributes) ->willReturn($serializedAttributes); - $this->cache->expects($this->any())->method('save')->with( + $this->cacheMock->method('save')->with( $serializedAttributes, $cacheId, [ - \Magento\Eav\Model\Cache\Type::CACHE_TAG, - \Magento\Eav\Model\Entity\Attribute::CACHE_TAG + Type::CACHE_TAG, + Attribute::CACHE_TAG ] ); $this->assertEquals( $attributes, $this->getConfig(true)->aroundGetAttributesUsedInListing( - $this->subject, + $this->configResourceModelMock, $this->mockPluginProceed($attributes) ) ); @@ -109,12 +125,12 @@ public function testGetAttributesUsedInListingWithCacheSave() public function testGetAttributesUsedForSortByOnCacheDisabled() { - $this->cache->expects($this->never())->method('load'); + $this->cacheMock->expects($this->never())->method('load'); $this->assertEquals( ['attributes'], $this->getConfig(false)->aroundGetAttributesUsedForSortBy( - $this->subject, + $this->configResourceModelMock, $this->mockPluginProceed(['attributes']) ) ); @@ -126,12 +142,12 @@ public function testGetAttributesUsedForSortByFromCache() $storeId = 'store'; $attributes = ['attributes']; $serializedAttributes = '["attributes"]'; - $this->subject->expects($this->any())->method('getEntityTypeId')->willReturn($entityTypeId); - $this->subject->expects($this->any())->method('getStoreId')->willReturn($storeId); - $cacheId = \Magento\Catalog\Plugin\Model\ResourceModel\Config::PRODUCT_LISTING_SORT_BY_ATTRIBUTES_CACHE_ID + $this->configResourceModelMock->method('getEntityTypeId')->willReturn($entityTypeId); + $this->configResourceModelMock->method('getStoreId')->willReturn($storeId); + $cacheId = Config::PRODUCT_LISTING_SORT_BY_ATTRIBUTES_CACHE_ID . $entityTypeId . '_' . $storeId; - $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn($serializedAttributes); - $this->serializer->expects($this->once()) + $this->cacheMock->method('load')->with($cacheId)->willReturn($serializedAttributes); + $this->serializerMock->expects($this->once()) ->method('unserialize') ->with($serializedAttributes) ->willReturn($attributes); @@ -139,7 +155,7 @@ public function testGetAttributesUsedForSortByFromCache() $this->assertEquals( $attributes, $this->getConfig(true)->aroundGetAttributesUsedForSortBy( - $this->subject, + $this->configResourceModelMock, $this->mockPluginProceed() ) ); @@ -151,30 +167,30 @@ public function testGetAttributesUsedForSortByWithCacheSave() $storeId = 'store'; $attributes = ['attributes']; $serializedAttributes = '["attributes"]'; - $this->subject->expects($this->any())->method('getEntityTypeId')->willReturn($entityTypeId); - $this->subject->expects($this->any())->method('getStoreId')->willReturn($storeId); - $cacheId = \Magento\Catalog\Plugin\Model\ResourceModel\Config::PRODUCT_LISTING_SORT_BY_ATTRIBUTES_CACHE_ID + $this->configResourceModelMock->method('getEntityTypeId')->willReturn($entityTypeId); + $this->configResourceModelMock->method('getStoreId')->willReturn($storeId); + $cacheId = Config::PRODUCT_LISTING_SORT_BY_ATTRIBUTES_CACHE_ID . $entityTypeId . '_' . $storeId; - $this->cache->expects($this->any())->method('load')->with($cacheId)->willReturn(false); - $this->serializer->expects($this->never()) + $this->cacheMock->method('load')->with($cacheId)->willReturn(false); + $this->serializerMock->expects($this->never()) ->method('unserialize'); - $this->serializer->expects($this->once()) + $this->serializerMock->expects($this->once()) ->method('serialize') ->with($attributes) ->willReturn($serializedAttributes); - $this->cache->expects($this->any())->method('save')->with( + $this->cacheMock->method('save')->with( $serializedAttributes, $cacheId, [ - \Magento\Eav\Model\Cache\Type::CACHE_TAG, - \Magento\Eav\Model\Entity\Attribute::CACHE_TAG + Type::CACHE_TAG, + Attribute::CACHE_TAG ] ); $this->assertEquals( $attributes, $this->getConfig(true)->aroundGetAttributesUsedForSortBy( - $this->subject, + $this->configResourceModelMock, $this->mockPluginProceed($attributes) ) ); @@ -182,29 +198,33 @@ public function testGetAttributesUsedForSortByWithCacheSave() /** * @param bool $cacheEnabledFlag - * @return \Magento\Catalog\Plugin\Model\ResourceModel\Config + * + * @return Config */ protected function getConfig($cacheEnabledFlag) { - $this->cacheState->expects($this->any())->method('isEnabled') - ->with(\Magento\Eav\Model\Cache\Type::TYPE_IDENTIFIER)->willReturn($cacheEnabledFlag); + $this->cacheStateMock->method('isEnabled') + ->with(Type::TYPE_IDENTIFIER) + ->willReturn($cacheEnabledFlag); + return (new ObjectManager($this))->getObject( - \Magento\Catalog\Plugin\Model\ResourceModel\Config::class, + Config::class, [ - 'cache' => $this->cache, - 'cacheState' => $this->cacheState, - 'serializer' => $this->serializer, + 'cache' => $this->cacheMock, + 'cacheState' => $this->cacheStateMock, + 'serializer' => $this->serializerMock, ] ); } /** * @param mixed $returnValue + * * @return callable */ protected function mockPluginProceed($returnValue = null) { - return function () use ($returnValue) { + return static function () use ($returnValue) { return $returnValue; }; } diff --git a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php index a442041660893..91bb534fff627 100644 --- a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php +++ b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php @@ -11,65 +11,72 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\ViewModel\Product\Breadcrumbs; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Escaper; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\Serialize\Serializer\JsonHexTag; +use Magento\Store\Model\ScopeInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; /** * Unit test for Magento\Catalog\ViewModel\Product\Breadcrumbs. */ -class BreadcrumbsTest extends \PHPUnit\Framework\TestCase +class BreadcrumbsTest extends TestCase { + private const XML_PATH_CATEGORY_URL_SUFFIX = 'catalog/seo/category_url_suffix'; + private const XML_PATH_PRODUCT_USE_CATEGORIES = 'catalog/seo/product_use_categories'; + /** * @var Breadcrumbs */ private $viewModel; /** - * @var CatalogHelper|\PHPUnit_Framework_MockObject_MockObject + * @var ObjectManager */ - private $catalogHelper; + private $objectManager; /** - * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CatalogHelper|MockObject */ - private $scopeConfig; + private $catalogHelperMock; /** - * @var ObjectManager + * @var ScopeConfigInterface|MockObject */ - private $objectManager; + private $scopeConfigMock; /** - * @var JsonHexTag|\PHPUnit_Framework_MockObject_MockObject + * @var JsonHexTag|MockObject */ - private $serializer; + private $serializerMock; /** * @inheritdoc */ protected function setUp() : void { - $this->catalogHelper = $this->getMockBuilder(CatalogHelper::class) + $this->catalogHelperMock = $this->getMockBuilder(CatalogHelper::class) ->setMethods(['getProduct']) ->disableOriginalConstructor() ->getMock(); - $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) ->setMethods(['getValue', 'isSetFlag']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $escaper = $this->getObjectManager()->getObject(\Magento\Framework\Escaper::class); + $escaper = $this->getObjectManager()->getObject(Escaper::class); - $this->serializer = $this->createMock(JsonHexTag::class); + $this->serializerMock = $this->createMock(JsonHexTag::class); $this->viewModel = $this->getObjectManager()->getObject( Breadcrumbs::class, [ - 'catalogData' => $this->catalogHelper, - 'scopeConfig' => $this->scopeConfig, + 'catalogData' => $this->catalogHelperMock, + 'scopeConfig' => $this->scopeConfigMock, 'escaper' => $escaper, - 'jsonSerializer' => $this->serializer + 'jsonSerializer' => $this->serializerMock ] ); } @@ -79,9 +86,9 @@ protected function setUp() : void */ public function testGetCategoryUrlSuffix() : void { - $this->scopeConfig->expects($this->once()) + $this->scopeConfigMock->expects($this->once()) ->method('getValue') - ->with('catalog/seo/category_url_suffix', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) + ->with(static::XML_PATH_CATEGORY_URL_SUFFIX, ScopeInterface::SCOPE_STORE) ->willReturn('.html'); $this->assertEquals('.html', $this->viewModel->getCategoryUrlSuffix()); @@ -92,9 +99,9 @@ public function testGetCategoryUrlSuffix() : void */ public function testIsCategoryUsedInProductUrl() : void { - $this->scopeConfig->expects($this->once()) + $this->scopeConfigMock->expects($this->once()) ->method('isSetFlag') - ->with('catalog/seo/product_use_categories', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) + ->with(static::XML_PATH_PRODUCT_USE_CATEGORIES, ScopeInterface::SCOPE_STORE) ->willReturn(false); $this->assertFalse($this->viewModel->isCategoryUsedInProductUrl()); @@ -105,11 +112,12 @@ public function testIsCategoryUsedInProductUrl() : void * * @param Product|null $product * @param string $expectedName + * * @return void */ public function testGetProductName($product, string $expectedName) : void { - $this->catalogHelper->expects($this->atLeastOnce()) + $this->catalogHelperMock->expects($this->atLeastOnce()) ->method('getProduct') ->willReturn($product); @@ -132,27 +140,26 @@ public function productDataProvider() : array * * @param Product|null $product * @param string $expectedJson + * * @return void */ - public function testGetJsonConfiguration($product, string $expectedJson) : void + public function testGetJsonConfigurationHtmlEscaped($product, string $expectedJson) : void { - $this->catalogHelper->expects($this->atLeastOnce()) + $this->catalogHelperMock->expects($this->atLeastOnce()) ->method('getProduct') ->willReturn($product); - $this->scopeConfig->expects($this->any()) - ->method('isSetFlag') - ->with('catalog/seo/product_use_categories', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) + $this->scopeConfigMock->method('isSetFlag') + ->with(static::XML_PATH_PRODUCT_USE_CATEGORIES, ScopeInterface::SCOPE_STORE) ->willReturn(false); - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->with('catalog/seo/category_url_suffix', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) + $this->scopeConfigMock->method('getValue') + ->with(static::XML_PATH_CATEGORY_URL_SUFFIX, ScopeInterface::SCOPE_STORE) ->willReturn('."html'); - $this->serializer->expects($this->once())->method('serialize')->willReturn($expectedJson); + $this->serializerMock->expects($this->once())->method('serialize')->willReturn($expectedJson); - $this->assertEquals($expectedJson, $this->viewModel->getJsonConfiguration()); + $this->assertEquals($expectedJson, $this->viewModel->getJsonConfigurationHtmlEscaped()); } /** diff --git a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php index 1aad46fc1e2f5..d3c8c406ee34d 100644 --- a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php +++ b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php @@ -3,26 +3,27 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\ViewModel\Product; use Magento\Catalog\Helper\Data; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; -use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\Serialize\Serializer\JsonHexTag; use Magento\Framework\View\Element\Block\ArgumentInterface; use Magento\Framework\Escaper; +use Magento\Store\Model\ScopeInterface; /** * Product breadcrumbs view model. */ class Breadcrumbs extends DataObject implements ArgumentInterface { + private const XML_PATH_CATEGORY_URL_SUFFIX = 'catalog/seo/category_url_suffix'; + private const XML_PATH_PRODUCT_USE_CATEGORIES = 'catalog/seo/product_use_categories'; + /** - * Catalog data. - * * @var Data */ private $catalogData; @@ -45,24 +46,21 @@ class Breadcrumbs extends DataObject implements ArgumentInterface /** * @param Data $catalogData * @param ScopeConfigInterface $scopeConfig - * @param Json|null $json - * @param Escaper|null $escaper - * @param JsonHexTag|null $jsonSerializer - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param Escaper $escaper + * @param JsonHexTag $jsonSerializer */ public function __construct( Data $catalogData, ScopeConfigInterface $scopeConfig, - Json $json = null, - Escaper $escaper = null, - JsonHexTag $jsonSerializer = null + Escaper $escaper, + JsonHexTag $jsonSerializer ) { parent::__construct(); $this->catalogData = $catalogData; $this->scopeConfig = $scopeConfig; - $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); - $this->jsonSerializer = $jsonSerializer ?: ObjectManager::getInstance()->get(JsonHexTag::class); + $this->escaper = $escaper; + $this->jsonSerializer = $jsonSerializer; } /** @@ -73,8 +71,8 @@ public function __construct( public function getCategoryUrlSuffix() { return $this->scopeConfig->getValue( - 'catalog/seo/category_url_suffix', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + static::XML_PATH_CATEGORY_URL_SUFFIX, + ScopeInterface::SCOPE_STORE ); } @@ -86,8 +84,8 @@ public function getCategoryUrlSuffix() public function isCategoryUsedInProductUrl(): bool { return $this->scopeConfig->isSetFlag( - 'catalog/seo/product_use_categories', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + static::XML_PATH_PRODUCT_USE_CATEGORIES, + ScopeInterface::SCOPE_STORE ); } @@ -108,7 +106,7 @@ public function getProductName(): string * * @return string */ - public function getJsonConfigurationHtmlEscaped() : string + public function getJsonConfigurationHtmlEscaped(): string { return $this->jsonSerializer->serialize( [ @@ -120,15 +118,4 @@ public function getJsonConfigurationHtmlEscaped() : string ] ); } - - /** - * Returns breadcrumb json. - * - * @return string - * @deprecated in favor of new method with name {suffix}Html{postfix}() - */ - public function getJsonConfiguration() - { - return $this->getJsonConfigurationHtmlEscaped(); - } } From 24481e96c171a51ca326f107ca76662bdec8732d Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Wed, 18 Mar 2020 09:51:59 +0200 Subject: [PATCH 328/369] MC-32494: Order placed within PayPal Payflow Pro is not set to Suspected Fraud status when fraud filters are triggered --- .../Payflow/Service/Response/Transaction.php | 1 + .../Paypal/Model/Payflow/Transparent.php | 171 ++++++++++++++++-- .../Paypal/Plugin/TransparentOrderPayment.php | 60 ++++++ .../Unit/Model/Payflow/TransparentTest.php | 11 +- .../adminhtml/system/paypal_payflowpro.xml | 6 + app/code/Magento/Paypal/etc/config.xml | 1 + app/code/Magento/Paypal/etc/di.xml | 3 + app/code/Magento/Paypal/i18n/en_US.csv | 1 + .../Order/Payment/State/AuthorizeCommand.php | 26 ++- .../Order/Payment/State/CaptureCommand.php | 26 ++- .../Sales/etc/extension_attributes.xml | 3 + .../Paypal/Model/Payflow/TransparentTest.php | 167 +++++++++++++++++ 12 files changed, 458 insertions(+), 18 deletions(-) create mode 100644 app/code/Magento/Paypal/Plugin/TransparentOrderPayment.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/TransparentTest.php diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php index 1e97ac8b8c766..be650baa22d75 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Response/Transaction.php @@ -117,6 +117,7 @@ public function savePaymentInQuote($response, $cartId) $payment->setData(OrderPaymentInterface::CC_TYPE, $response->getData(OrderPaymentInterface::CC_TYPE)); $payment->setAdditionalInformation(Payflowpro::PNREF, $response->getData(Payflowpro::PNREF)); + $payment->setAdditionalInformation('result_code', $response->getData('result')); $expDate = $response->getData('expdate'); $expMonth = $this->getCcExpMonth($expDate); diff --git a/app/code/Magento/Paypal/Model/Payflow/Transparent.php b/app/code/Magento/Paypal/Model/Payflow/Transparent.php index 6569bdb20edfe..44a9b6f73c80e 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Transparent.php +++ b/app/code/Magento/Paypal/Model/Payflow/Transparent.php @@ -7,23 +7,24 @@ namespace Magento\Paypal\Model\Payflow; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\State\InvalidTransitionException; use Magento\Payment\Helper\Formatter; use Magento\Payment\Model\InfoInterface; -use Magento\Paypal\Model\Payflowpro; -use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; -use Magento\Sales\Model\Order\Payment; -use Magento\Paypal\Model\Payflow\Service\Gateway; -use Magento\Framework\Exception\LocalizedException; -use Magento\Payment\Model\Method\TransparentInterface; use Magento\Payment\Model\Method\ConfigInterfaceFactory; -use Magento\Framework\Exception\State\InvalidTransitionException; +use Magento\Payment\Model\Method\TransparentInterface; +use Magento\Payment\Model\MethodInterface; +use Magento\Paypal\Model\Payflow\Service\Gateway; use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface; use Magento\Paypal\Model\Payflow\Service\Response\Validator\ResponseValidator; +use Magento\Paypal\Model\Payflowpro; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; +use Magento\Sales\Model\Order\Payment; use Magento\Vault\Api\Data\PaymentTokenInterface; use Magento\Vault\Api\Data\PaymentTokenInterfaceFactory; /** - * Payflow Pro payment gateway model + * Payflow Pro payment gateway model (transparent redirect). * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -35,6 +36,16 @@ class Transparent extends Payflowpro implements TransparentInterface const CC_VAULT_CODE = 'payflowpro_cc_vault'; + /** + * Result code of account verification transaction request. + */ + private const RESULT_CODE = 'result_code'; + + /** + * Fraud Management Filters config setting. + */ + private const CONFIG_FMF = 'fmf'; + /** * @var string */ @@ -45,6 +56,13 @@ class Transparent extends Payflowpro implements TransparentInterface */ protected $_infoBlockType = \Magento\Paypal\Block\Payment\Info::class; + /** + * Fetch transaction details availability option. + * + * @var bool + */ + protected $_canFetchTransactionInfo = false; + /** * @var ResponseValidator */ @@ -165,6 +183,14 @@ public function validate() */ public function authorize(InfoInterface $payment, $amount) { + if ($this->isFraudDetected($payment)) { + $this->markPaymentAsFraudulent($payment); + return $this; + } + + $zeroAmountAuthorizationId = $this->getZeroAmountAuthorizationId($payment); + /** @var PaymentTokenInterface $vaultPaymentToken */ + $vaultPaymentToken = $payment->getExtensionAttributes()->getVaultPaymentToken(); /** @var Payment $payment */ $request = $this->buildBasicRequest(); @@ -177,9 +203,9 @@ public function authorize(InfoInterface $payment, $amount) $payPalCart = $this->payPalCartFactory->create(['salesModel' => $order]); $payPalCart->getAmounts(); - $token = $payment->getAdditionalInformation(self::PNREF); + $parentTransactionId = $vaultPaymentToken ? $vaultPaymentToken->getGatewayToken() : $zeroAmountAuthorizationId; $request->setData('trxtype', self::TRXTYPE_AUTH_ONLY); - $request->setData('origid', $token); + $request->setData('origid', $parentTransactionId); $request->setData('amt', $this->formatPrice($amount)); $request->setData('currency', $order->getBaseCurrencyCode()); $request->setData('itemamt', $this->formatPrice($payPalCart->getSubtotal())); @@ -200,10 +226,15 @@ public function authorize(InfoInterface $payment, $amount) $this->setTransStatus($payment, $response); - $this->createPaymentToken($payment, $token); + if ($vaultPaymentToken) { + $payment->setParentTransactionId($vaultPaymentToken->getGatewayToken()); + } else { + $this->createPaymentToken($payment, $zeroAmountAuthorizationId); + } $payment->unsAdditionalInformation(self::CC_DETAILS); $payment->unsAdditionalInformation(self::PNREF); + $payment->unsAdditionalInformation(self::RESULT_CODE); return $this; } @@ -291,14 +322,126 @@ private function getPaymentExtensionAttributes(Payment $payment) */ public function capture(InfoInterface $payment, $amount) { + if ($this->isFraudDetected($payment)) { + $this->markPaymentAsFraudulent($payment); + return $this; + } + /** @var Payment $payment */ - $token = $payment->getAdditionalInformation(self::PNREF); + $zeroAmountAuthorizationId = $this->getZeroAmountAuthorizationId($payment); + /** @var PaymentTokenInterface $vaultPaymentToken */ + $vaultPaymentToken = $payment->getExtensionAttributes()->getVaultPaymentToken(); + if ($vaultPaymentToken && empty($zeroAmountAuthorizationId)) { + $payment->setAdditionalInformation(self::PNREF, $vaultPaymentToken->getGatewayToken()); + if (!$payment->getParentTransactionId()) { + $payment->setParentTransactionId($vaultPaymentToken->getGatewayToken()); + } + } parent::capture($payment, $amount); - if ($token && !$payment->getAuthorizationTransaction()) { - $this->createPaymentToken($payment, $token); + if ($zeroAmountAuthorizationId && $vaultPaymentToken === null) { + $this->createPaymentToken($payment, $zeroAmountAuthorizationId); } return $this; } + + /** + * Attempt to accept a pending payment. + * + * Order acquires a payment review state based on results of PayPal account verification transaction (zero-amount + * authorization). For accepting a payment should be created PayPal reference transaction with a real order amount. + * Fraud Protection Service filters do not screen reference transactions. + * + * @param InfoInterface $payment + * @return bool + * @throws InvalidTransitionException + * @throws LocalizedException + */ + public function acceptPayment(InfoInterface $payment) + { + if ($this->getConfigPaymentAction() === MethodInterface::ACTION_AUTHORIZE_CAPTURE) { + $invoices = iterator_to_array($payment->getOrder()->getInvoiceCollection()); + $invoice = count($invoices) ? reset($invoices) : null; + $payment->capture($invoice); + } else { + $amount = $payment->getOrder()->getBaseGrandTotal(); + $payment->authorize(true, $amount); + } + + return true; + } + + /** + * Deny a pending payment. + * + * Order acquires a payment review state based on results of PayPal account verification transaction (zero-amount + * authorization). This transaction type cannot be voided, so we do not send any request to payment gateway. + * + * @param InfoInterface $payment + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function denyPayment(InfoInterface $payment) + { + return true; + } + + /** + * Marks payment as fraudulent. + * + * @param InfoInterface $payment + * @throws \Exception + */ + private function markPaymentAsFraudulent(InfoInterface $payment): void + { + $zeroAmountAuthorizationId = $this->getZeroAmountAuthorizationId($payment); + $payment->setTransactionId($zeroAmountAuthorizationId); + $payment->setIsTransactionClosed(0); + $payment->setIsTransactionPending(true); + $payment->setIsFraudDetected(true); + $this->createPaymentToken($payment, $zeroAmountAuthorizationId); + $fraudulentMsg = 'Order is suspended as an account verification transaction is suspected to be fraudulent.'; + $extensionAttributes = $this->getPaymentExtensionAttributes($payment); + $extensionAttributes->setNotificationMessage($fraudulentMsg); + $payment->unsAdditionalInformation(self::CC_DETAILS); + $payment->unsAdditionalInformation(self::PNREF); + $payment->unsAdditionalInformation(self::RESULT_CODE); + } + + /** + * Checks if fraud filters were triggered for the payment. + * + * For current PayPal PayflowPro transparent redirect integration + * Fraud Protection Service filters screen only account verification + * transaction (also known as zero dollar authorization). + * Following reference transaction with real dollar amount will not be screened + * by Fraud Protection Service. + * + * @param InfoInterface $payment + * @return bool + */ + private function isFraudDetected(InfoInterface $payment): bool + { + $resultCode = $payment->getAdditionalInformation(self::RESULT_CODE); + $isFmfEnabled = (bool)$this->getConfig()->getValue(self::CONFIG_FMF); + return $isFmfEnabled && $this->getZeroAmountAuthorizationId($payment) && in_array( + $resultCode, + [self::RESPONSE_CODE_DECLINED_BY_FILTER, self::RESPONSE_CODE_FRAUDSERVICE_FILTER] + ); + } + + /** + * Returns zero dollar authorization transaction id. + * + * PNREF (transaction id) is available in payment additional information only right after + * PayPal account verification transaction (also known as zero dollar authorization). + * + * @param InfoInterface $payment + * @return string + */ + private function getZeroAmountAuthorizationId(InfoInterface $payment): string + { + return (string)$payment->getAdditionalInformation(self::PNREF); + } } diff --git a/app/code/Magento/Paypal/Plugin/TransparentOrderPayment.php b/app/code/Magento/Paypal/Plugin/TransparentOrderPayment.php new file mode 100644 index 0000000000000..ab1d9c210d7d7 --- /dev/null +++ b/app/code/Magento/Paypal/Plugin/TransparentOrderPayment.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Plugin; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Sales\Api\InvoiceRepositoryInterface; +use Magento\Sales\Model\Order\Payment; + +/** + * Updates invoice transaction id for PayPal PayflowPro payment. + */ +class TransparentOrderPayment +{ + /** + * @var InvoiceRepositoryInterface + */ + private $invoiceRepository; + + /** + * @param InvoiceRepositoryInterface $invoiceRepository + */ + public function __construct(InvoiceRepositoryInterface $invoiceRepository) + { + $this->invoiceRepository = $invoiceRepository; + } + + /** + * Updates invoice transaction id. + * + * Accepting PayPal PayflowPro payment actually means executing new reference transaction + * based on account verification. So for existing pending invoice, transaction id should be updated + * with the id of last reference transaction. + * + * @param Payment $subject + * @param Payment $result + * @return Payment + * @throws LocalizedException + */ + public function afterAccept(Payment $subject, Payment $result): Payment + { + $paymentMethod = $subject->getMethodInstance(); + if (!$paymentMethod instanceof \Magento\Paypal\Model\Payflow\Transparent) { + return $result; + } + + $invoices = iterator_to_array($subject->getOrder()->getInvoiceCollection()); + $invoice = reset($invoices); + if ($invoice) { + $invoice->setTransactionId($subject->getLastTransId()); + $this->invoiceRepository->save($invoice); + } + + return $result; + } +} diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php index 9587600203561..acadbc80cd559 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php @@ -196,7 +196,9 @@ private function getPaymentExtensionInterfaceFactory() ->disableOriginalConstructor() ->getMock(); $orderPaymentExtension = $this->getMockBuilder(OrderPaymentExtensionInterface::class) - ->setMethods(['setVaultPaymentToken', 'getVaultPaymentToken']) + ->setMethods( + ['setVaultPaymentToken', 'getVaultPaymentToken', 'setNotificationMessage', 'getNotificationMessage'] + ) ->disableOriginalConstructor() ->getMock(); @@ -290,12 +292,17 @@ private function initPayment() $this->order = $this->getMockBuilder(Order::class) ->disableOriginalConstructor() ->getMock(); - + $paymentExtensionAttributes = $this->getMockBuilder(OrderPaymentExtensionInterface::class) + ->setMethods( + ['setVaultPaymentToken', 'getVaultPaymentToken', 'setNotificationMessage', 'getNotificationMessage'] + ) + ->getMockForAbstractClass(); $this->payment->method('getOrder')->willReturn($this->order); $this->payment->method('setTransactionId')->willReturnSelf(); $this->payment->method('setIsTransactionClosed')->willReturnSelf(); $this->payment->method('getCcExpYear')->willReturn('2019'); $this->payment->method('getCcExpMonth')->willReturn('05'); + $this->payment->method('getExtensionAttributes')->willReturn($paymentExtensionAttributes); return $this->payment; } diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml index c87a781f36c00..77ec9eb0d0069 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml @@ -170,6 +170,12 @@ <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <attribute type="shared">1</attribute> </field> + <field id="fmf" translate="label comment" type="select" sortOrder="45" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Fraud Management Filters</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <comment>Be sure to configure Fraud Management Filters in your PayPal account in "Service Settings/Fraud Protection" section. Attention! Please don't use Total Purchase Price Ceiling/Floor Filters. Current integration doesn't support them.</comment> + <config_path>payment/payflowpro/fmf</config_path> + </field> <group id="paypal_payflow_avs_check" translate="label" showInDefault="1" showInWebsite="1" sortOrder="80"> <label>CVV and AVS Settings</label> <field id="heading_avs_settings" translate="label" sortOrder="0" showInDefault="1" showInWebsite="1"> diff --git a/app/code/Magento/Paypal/etc/config.xml b/app/code/Magento/Paypal/etc/config.xml index 6c0601f80137d..24bb54a86103d 100644 --- a/app/code/Magento/Paypal/etc/config.xml +++ b/app/code/Magento/Paypal/etc/config.xml @@ -109,6 +109,7 @@ <cgi_url>https://payflowlink.paypal.com</cgi_url> <transaction_url_test_mode>https://pilot-payflowpro.paypal.com</transaction_url_test_mode> <transaction_url>https://payflowpro.paypal.com</transaction_url> + <fmf>0</fmf> <avs_street>0</avs_street> <avs_zip>0</avs_zip> <avs_international>0</avs_international> diff --git a/app/code/Magento/Paypal/etc/di.xml b/app/code/Magento/Paypal/etc/di.xml index 973ed0f91924c..e148320fdaf17 100644 --- a/app/code/Magento/Paypal/etc/di.xml +++ b/app/code/Magento/Paypal/etc/di.xml @@ -255,4 +255,7 @@ <type name="Magento\Framework\Session\SessionStartChecker"> <plugin name="transparent_session_checker" type="Magento\Paypal\Plugin\TransparentSessionChecker"/> </type> + <type name="Magento\Sales\Model\Order\Payment"> + <plugin name="paypal_transparent" type="Magento\Paypal\Plugin\TransparentOrderPayment"/> + </type> </config> diff --git a/app/code/Magento/Paypal/i18n/en_US.csv b/app/code/Magento/Paypal/i18n/en_US.csv index 4e47c4c1f9e9f..54dd611d49073 100644 --- a/app/code/Magento/Paypal/i18n/en_US.csv +++ b/app/code/Magento/Paypal/i18n/en_US.csv @@ -738,3 +738,4 @@ User,User "PayPal Guest Checkout Credit Card Icons","PayPal Guest Checkout Credit Card Icons" "Elektronisches Lastschriftverfahren - German ELV","Elektronisches Lastschriftverfahren - German ELV" "Please enter at least 0 and at most 65535","Please enter at least 0 and at most 65535" +"Order is suspended as an account verification transaction is suspected to be fraudulent.","Order is suspended as an account verification transaction is suspected to be fraudulent." diff --git a/app/code/Magento/Sales/Model/Order/Payment/State/AuthorizeCommand.php b/app/code/Magento/Sales/Model/Order/Payment/State/AuthorizeCommand.php index 98cf1babdc92d..89731b5130605 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/State/AuthorizeCommand.php +++ b/app/code/Magento/Sales/Model/Order/Payment/State/AuthorizeCommand.php @@ -11,6 +11,9 @@ use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\StatusResolver; +/** + * Process order state and status after authorize operation. + */ class AuthorizeCommand implements CommandInterface { /** @@ -28,6 +31,8 @@ public function __construct(StatusResolver $statusResolver = null) } /** + * Run command. + * * @param OrderPaymentInterface $payment * @param string|float $amount * @param OrderInterface $order @@ -50,6 +55,8 @@ public function execute(OrderPaymentInterface $payment, $amount, OrderInterface $message .= ' Order is suspended as its authorizing amount %1 is suspected to be fraudulent.'; } + $message = $this->getNotificationMessage($payment) ?? $message; + if (!isset($status)) { $status = $this->statusResolver->getOrderStatusByState($order, $state); } @@ -61,12 +68,29 @@ public function execute(OrderPaymentInterface $payment, $amount, OrderInterface } /** - * @deprecated 100.2.0 Replaced by a StatusResolver class call. + * Returns payment notification message. + * + * @param OrderPaymentInterface $payment + * @return string|null + */ + private function getNotificationMessage(OrderPaymentInterface $payment): ?string + { + $extensionAttributes = $payment->getExtensionAttributes(); + if ($extensionAttributes && $extensionAttributes->getNotificationMessage()) { + return $extensionAttributes->getNotificationMessage(); + } + + return null; + } + + /** + * Sets order state and status. * * @param Order $order * @param string $status * @param string $state * @return void + * @deprecated 100.2.0 Replaced by a StatusResolver class call. */ protected function setOrderStateAndStatus(Order $order, $status, $state) { diff --git a/app/code/Magento/Sales/Model/Order/Payment/State/CaptureCommand.php b/app/code/Magento/Sales/Model/Order/Payment/State/CaptureCommand.php index 0e4135a1bf814..f57e1933a7e5a 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/State/CaptureCommand.php +++ b/app/code/Magento/Sales/Model/Order/Payment/State/CaptureCommand.php @@ -11,6 +11,9 @@ use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\StatusResolver; +/** + * Process order state and status after capture operation. + */ class CaptureCommand implements CommandInterface { /** @@ -28,6 +31,8 @@ public function __construct(StatusResolver $statusResolver = null) } /** + * Run command. + * * @param OrderPaymentInterface $payment * @param string|float $amount * @param OrderInterface $order @@ -50,6 +55,8 @@ public function execute(OrderPaymentInterface $payment, $amount, OrderInterface $message .= ' Order is suspended as its capturing amount %1 is suspected to be fraudulent.'; } + $message = $this->getNotificationMessage($payment) ?? $message; + if (!isset($status)) { $status = $this->statusResolver->getOrderStatusByState($order, $state); } @@ -61,12 +68,13 @@ public function execute(OrderPaymentInterface $payment, $amount, OrderInterface } /** - * @deprecated 100.2.0 Replaced by a StatusResolver class call. + * Sets order state and status. * * @param Order $order * @param string $status * @param string $state * @return void + * @deprecated 100.2.0 Replaced by a StatusResolver class call. */ protected function setOrderStateAndStatus(Order $order, $status, $state) { @@ -76,4 +84,20 @@ protected function setOrderStateAndStatus(Order $order, $status, $state) $order->setState($state)->setStatus($status); } + + /** + * Returns payment notification message. + * + * @param OrderPaymentInterface $payment + * @return string|null + */ + private function getNotificationMessage(OrderPaymentInterface $payment): ?string + { + $extensionAttributes = $payment->getExtensionAttributes(); + if ($extensionAttributes && $extensionAttributes->getNotificationMessage()) { + return $extensionAttributes->getNotificationMessage(); + } + + return null; + } } diff --git a/app/code/Magento/Sales/etc/extension_attributes.xml b/app/code/Magento/Sales/etc/extension_attributes.xml index 222f61cdc7324..08e295cb6721c 100644 --- a/app/code/Magento/Sales/etc/extension_attributes.xml +++ b/app/code/Magento/Sales/etc/extension_attributes.xml @@ -13,4 +13,7 @@ <extension_attributes for="Magento\Sales\Api\Data\OrderInterface"> <attribute code="payment_additional_info" type="Magento\Payment\Api\Data\PaymentAdditionalInfoInterface[]" /> </extension_attributes> + <extension_attributes for="Magento\Sales\Api\Data\OrderPaymentInterface"> + <attribute code="notification_message" type="string" /> + </extension_attributes> </config> diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/TransparentTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/TransparentTest.php new file mode 100644 index 0000000000000..20720f491c18d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/TransparentTest.php @@ -0,0 +1,167 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Model\Payflow; + +use Magento\Checkout\Api\PaymentInformationManagementInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Paypal\Model\Config; +use Magento\Paypal\Model\Payflowpro; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartInterface; +use Magento\Sales\Api\Data\TransactionInterface; +use Magento\Sales\Api\OrderManagementInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class TransparentTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var PaymentInformationManagementInterface + */ + private $management; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->management = $this->objectManager->get(PaymentInformationManagementInterface::class); + } + + /** + * Checks a case when order should be placed in "Suspected Fraud" status based on after account verification. + * + * @magentoDataFixture Magento/Checkout/_files/quote_with_shipping_method.php + * @magentoConfigFixture current_store payment/payflowpro/active 1 + * @magentoConfigFixture current_store payment/payflowpro/payment_action Authorization + * @magentoConfigFixture current_store payment/payflowpro/fmf 1 + */ + public function testPlaceOrderSuspectedFraud() + { + $quote = $this->getQuote('test_order_1'); + $this->addFraudPayment($quote); + $payment = $quote->getPayment(); + $pnref = $payment->getAdditionalInformation(Payflowpro::PNREF); + + $orderId = (int)$this->management->savePaymentInformationAndPlaceOrder($quote->getId(), $payment); + self::assertNotEmpty($orderId); + + /** @var OrderRepositoryInterface $orderManagement */ + $orderManagement = $this->objectManager->get(OrderRepositoryInterface::class); + $order = $orderManagement->get($orderId); + + self::assertEquals(Order::STATUS_FRAUD, $order->getStatus()); + self::assertEquals(Order::STATE_PAYMENT_REVIEW, $order->getState()); + + $transactions = $this->getPaymentTransactionList((int) $orderId); + self::assertEquals(1, sizeof($transactions), 'Only one transaction should be present.'); + + /** @var TransactionInterface $transaction */ + $transaction = array_pop($transactions); + self::assertEquals( + $pnref, + $transaction->getTxnId(), + 'Authorization transaction id should be equal to PNREF.' + ); + + self::assertContains( + 'Order is suspended as an account verification transaction is suspected to be fraudulent.', + $this->getOrderComment($orderId) + ); + } + + /** + * Retrieves quote by provided order ID. + * + * @param string $reservedOrderId + * @return CartInterface + */ + private function getQuote(string $reservedOrderId): CartInterface + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); + } + + /** + * Sets payment with fraud to quote. + * + * @return void + */ + private function addFraudPayment(CartInterface $quote) + { + $payment = $quote->getPayment(); + $payment->setMethod(Config::METHOD_PAYFLOWPRO); + $payment->setAdditionalInformation(Payflowpro::PNREF, 'A90A0D1B361D'); + $payment->setAdditionalInformation('result_code', Payflowpro::RESPONSE_CODE_FRAUDSERVICE_FILTER); + $payment->setCcType('VI'); + $payment->setCcLast4('1111'); + $payment->setCcExpMonth('3'); + $payment->setCcExpYear('2025'); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $quoteRepository->save($quote); + } + + /** + * Get list of order transactions. + * + * @param int $orderId + * @return TransactionInterface[] + */ + private function getPaymentTransactionList(int $orderId): array + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('order_id', $orderId) + ->create(); + + /** @var TransactionRepositoryInterface $transactionRepository */ + $transactionRepository = $this->objectManager->get(TransactionRepositoryInterface::class); + return $transactionRepository->getList($searchCriteria) + ->getItems(); + } + + /** + * Returns order comment. + * + * @param int $orderId + * @return string + */ + private function getOrderComment(int $orderId): string + { + /** @var OrderManagementInterface $orderManagement */ + $orderManagement = $this->objectManager->get(OrderManagementInterface::class); + $comments = $orderManagement->getCommentsList($orderId)->getItems(); + $comment = reset($comments); + + return $comment ? $comment->getComment() : ''; + } +} From 851501e9435c95bc361794901a0f135a8974b6e7 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Wed, 18 Mar 2020 12:13:41 +0200 Subject: [PATCH 329/369] MC-31979: Product stock alert - unsubscribe for the product not working because cannot be accessed with GET method --- .../ProductAlert/Block/Email/Stock.php | 2 +- .../Controller/Unsubscribe/Email.php | 56 +++++++++++++++++++ app/code/Magento/ProductAlert/composer.json | 3 +- .../layout/productalert_unsubscribe_email.xml | 14 +++++ .../view/frontend/templates/email/email.phtml | 22 ++++++++ .../view/frontend/web/js/form-submitter.js | 15 +++++ 6 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/ProductAlert/Controller/Unsubscribe/Email.php create mode 100644 app/code/Magento/ProductAlert/view/frontend/layout/productalert_unsubscribe_email.xml create mode 100644 app/code/Magento/ProductAlert/view/frontend/templates/email/email.phtml create mode 100644 app/code/Magento/ProductAlert/view/frontend/web/js/form-submitter.js diff --git a/app/code/Magento/ProductAlert/Block/Email/Stock.php b/app/code/Magento/ProductAlert/Block/Email/Stock.php index d01960b8eb855..41f149eb5874e 100644 --- a/app/code/Magento/ProductAlert/Block/Email/Stock.php +++ b/app/code/Magento/ProductAlert/Block/Email/Stock.php @@ -27,7 +27,7 @@ public function getProductUnsubscribeUrl($productId) { $params = $this->_getUrlParams(); $params['product'] = $productId; - return $this->getUrl('productalert/unsubscribe/stock', $params); + return $this->getUrl('productalert/unsubscribe/email', $params); } /** diff --git a/app/code/Magento/ProductAlert/Controller/Unsubscribe/Email.php b/app/code/Magento/ProductAlert/Controller/Unsubscribe/Email.php new file mode 100644 index 0000000000000..d2f589374c225 --- /dev/null +++ b/app/code/Magento/ProductAlert/Controller/Unsubscribe/Email.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\ProductAlert\Controller\Unsubscribe; + +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\Action; +use Magento\Framework\View\Result\Page; +use Magento\Framework\View\Result\PageFactory; + +/** + * Unsubscribing from 'Back in stock Alert'. + * + * Is used to transform a Get request that triggered in the email into the Post request endpoint + */ +class Email extends Action implements HttpGetActionInterface +{ + /** + * @var PageFactory + */ + private $resultPageFactory; + + /** + * @param Context $context + * @param PageFactory $resultPageFactory + */ + public function __construct( + Context $context, + PageFactory $resultPageFactory + ) { + $this->resultPageFactory = $resultPageFactory; + parent::__construct($context); + } + + /** + * Processes the the request triggered in Unsubscription email related to 'back in stock alert'. + * + * @return Page + */ + public function execute(): Page + { + $productId = (int)$this->getRequest()->getParam('product'); + /** @var Page $resultPage */ + $resultPage = $this->resultPageFactory->create(); + /** @var @va \Magento\Framework\View\Element\AbstractBlock $block */ + $block = $resultPage->getLayout()->getBlock('unsubscription_form'); + $block->setProductId($productId); + return $resultPage; + } +} diff --git a/app/code/Magento/ProductAlert/composer.json b/app/code/Magento/ProductAlert/composer.json index 25fe8edbede7b..5e3e486d4e4a8 100644 --- a/app/code/Magento/ProductAlert/composer.json +++ b/app/code/Magento/ProductAlert/composer.json @@ -10,7 +10,8 @@ "magento/module-backend": "*", "magento/module-catalog": "*", "magento/module-customer": "*", - "magento/module-store": "*" + "magento/module-store": "*", + "magento/module-theme": "*" }, "suggest": { "magento/module-config": "*" diff --git a/app/code/Magento/ProductAlert/view/frontend/layout/productalert_unsubscribe_email.xml b/app/code/Magento/ProductAlert/view/frontend/layout/productalert_unsubscribe_email.xml new file mode 100644 index 0000000000000..8666fb83e01e3 --- /dev/null +++ b/app/code/Magento/ProductAlert/view/frontend/layout/productalert_unsubscribe_email.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <body> + <referenceContainer name="content"> + <block class="Magento\Framework\View\Element\Template" name="unsubscription_form" template="Magento_ProductAlert::email/email.phtml" /> + </referenceContainer> + </body> +</page> diff --git a/app/code/Magento/ProductAlert/view/frontend/templates/email/email.phtml b/app/code/Magento/ProductAlert/view/frontend/templates/email/email.phtml new file mode 100644 index 0000000000000..a99fe71d06dfd --- /dev/null +++ b/app/code/Magento/ProductAlert/view/frontend/templates/email/email.phtml @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $block Magento\Framework\View\Element\Template */ +?> + +<form action="<?= $block->escapeUrl($block->getUrl('productalert/unsubscribe/stock')) ?>" + method="post" + data-form="unsubscription_form"> + <?= /* @noEscape */ $block->getBlockHtml('formkey') ?> + <input type="hidden" id="productId" name="product" value="<?= $block->escapeHtml($block->getProductId()) ?>" /> +</form> +<script type="text/x-magento-init"> + { + "[data-form=unsubscription_form]": { + "Magento_ProductAlert/js/form-submitter": {} + } + } +</script> diff --git a/app/code/Magento/ProductAlert/view/frontend/web/js/form-submitter.js b/app/code/Magento/ProductAlert/view/frontend/web/js/form-submitter.js new file mode 100644 index 0000000000000..7a0d5f663f3f9 --- /dev/null +++ b/app/code/Magento/ProductAlert/view/frontend/web/js/form-submitter.js @@ -0,0 +1,15 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery' +], function ($) { + 'use strict'; + + return function (data, element) { + + $(element).submit(); + }; +}); From 3102341b0c5f113e8785711bb5b35c3700b58658 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Wed, 18 Mar 2020 13:04:20 +0200 Subject: [PATCH 330/369] MC-32306: Errors while trying to update downloadable product after MC-29952 --- .../Import/Product/Type/Downloadable.php | 6 +- .../Import/Product/Type/DownloadableTest.php | 60 ++++++++++++++----- 2 files changed, 48 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php index f148550dd96bb..b408a77e0c95e 100644 --- a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php +++ b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php @@ -332,7 +332,7 @@ public function isRowValid(array $rowData, $rowNum, $isNewProduct = true) { $this->rowNum = $rowNum; $error = false; - if (!$this->downloadableHelper->isRowDownloadableNoValid($rowData)) { + if (!$this->downloadableHelper->isRowDownloadableNoValid($rowData) && $isNewProduct) { $this->_entityModel->addRowError(self::ERROR_OPTIONS_NOT_FOUND, $this->rowNum); $error = true; } @@ -898,8 +898,8 @@ protected function uploadDownloadableFiles($fileName, $type = 'links', $renameFi try { $uploader = $this->uploaderHelper->getUploader($type, $this->parameters); if (!$this->uploaderHelper->isFileExist($fileName)) { - $uploader->move($fileName, $renameFileOff); - $fileName = $uploader['file']; + $res = $uploader->move($fileName, $renameFileOff); + $fileName = $res['file']; } } catch (\Exception $e) { $this->_entityModel->addRowError(self::ERROR_MOVE_FILE, $this->rowNum); diff --git a/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php b/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php index 482bfa4f7c569..5c084f4588e07 100644 --- a/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php +++ b/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php @@ -6,6 +6,7 @@ namespace Magento\DownloadableImportExport\Test\Unit\Model\Import\Product\Type; +use Magento\Downloadable\Model\Url\DomainValidator; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManager; /** @@ -39,6 +40,11 @@ class DownloadableTest extends \Magento\ImportExport\Test\Unit\Model\Import\Abst */ protected $prodAttrColFacMock; + /** + * @var DomainValidator + */ + private $domainValidator; + /** * @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection|\PHPUnit_Framework_MockObject_MockObject */ @@ -498,7 +504,7 @@ public function dataForSave() /** * @dataProvider isRowValidData */ - public function testIsRowValid(array $rowData, $rowNum, $isNewProduct = true) + public function testIsRowValid(array $rowData, $rowNum, $isNewProduct, $isDomainValid, $expectedResult) { $this->connectionMock->expects($this->any())->method('fetchAll')->with( $this->select @@ -514,6 +520,13 @@ public function testIsRowValid(array $rowData, $rowNum, $isNewProduct = true) ], ] ); + + $this->domainValidator = $this->createMock(DomainValidator::class); + $this->domainValidator + ->expects($this->any())->method('isValid') + ->withAnyParameters() + ->willReturn($isDomainValid); + $this->downloadableModelMock = $this->objectManagerHelper->getObject( \Magento\DownloadableImportExport\Model\Import\Product\Type\Downloadable::class, [ @@ -522,11 +535,12 @@ public function testIsRowValid(array $rowData, $rowNum, $isNewProduct = true) 'resource' => $this->resourceMock, 'params' => $this->paramsArray, 'uploaderHelper' => $this->uploaderHelper, - 'downloadableHelper' => $this->downloadableHelper + 'downloadableHelper' => $this->downloadableHelper, + 'domainValidator' => $this->domainValidator ] ); $result = $this->downloadableModelMock->isRowValid($rowData, $rowNum, $isNewProduct); - $this->assertNotNull($result); + $this->assertEquals($expectedResult, $result); } /** @@ -550,6 +564,8 @@ public function isRowValidData() . 'title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', ], 0, + true, + true, true ], [ @@ -564,15 +580,8 @@ public function isRowValidData() . ' title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', ], 1, - true - ], - [ - [ - 'sku' => 'downloadablesku12', - 'product_type' => 'downloadable', - 'name' => 'Downloadable Product 2', - ], - 2, + true, + true, true ], [ @@ -587,6 +596,8 @@ public function isRowValidData() . ' url=media/file2.mp4,sortorder=0', ], 3, + true, + true, true ], [ @@ -594,13 +605,15 @@ public function isRowValidData() 'sku' => 'downloadablesku12', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 2', - 'downloadable_samples' => 'file=media/file.mp4,sortorder=1|group_title=Group Title, ' - . 'url=media/file2.mp4,sortorder=0', + 'downloadable_samples' => 'title=Title 1, file=media/file.mp4,sortorder=1|title=Title 2,' . + ' group_title=Group Title, url=media/file2.mp4,sortorder=0', 'downloadable_links' => 'title=Title 1, price=10, downloads=unlimited, file=media/file.mp4,' . 'sortorder=1|group_title=Group Title, title=Title 2, price=10, downloads=unlimited,' . ' url=media/file2.mp4,sortorder=0', ], 4, + true, + true, true ], [ //empty group title samples @@ -615,6 +628,8 @@ public function isRowValidData() . ' title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', ], 5, + true, + true, true ], [ //empty group title links @@ -629,6 +644,19 @@ public function isRowValidData() . 'downloads=unlimited, url=media/file2.mp4,sortorder=0', ], 6, + true, + true, + true + ], + [ + [ + 'sku' => 'downloadablesku12', + 'product_type' => 'downloadable', + 'name' => 'Downloadable Product 2', + ], + 2, + false, + true, true ], [ @@ -640,7 +668,9 @@ public function isRowValidData() 'downloadable_links' => '', ], 7, - true + true, + true, + false ], ]; } From 6ad372ae3768d10a77810bce9bf2eeb8a5ea01d2 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Wed, 18 Mar 2020 13:40:49 +0200 Subject: [PATCH 331/369] magento/magento2#25540: Static tests fix. --- .../BundleImportExport/Model/Import/Product/Type/Bundle.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php index 33a7d2efaa273..dcc6e52460793 100644 --- a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php +++ b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php @@ -20,9 +20,8 @@ use Magento\Store\Model\StoreManagerInterface; /** - * Class Bundle + * Import entity Bundle product type. * - * @package Magento\BundleImportExport\Model\Import\Product\Type * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType @@ -723,6 +722,8 @@ protected function _initAttributes() } } } + + return $this; } /** From 1e1ebd3a9ee69038841f41ebe8771b6a1fa700d6 Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Thu, 12 Mar 2020 13:17:37 +0200 Subject: [PATCH 332/369] Fix: ORDER BY has two similar conditions in the SQL query --- app/code/Magento/Catalog/Block/Product/ListProduct.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php index 144cb682e2d22..f1adc1d924836 100644 --- a/app/code/Magento/Catalog/Block/Product/ListProduct.php +++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php @@ -475,8 +475,6 @@ private function initializeProductCollection() $layer->setCurrentCategory($origCategory); } - $this->addToolbarBlock($collection); - $this->_eventManager->dispatch( 'catalog_block_product_list_collection', ['collection' => $collection] From a070cf368ed4b19c5e63b4453d9af2d72451901e Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Wed, 18 Mar 2020 15:00:43 +0200 Subject: [PATCH 333/369] magento/magento2#25540: MFTF test added. --- .../Test/UpdateBundleProductViaImportTest.xml | 70 +++++++++++++++++++ .../_data/catalog_product_import_bundle.csv | 3 + 2 files changed, 73 insertions(+) create mode 100644 app/code/Magento/BundleImportExport/Test/Mftf/Test/UpdateBundleProductViaImportTest.xml create mode 100644 dev/tests/acceptance/tests/_data/catalog_product_import_bundle.csv diff --git a/app/code/Magento/BundleImportExport/Test/Mftf/Test/UpdateBundleProductViaImportTest.xml b/app/code/Magento/BundleImportExport/Test/Mftf/Test/UpdateBundleProductViaImportTest.xml new file mode 100644 index 0000000000000..45b4c4f5ededd --- /dev/null +++ b/app/code/Magento/BundleImportExport/Test/Mftf/Test/UpdateBundleProductViaImportTest.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="UpdateBundleProductViaImportTest"> + <annotations> + <features value="Import/Export"/> + <title value="Update Bundle product via import"/> + <description + value="Check that Bundle products are displaying on the storefront after updating product via importing CSV"/> + <severity value="MAJOR"/> + <group value="importExport"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Delete products created via import --> + <actionGroup ref="DeleteProductBySkuActionGroup" stepKey="deleteBundleProduct"> + <argument name="sku" value="Bundle"/> + </actionGroup> + <actionGroup ref="DeleteProductBySkuActionGroup" stepKey="deleteSimpleProduct"> + <argument name="sku" value="Simple"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <!-- Create Bundle product via import --> + <actionGroup ref="AdminImportProductsActionGroup" stepKey="adminImportProductsCreate"> + <argument name="behavior" value="Add/Update"/> + <argument name="importFile" value="catalog_product_import_bundle.csv"/> + <argument name="importNoticeMessage" value="Created: 2, Updated: 0, Deleted: 0"/> + </actionGroup> + <magentoCLI command="cache:flush" arguments="full_page" stepKey="flushCacheAfterCreate"/> + <magentoCLI command="indexer:reindex" stepKey="indexerReindexAfterCreate"/> + + <!-- Check Bundle product is visible on the storefront--> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="openCategoryPageAfterCreation"> + <argument name="categoryName" value="New"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductIsPresentOnCategoryPageActionGroup" + stepKey="assertBundleProductInStockAfterCreation"> + <argument name="productName" value="Bundle"/> + </actionGroup> + + <!-- Update Bundle product via import --> + <actionGroup ref="AdminImportProductsActionGroup" stepKey="adminImportProductsUpdate"> + <argument name="behavior" value="Add/Update"/> + <argument name="importFile" value="catalog_product_import_bundle.csv"/> + <argument name="importNoticeMessage" value="Created: 0, Updated: 2, Deleted: 0"/> + </actionGroup> + <magentoCLI command="cache:flush" arguments="full_page" stepKey="flushCacheAfterUpdate"/> + <magentoCLI command="indexer:reindex" stepKey="indexerReindexAfterUpdate"/> + + <!-- Check Bundle product is still visible on the storefront--> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="openCategoryPageAfterUpdate"> + <argument name="categoryName" value="New"/> + </actionGroup> + <actionGroup ref="AssertStorefrontProductIsPresentOnCategoryPageActionGroup" + stepKey="assertBundleProductInStockAfterUpdate"> + <argument name="productName" value="Bundle"/> + </actionGroup> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/_data/catalog_product_import_bundle.csv b/dev/tests/acceptance/tests/_data/catalog_product_import_bundle.csv new file mode 100644 index 0000000000000..6804675940a02 --- /dev/null +++ b/dev/tests/acceptance/tests/_data/catalog_product_import_bundle.csv @@ -0,0 +1,3 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,associated_skus,downloadable_links,downloadable_samples,configurable_variations,configurable_variation_labels +Simple,,Default,simple,"Default Category/New",base,Simple,,,1.000000,1,"Taxable Goods","Catalog, Search",100.000000,,,,simple,Simple,Simple,"Simple ",,,,,,,,,"3/18/20, 6:56 AM","3/18/20, 6:56 AM",,,"Block after Info Column",,,,"Use config",,,,,,,"Use config",,,1000.0000,0.0000,1,0,0,1,1.0000,1,10000.0000,1,1,1.0000,1,1,1,1,1.0000,1,0,0,0,,,,,,,,,,,,,,,,,,,,, +Bundle,,Default,bundle,"Default Category/New",base,Bundle,,,,1,"Taxable Goods","Catalog, Search",,,,,bundle,Bundle,Bundle,"Bundle ",,,,,,,,,"3/18/20, 6:57 AM","3/18/20, 6:57 AM",,,"Block after Info Column",,,,"Use config",,,,,,,"Use config",,,0.0000,0.0000,1,0,0,1,1.0000,1,10000.0000,1,1,1.0000,1,1,1,1,1.0000,1,0,0,0,,,,,,,,,,,dynamic,dynamic,"Price range",dynamic,"name=Test Option,type=select,required=1,sku=Simple,price=0.0000,default=1,default_qty=1.0000,price_type=fixed,can_change_qty=0",together,,,,, From 18c8f31b6467e410bb94ab6b3fa2733dd024024e Mon Sep 17 00:00:00 2001 From: Dmitry Tsymbal <d.tsymbal@atwix.com> Date: Wed, 18 Mar 2020 15:45:16 +0200 Subject: [PATCH 334/369] Customer Subscribes To Newsletter Subscription --- ...tStorefrontCustomerMessagesActionGroup.xml | 19 ++++++++++ .../StorefrontCustomerLoginActionGroup.xml | 21 +++++++++++ ...merNavigateToNewsletterPageActionGroup.xml | 15 ++++++++ ...erUpdateGeneralSubscriptionActionGroup.xml | 15 ++++++++ ...StorefrontCustomerNewsletterManagePage.xml | 14 ++++++++ .../StorefrontCustomerLoginFormSection.xml | 16 +++++++++ .../StorefrontCustomerNewsletterSection.xml | 15 ++++++++ ...frontCustomerSubscribeToNewsletterTest.xml | 35 +++++++++++++++++++ 8 files changed, 150 insertions(+) create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerMessagesActionGroup.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLoginActionGroup.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerNavigateToNewsletterPageActionGroup.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerUpdateGeneralSubscriptionActionGroup.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerNewsletterManagePage.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginFormSection.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerNewsletterSection.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerSubscribeToNewsletterTest.xml diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerMessagesActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerMessagesActionGroup.xml new file mode 100644 index 0000000000000..50e052207bd9e --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerMessagesActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontCustomerMessagesActionGroup"> + <arguments> + <argument name="message" type="string"/> + </arguments> + + <waitForElementVisible selector="{{StorefrontCustomerMessagesSection.successMessage}}" stepKey="waitForElement"/> + <see userInput="{{message}}" selector="{{StorefrontCustomerMessagesSection.successMessage}}" stepKey="seeMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLoginActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLoginActionGroup.xml new file mode 100644 index 0000000000000..4105034e33988 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLoginActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerLoginActionGroup"> + <arguments> + <argument name="customer" type="entity"/> + </arguments> + + <click selector="{{StorefrontPanelHeaderSection.customerLoginLink}}" stepKey="clickSignInLnk"/> + <fillField selector="{{StorefrontCustomerLoginFormSection.emailField}}" userInput="{{customer.email}}" stepKey="fillEmailField"/> + <fillField selector="{{StorefrontCustomerLoginFormSection.passwordField}}" userInput="{{customer.password}}" stepKey="fillPasswordField"/> + <click selector="{{StorefrontCustomerLoginFormSection.signInAccountButton}}" stepKey="clickSignInButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerNavigateToNewsletterPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerNavigateToNewsletterPageActionGroup.xml new file mode 100644 index 0000000000000..559dee27b551d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerNavigateToNewsletterPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerNavigateToNewsletterPageActionGroup"> + <amOnPage url="{{StorefrontCustomerNewsletterManagePage.url}}" stepKey="goToNewsletterPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerUpdateGeneralSubscriptionActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerUpdateGeneralSubscriptionActionGroup.xml new file mode 100644 index 0000000000000..16f8b5d17d7e1 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerUpdateGeneralSubscriptionActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCustomerUpdateGeneralSubscriptionActionGroup"> + <click selector="{{StorefrontCustomerNewsletterSection.newsletterCheckbox}}" stepKey="checkNewsLetterSubscriptionCheckbox"/> + <click selector="{{StorefrontCustomerNewsletterSection.submit}}" stepKey="clickSubmitButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerNewsletterManagePage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerNewsletterManagePage.xml new file mode 100644 index 0000000000000..62fa49f7b4b38 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerNewsletterManagePage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomerNewsletterManagePage" url="/newsletter/manage/" area="storefront" module="Magento_Customer"> + <section name="StorefrontCustomerNewsletterSection"/> + </page> +</pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginFormSection.xml new file mode 100644 index 0000000000000..55f2835d584af --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginFormSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerLoginFormSection"> + <element name="emailField" type="input" selector=".fieldset.login #email.input-text"/> + <element name="passwordField" type="input" selector=".field.password.required #pass.input-text"/> + <element name="signInAccountButton" type="button" selector=".actions-toolbar #send2.action.login.primary" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerNewsletterSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerNewsletterSection.xml new file mode 100644 index 0000000000000..0275603b26227 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerNewsletterSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerNewsletterSection"> + <element name="newsletterCheckbox" type="checkbox" selector="#subscription.checkbox"/> + <element name="submit" type="button" selector=".action.save.primary"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerSubscribeToNewsletterTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerSubscribeToNewsletterTest.xml new file mode 100644 index 0000000000000..be937ebe4d970 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerSubscribeToNewsletterTest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerSubscribeToNewsletterTest"> + <annotations> + <features value="Newsletter Subscription"/> + <stories value="Subscribe To Newsletter Subscription on StoreFront"/> + <title value="StoreFront Customer Newsletter Subscription"/> + <description value="Customer can be subscribed to Newsletter Subscription on StoreFront"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCreatedCustomer"/> + </after> + + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openHomePage"/> + <actionGroup ref="StorefrontCustomerLoginActionGroup" stepKey="loginAsCustomer"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontCustomerNavigateToNewsletterPageActionGroup" stepKey="navigateToNewsletterPage"/> + <actionGroup ref="StorefrontCustomerUpdateGeneralSubscriptionActionGroup" stepKey="subscribeToNewsletter"/> + <actionGroup ref="AssertStorefrontCustomerMessagesActionGroup" stepKey="assertMessage"> + <argument name="message" value="We have saved your subscription."/> + </actionGroup> + </test> +</tests> From df35ddac0dacf30be6547a7cbad113c3ed6ed3fc Mon Sep 17 00:00:00 2001 From: Burlacu Vasilii <v.burlacu@atwix.com> Date: Wed, 18 Mar 2020 16:04:54 +0200 Subject: [PATCH 335/369] Fix failed unit test --- .../Catalog/Test/Unit/Block/Product/ListProductTest.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php index fe07f69e8046f..4653934311938 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php @@ -187,10 +187,6 @@ public function testGetIdentities() ->method('getProductCollection') ->will($this->returnValue($this->prodCollectionMock)); - $this->layoutMock->expects($this->once()) - ->method('getBlock') - ->will($this->returnValue($this->toolbarMock)); - $this->assertEquals( [$categoryTag, $productTag], $this->block->getIdentities() From 97bd66e984895f12c8f17b7f401ebf352a28805c Mon Sep 17 00:00:00 2001 From: Andrii Beziazychnyi <a.beziazychnyi@atwix.com> Date: Tue, 17 Mar 2020 10:22:44 +0200 Subject: [PATCH 336/369] magento/magento2: fixes for the cache configuration schema --- .../Magento/Framework/Cache/ConfigTest.php | 89 ++++++++++++++ .../_files/invalidCacheConfigXmlArray.php | 52 ++++++++ .../Cache/_files/valid_cache_config.xml | 17 +++ .../Magento/Framework/App/Cache/TypeList.php | 9 +- .../App/Test/Unit/Cache/TypeListTest.php | 116 ++++++++++-------- .../Magento/Framework/Cache/Config/Reader.php | 31 +++-- .../Framework/Cache/Config/SchemaLocator.php | 12 +- .../Cache/Test/Unit/Config/ConverterTest.php | 30 ----- .../Test/Unit/Config/_files/cache_config.php | 23 ---- .../Test/Unit/Config/_files/cache_config.xml | 17 --- .../Magento/Framework/Cache/etc/cache.xsd | 40 +++--- 11 files changed, 286 insertions(+), 150 deletions(-) create mode 100644 dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/ConfigTest.php create mode 100644 dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/_files/invalidCacheConfigXmlArray.php create mode 100644 dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/_files/valid_cache_config.xml delete mode 100644 lib/internal/Magento/Framework/Cache/Test/Unit/Config/ConverterTest.php delete mode 100644 lib/internal/Magento/Framework/Cache/Test/Unit/Config/_files/cache_config.php delete mode 100644 lib/internal/Magento/Framework/Cache/Test/Unit/Config/_files/cache_config.xml diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/ConfigTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/ConfigTest.php new file mode 100644 index 0000000000000..2d4a1dc0c2ce3 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/ConfigTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Test\Integrity\Magento\Framework\Cache; + +use Magento\Framework\Config\Dom\UrnResolver; +use Magento\Framework\TestFramework\Unit\Utility\XsdValidator; +use PHPUnit\Framework\TestCase; + +/** + * Unit test of the cache configuration + */ +class ConfigTest extends TestCase +{ + /** + * Path to xsd schema file + * @var string + */ + private $xsdSchema; + + /** + * @var UrnResolver + */ + private $urnResolver; + + /** + * @var XsdValidator + */ + private $xsdValidator; + + /** + * Setup environment for test + */ + protected function setUp(): void + { + if (!function_exists('libxml_set_external_entity_loader')) { + $this->markTestSkipped('Skipped on HHVM. Will be fixed in MAGETWO-45033'); + } + $this->urnResolver = new UrnResolver(); + $this->xsdSchema = $this->urnResolver->getRealPath( + 'urn:magento:framework:Cache/etc/cache.xsd' + ); + $this->xsdValidator = new XsdValidator(); + } + + /** + * Tests invalid configurations + * + * @param string $xmlString + * @param array $expectedError + * @dataProvider schemaCorrectlyIdentifiesInvalidXmlDataProvider + */ + public function testSchemaCorrectlyIdentifiesInvalidXml( + string $xmlString, + array $expectedError + ): void { + $actualError = $this->xsdValidator->validate( + $this->xsdSchema, + $xmlString + ); + $this->assertEquals($expectedError, $actualError); + } + + /** + * Tests valid configurations + */ + public function testSchemaCorrectlyIdentifiesValidXml(): void + { + $xmlString = file_get_contents(__DIR__ . '/_files/valid_cache_config.xml'); + $actualResult = $this->xsdValidator->validate( + $this->xsdSchema, + $xmlString + ); + + $this->assertEmpty($actualResult); + } + + /** + * Data provider with invalid xml array according to cache.xsd + */ + public function schemaCorrectlyIdentifiesInvalidXmlDataProvider(): array + { + return include __DIR__ . '/_files/invalidCacheConfigXmlArray.php'; + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/_files/invalidCacheConfigXmlArray.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/_files/invalidCacheConfigXmlArray.php new file mode 100644 index 0000000000000..8d2d631334a9b --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/_files/invalidCacheConfigXmlArray.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +return [ + 'without_type_handle' => [ + '<?xml version="1.0"?><config></config>', + ["Element 'config': Missing child element(s). Expected is ( type ).\nLine: 1\n"], + ], + 'cache_config_with_notallowed_attribute' => [ + '<?xml version="1.0"?><config>' . + '<type name="test" translate="label,description" instance="Class\Name" notallowed="some value">' . + '<label>Test</label><description>Test</description></type></config>', + ["Element 'type', attribute 'notallowed': The attribute 'notallowed' is not allowed.\nLine: 1\n"], + ], + 'cache_config_without_name_attribute' => [ + '<?xml version="1.0"?><config><type translate="label,description" instance="Class\Name">' . + '<label>Test</label><description>Test</description></type></config>', + ["Element 'type': The attribute 'name' is required but missing.\nLine: 1\n"], + ], + 'cache_config_without_instance_attribute' => [ + '<?xml version="1.0"?><config><type name="test" translate="label,description">' . + '<label>Test</label><description>Test</description></type></config>', + ["Element 'type': The attribute 'instance' is required but missing.\nLine: 1\n"], + ], + 'cache_config_without_label_element' => [ + '<?xml version="1.0"?><config><type name="test" translate="label,description" instance="Class\Name">' . + '<description>Test</description></type></config>', + ["Element 'type': Missing child element(s). Expected is ( label ).\nLine: 1\n"], + ], + 'cache_config_without_description_element' => [ + '<?xml version="1.0"?><config><type name="test" translate="label,description" instance="Class\Name">' . + '<label>Test</label></type></config>', + ["Element 'type': Missing child element(s). Expected is ( description ).\nLine: 1\n"], + ], + 'cache_config_without_child_elements' => [ + '<?xml version="1.0"?><config><type name="test" translate="label,description" instance="Class\Name">' . + '</type></config>', + ["Element 'type': Missing child element(s). Expected is one of ( label, description ).\nLine: 1\n"], + ], + 'cache_config_cache_name_not_unique' => [ + '<?xml version="1.0"?><config><type name="test" translate="label,description" instance="Class\Name1">' . + '<label>Test1</label><description>Test1</description></type>' . + '<type name="test" translate="label,description" instance="Class\Name2">' . + '<label>Test2</label><description>Test2</description></type></config>', + [ + "Element 'type': Duplicate key-sequence ['test'] in unique identity-constraint" + . " 'uniqueCacheName'.\nLine: 1\n" + ], + ], +]; diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/_files/valid_cache_config.xml b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/_files/valid_cache_config.xml new file mode 100644 index 0000000000000..ef45c083daf0d --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Cache/_files/valid_cache_config.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Cache/etc/cache.xsd"> + <type name="type_name_1" translate="label,description" instance="Instance1\Class\Name"> + <label>Type1</label> + <description>Type1</description> + </type> + <type name="type_name_2" translate="label,description" instance="Instance2\Class\Name"> + <label>Type2</label> + <description>Type2</description> + </type> +</config> diff --git a/lib/internal/Magento/Framework/App/Cache/TypeList.php b/lib/internal/Magento/Framework/App/Cache/TypeList.php index b695ee3a37fa8..c0790c4d40ad4 100644 --- a/lib/internal/Magento/Framework/App/Cache/TypeList.php +++ b/lib/internal/Magento/Framework/App/Cache/TypeList.php @@ -8,6 +8,9 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Serialize\SerializerInterface; +/** + * Application cache type list + */ class TypeList implements TypeListInterface { const INVALIDATED_TYPES = 'core_cache_invalidate'; @@ -68,9 +71,7 @@ public function __construct( protected function _getTypeInstance($type) { $config = $this->_config->getType($type); - if (!isset($config['instance'])) { - return null; - } + return $this->_factory->get($config['instance']); } @@ -132,7 +133,7 @@ public function getTypes() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTypeLabels() { diff --git a/lib/internal/Magento/Framework/App/Test/Unit/Cache/TypeListTest.php b/lib/internal/Magento/Framework/App/Test/Unit/Cache/TypeListTest.php index 8d9b297d7dded..02c9a872fe97f 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/Cache/TypeListTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/Cache/TypeListTest.php @@ -6,21 +6,30 @@ namespace Magento\Framework\App\Test\Unit\Cache; -use \Magento\Framework\App\Cache\TypeList; +use Magento\Framework\App\Cache\InstanceFactory; +use Magento\Framework\App\Cache\StateInterface; +use Magento\Framework\App\Cache\TypeList; +use Magento\Framework\App\CacheInterface; +use Magento\Framework\Cache\Frontend\Decorator\TagScope; +use Magento\Framework\Cache\ConfigInterface; +use Magento\Framework\DataObject; use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; /** * Test class for \Magento\Framework\App\Cache\TypeList */ -class TypeListTest extends \PHPUnit\Framework\TestCase +class TypeListTest extends TestCase { /** - * @var \Magento\Framework\App\Cache\TypeList + * @var TypeList */ protected $_typeList; /** - * @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CacheInterface|MockObject */ protected $_cache; @@ -30,7 +39,7 @@ class TypeListTest extends \PHPUnit\Framework\TestCase protected $_typesArray; /** - * @var \Magento\Framework\Cache\ConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ConfigInterface|MockObject */ protected $_config; @@ -47,10 +56,10 @@ class TypeListTest extends \PHPUnit\Framework\TestCase /** * Expected cache type */ - const CACHE_TYPE = \Magento\Framework\Cache\FrontendInterface::class; + const CACHE_TYPE = TagScope::class; /** - * @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var SerializerInterface|MockObject */ private $serializerMock; @@ -58,33 +67,44 @@ protected function setUp() { $this->_typesArray = [ self::TYPE_KEY => [ + 'name' => self::TYPE_KEY, + 'instance' => self::CACHE_TYPE, 'label' => 'Type Label', 'description' => 'Type Description', ], ]; - $this->_config = - $this->createPartialMock(\Magento\Framework\Cache\ConfigInterface::class, ['getTypes', 'getType']); - $this->_config->expects($this->any())->method('getTypes')->will($this->returnValue($this->_typesArray)); + $this->_config = $this->createPartialMock( + ConfigInterface::class, + ['getTypes', 'getType'] + ); + $this->_config->expects($this->any())->method('getTypes') + ->will($this->returnValue($this->_typesArray)); + $this->_config->expects($this->any())->method('getType') + ->with(self::TYPE_KEY) + ->will($this->returnValue($this->_typesArray[self::TYPE_KEY])); $cacheState = $this->createPartialMock( - \Magento\Framework\App\Cache\StateInterface::class, + StateInterface::class, ['isEnabled', 'setEnabled', 'persist'] ); - $cacheState->expects($this->any())->method('isEnabled')->will($this->returnValue(self::IS_CACHE_ENABLED)); - $cacheBlockMock = $this->createMock(self::CACHE_TYPE); - $factory = $this->createPartialMock(\Magento\Framework\App\Cache\InstanceFactory::class, ['get']); - $factory->expects($this->any())->method('get')->with(self::CACHE_TYPE)->will( - $this->returnValue($cacheBlockMock) - ); + $cacheState->expects($this->any())->method('isEnabled') + ->will($this->returnValue(self::IS_CACHE_ENABLED)); + $cacheTypeMock = $this->createMock(self::CACHE_TYPE); + $cacheTypeMock->expects($this->any())->method('getTag') + ->will($this->returnValue('TEST')); + $factory = $this->createPartialMock(InstanceFactory::class, ['get']); + $factory->expects($this->any())->method('get') + ->with(self::CACHE_TYPE) + ->will($this->returnValue($cacheTypeMock)); $this->_cache = $this->createPartialMock( - \Magento\Framework\App\CacheInterface::class, + CacheInterface::class, ['load', 'getFrontend', 'save', 'remove', 'clean'] ); $this->serializerMock = $this->createMock(SerializerInterface::class); - $objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $objectHelper = new ObjectManager($this); $this->_typeList = $objectHelper->getObject( - \Magento\Framework\App\Cache\TypeList::class, + TypeList::class, [ 'config' => $this->_config, 'cacheState' => $cacheState, @@ -114,9 +134,9 @@ public function testGetTypeLabels() public function testGetInvalidated() { $expectation = [self::TYPE_KEY => $this->_getPreparedType()]; - $this->_cache->expects($this->once())->method('load')->with(TypeList::INVALIDATED_TYPES)->will( - $this->returnValue('serializedData') - ); + $this->_cache->expects($this->once())->method('load') + ->with(TypeList::INVALIDATED_TYPES) + ->will($this->returnValue('serializedData')); $this->serializerMock->expects($this->once()) ->method('unserialize') ->with('serializedData') @@ -127,9 +147,9 @@ public function testGetInvalidated() public function testInvalidate() { // there are no invalidated types - $this->_cache->expects($this->once())->method('load')->with(TypeList::INVALIDATED_TYPES)->will( - $this->returnValue([]) - ); + $this->_cache->expects($this->once())->method('load') + ->with(TypeList::INVALIDATED_TYPES) + ->will($this->returnValue([])); $expectedInvalidated = [ self::TYPE_KEY => 1, ]; @@ -137,18 +157,16 @@ public function testInvalidate() ->method('serialize') ->with($expectedInvalidated) ->willReturn('serializedData'); - $this->_cache->expects($this->once())->method('save')->with( - 'serializedData', - TypeList::INVALIDATED_TYPES - ); + $this->_cache->expects($this->once())->method('save') + ->with('serializedData', TypeList::INVALIDATED_TYPES); $this->_typeList->invalidate(self::TYPE_KEY); } public function testInvalidateList() { - $this->_cache->expects($this->once())->method('load')->with(TypeList::INVALIDATED_TYPES)->will( - $this->returnValue([]) - ); + $this->_cache->expects($this->once())->method('load') + ->with(TypeList::INVALIDATED_TYPES) + ->will($this->returnValue([])); $expectedInvalidated = [ self::TYPE_KEY => 1, ]; @@ -156,10 +174,8 @@ public function testInvalidateList() ->method('serialize') ->with($expectedInvalidated) ->willReturn('serializedData'); - $this->_cache->expects($this->once())->method('save')->with( - 'serializedData', - TypeList::INVALIDATED_TYPES - ); + $this->_cache->expects($this->once())->method('save') + ->with('serializedData', TypeList::INVALIDATED_TYPES); $this->_typeList->invalidate([self::TYPE_KEY]); } @@ -169,38 +185,36 @@ public function testCleanType() ->method('unserialize') ->with('serializedData') ->willReturn($this->_typesArray); - $this->_cache->expects($this->once())->method('load')->with(TypeList::INVALIDATED_TYPES)->will( - $this->returnValue('serializedData') - ); - $this->_config->expects($this->once())->method('getType')->with(self::TYPE_KEY)->will( - $this->returnValue(['instance' => self::CACHE_TYPE]) - ); + $this->_cache->expects($this->once())->method('load') + ->with(TypeList::INVALIDATED_TYPES) + ->will($this->returnValue('serializedData')); + $this->_config->expects($this->once())->method('getType') + ->with(self::TYPE_KEY) + ->will($this->returnValue(['instance' => self::CACHE_TYPE])); unset($this->_typesArray[self::TYPE_KEY]); $this->serializerMock->expects($this->once()) ->method('serialize') ->with($this->_typesArray) ->willReturn('serializedData'); - $this->_cache->expects($this->once())->method('save')->with( - 'serializedData', - TypeList::INVALIDATED_TYPES - ); + $this->_cache->expects($this->once())->method('save') + ->with('serializedData', TypeList::INVALIDATED_TYPES); $this->_typeList->cleanType(self::TYPE_KEY); } /** * Returns prepared type * - * @return \Magento\Framework\DataObject + * @return DataObject */ private function _getPreparedType() { - return new \Magento\Framework\DataObject( + return new DataObject( [ 'id' => self::TYPE_KEY, 'cache_type' => $this->_typesArray[self::TYPE_KEY]['label'], 'description' => $this->_typesArray[self::TYPE_KEY]['description'], - 'tags' => '', - 'status' => self::IS_CACHE_ENABLED, + 'tags' => 'TEST', + 'status' => (int)self::IS_CACHE_ENABLED, ] ); } diff --git a/lib/internal/Magento/Framework/Cache/Config/Reader.php b/lib/internal/Magento/Framework/Cache/Config/Reader.php index 445e91240e7e5..942a3931e0173 100644 --- a/lib/internal/Magento/Framework/Cache/Config/Reader.php +++ b/lib/internal/Magento/Framework/Cache/Config/Reader.php @@ -3,9 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\Cache\Config; -class Reader extends \Magento\Framework\Config\Reader\Filesystem +use Magento\Framework\App\Area; +use Magento\Framework\Config\Dom; +use Magento\Framework\Config\FileResolverInterface; +use Magento\Framework\Config\Reader\Filesystem; +use Magento\Framework\Config\ValidationStateInterface; + +/** + * Cache configuration reader + */ +class Reader extends Filesystem { /** * List of id attributes for merge @@ -15,24 +25,27 @@ class Reader extends \Magento\Framework\Config\Reader\Filesystem protected $_idAttributes = ['/config/type' => 'name']; /** - * @param \Magento\Framework\Config\FileResolverInterface $fileResolver + * Initialize dependencies. + * + * @param FileResolverInterface $fileResolver * @param Converter $converter * @param SchemaLocator $schemaLocator - * @param \Magento\Framework\Config\ValidationStateInterface $validationState + * @param ValidationStateInterface $validationState * @param string $fileName * @param array $idAttributes * @param string $domDocumentClass * @param string $defaultScope + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ public function __construct( - \Magento\Framework\Config\FileResolverInterface $fileResolver, - \Magento\Framework\Cache\Config\Converter $converter, - \Magento\Framework\Cache\Config\SchemaLocator $schemaLocator, - \Magento\Framework\Config\ValidationStateInterface $validationState, + FileResolverInterface $fileResolver, + Converter $converter, + SchemaLocator $schemaLocator, + ValidationStateInterface $validationState, $fileName = 'cache.xml', $idAttributes = [], - $domDocumentClass = \Magento\Framework\Config\Dom::class, - $defaultScope = 'global' + $domDocumentClass = Dom::class, + $defaultScope = Area::AREA_GLOBAL ) { parent::__construct( $fileResolver, diff --git a/lib/internal/Magento/Framework/Cache/Config/SchemaLocator.php b/lib/internal/Magento/Framework/Cache/Config/SchemaLocator.php index 5471dbcfb6c62..2d3be1b1a4067 100644 --- a/lib/internal/Magento/Framework/Cache/Config/SchemaLocator.php +++ b/lib/internal/Magento/Framework/Cache/Config/SchemaLocator.php @@ -7,6 +7,9 @@ */ namespace Magento\Framework\Cache\Config; +/** + * Cache configuration schema locator + */ class SchemaLocator implements \Magento\Framework\Config\SchemaLocatorInterface { /** @@ -15,6 +18,9 @@ class SchemaLocator implements \Magento\Framework\Config\SchemaLocatorInterface protected $urnResolver; /** + * Initialize dependencies. + * + * @param \Magento\Framework\Config\Dom\UrnResolver $urnResolver */ public function __construct(\Magento\Framework\Config\Dom\UrnResolver $urnResolver) { @@ -25,6 +31,7 @@ public function __construct(\Magento\Framework\Config\Dom\UrnResolver $urnResolv * Get path to merged config schema * * @return string|null + * @throws \Magento\Framework\Exception\NotFoundException */ public function getSchema() { @@ -34,10 +41,11 @@ public function getSchema() /** * Get path to pre file validation schema * - * @return null + * @return string|null + * @throws \Magento\Framework\Exception\NotFoundException */ public function getPerFileSchema() { - return null; + return $this->urnResolver->getRealPath('urn:magento:framework:Cache/etc/cache.xsd'); } } diff --git a/lib/internal/Magento/Framework/Cache/Test/Unit/Config/ConverterTest.php b/lib/internal/Magento/Framework/Cache/Test/Unit/Config/ConverterTest.php deleted file mode 100644 index 7f86e162311c8..0000000000000 --- a/lib/internal/Magento/Framework/Cache/Test/Unit/Config/ConverterTest.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Cache\Test\Unit\Config; - -class ConverterTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Cache\Config\Converter - */ - protected $_model; - - protected function setUp() - { - $this->_model = new \Magento\Framework\Cache\Config\Converter(); - } - - public function testConvert() - { - $dom = new \DOMDocument(); - $xmlFile = __DIR__ . '/_files/cache_config.xml'; - $dom->loadXML(file_get_contents($xmlFile)); - - $convertedFile = __DIR__ . '/_files/cache_config.php'; - $expectedResult = include $convertedFile; - $this->assertEquals($expectedResult, $this->_model->convert($dom)); - } -} diff --git a/lib/internal/Magento/Framework/Cache/Test/Unit/Config/_files/cache_config.php b/lib/internal/Magento/Framework/Cache/Test/Unit/Config/_files/cache_config.php deleted file mode 100644 index 0a45e50bbe198..0000000000000 --- a/lib/internal/Magento/Framework/Cache/Test/Unit/Config/_files/cache_config.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -return [ - 'types' => [ - 'config' => [ - 'name' => 'config', - 'translate' => 'label,description', - 'instance' => \Magento\Framework\App\Cache\Type\Config::class, - 'label' => 'Configuration', - 'description' => 'Cache Description', - ], - 'layout' => [ - 'name' => 'layout', - 'translate' => 'label,description', - 'instance' => \Magento\Framework\App\Cache\Type\Layout::class, - 'label' => 'Layouts', - 'description' => 'Layout building instructions', - ], - ] -]; diff --git a/lib/internal/Magento/Framework/Cache/Test/Unit/Config/_files/cache_config.xml b/lib/internal/Magento/Framework/Cache/Test/Unit/Config/_files/cache_config.xml deleted file mode 100644 index 315ddd9cec67c..0000000000000 --- a/lib/internal/Magento/Framework/Cache/Test/Unit/Config/_files/cache_config.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Cache/etc/cache.xsd"> - <type name="config" translate="label,description" instance="Magento\Framework\App\Cache\Type\Config"> - <label>Configuration</label> - <description>Cache Description</description> - </type> - <type name="layout" translate="label,description" instance="Magento\Framework\App\Cache\Type\Layout"> - <label>Layouts</label> - <description>Layout building instructions</description> - </type> -</config> diff --git a/lib/internal/Magento/Framework/Cache/etc/cache.xsd b/lib/internal/Magento/Framework/Cache/etc/cache.xsd index 74b831bb6ac03..d997e295140f5 100644 --- a/lib/internal/Magento/Framework/Cache/etc/cache.xsd +++ b/lib/internal/Magento/Framework/Cache/etc/cache.xsd @@ -6,24 +6,36 @@ */ --> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> - <xs:element name="config" type="configType" /> - - <xs:complexType name="configType"> - <xs:sequence> - <xs:element type="cacheType" name="type" maxOccurs="unbounded" minOccurs="0"/> - </xs:sequence> - </xs:complexType> - <xs:complexType name="cacheType"> <xs:annotation> - <xs:documentation>Cache type declaration</xs:documentation> + <xs:documentation> + Cache type declaration + </xs:documentation> </xs:annotation> - <xs:choice maxOccurs="unbounded" minOccurs="0"> - <xs:element name="label" type="xs:string" /> - <xs:element name="description" type="xs:string" /> - </xs:choice> + <xs:all> + <xs:element name="label" type="xs:string" minOccurs="1" maxOccurs="1"/> + <xs:element name="description" type="xs:string" minOccurs="1" maxOccurs="1"/> + </xs:all> <xs:attribute type="xs:string" name="name" use="required"/> <xs:attribute type="xs:string" name="translate" use="optional"/> - <xs:attribute type="xs:string" name="instance" use="optional"/> + <xs:attribute type="xs:string" name="instance" use="required"/> + </xs:complexType> + + <xs:element name="config" type="configType"> + <xs:unique name="uniqueCacheName"> + <xs:annotation> + <xs:documentation> + Cache name must be unique. + </xs:documentation> + </xs:annotation> + <xs:selector xpath="type"/> + <xs:field xpath="@name"/> + </xs:unique> + </xs:element> + + <xs:complexType name="configType"> + <xs:sequence> + <xs:element type="cacheType" name="type" maxOccurs="unbounded" minOccurs="1"/> + </xs:sequence> </xs:complexType> </xs:schema> From 5abd3665e5f93588670a81503e6823d11d2e0d55 Mon Sep 17 00:00:00 2001 From: Abrar pathan <abrarkhan@krishtechnolabs.com> Date: Wed, 18 Mar 2020 20:08:48 +0530 Subject: [PATCH 337/369] fixed issue#27335 --- .../luma/Magento_Customer/web/css/source/_module.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less index 34a2dbfeca472..a0a36f55574fe 100644 --- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less @@ -348,6 +348,12 @@ position: relative; } } + + .additional-addresses { + table > thead > tr > th { + white-space: nowrap; + } + } } // From f6d047a51fd91a02c4ab9347dc2f41532d827ebb Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Wed, 18 Mar 2020 19:47:45 +0200 Subject: [PATCH 338/369] fix expected count items --- .../testsuite/Magento/Backend/Model/Dashboard/ChartTest.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php index fbca1b8af7e0e..c7d2e3ef17ec0 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php @@ -7,7 +7,6 @@ namespace Magento\Backend\Model\Dashboard; -use Magento\Backend\Model\Dashboard\Chart; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -62,7 +61,7 @@ public function getChartDataProvider(): array 'quantity' ], [ - 6, + 19, '1m', 'quantity' ], From b8163347447bc8ee6636440c1d4ab522cc950af2 Mon Sep 17 00:00:00 2001 From: Eduard Chitoraga <e.chitoraga@atwix.com> Date: Thu, 19 Mar 2020 08:52:34 +0200 Subject: [PATCH 339/369] Fixing static test --- app/code/Magento/Catalog/Block/Product/ListProduct.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php index f1adc1d924836..59f5cc1b53b26 100644 --- a/app/code/Magento/Catalog/Block/Product/ListProduct.php +++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php @@ -355,6 +355,7 @@ public function getIdentities() } foreach ($this->_getProductCollection() as $item) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $identities = array_merge($identities, $item->getIdentities()); } From 26d4b9299d8aefeb37c6cd9f3eb933b8ae23b1c9 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Thu, 19 Mar 2020 08:59:01 +0200 Subject: [PATCH 340/369] MC-25206: Error during image import if "no_selection" is set --- .../Model/Import/Product.php | 22 +++++++++++++++++++ .../Model/Import/ProductTest.php | 17 ++++++++++++++ .../_files/import_media_with_no_selection.csv | 2 ++ 3 files changed, 41 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_with_no_selection.csv diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index d1bce7aaaf951..56baf1c395de0 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1723,6 +1723,7 @@ protected function _saveProducts() } $rowData[self::COL_MEDIA_IMAGE] = []; + list($rowImages, $rowData) = $this->clearNoSelectionImages($rowImages, $rowData); /* * Note: to avoid problems with undefined sorting, the value of media gallery items positions @@ -1929,6 +1930,27 @@ protected function _saveProducts() } //phpcs:enable Generic.Metrics.NestingLevel + /** + * Clears entries from Image Set and Row Data marked as no_selection + * + * @param array $rowImages + * @param array $rowData + * @return array + */ + private function clearNoSelectionImages($rowImages, $rowData) + { + foreach ($rowImages as $column => $columnImages) { + foreach ($columnImages as $key => $image) { + if ($image == 'no_selection') { + unset($rowImages[$column][$key]); + unset($rowData[$column]); + } + } + } + + return [$rowImages, $rowData]; + } + /** * Prepare array with image states (visible or hidden from product page) * diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 93684b1e7a070..a9b1fe6d936ba 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -925,6 +925,23 @@ static function (DataObject $image) { $this->assertEquals('/m/a/magento_additional_image_two.jpg', $imageItem->getFile()); } + /** + * Tests importing product images with "no_selection" attribute. + * + * @magentoDataFixture mediaImportImageFixture + * @magentoAppIsolation enabled + */ + public function testSaveImagesNoSelection() + { + $this->importDataForMediaTest('import_media_with_no_selection.csv'); + $product = $this->getProductBySku('simple_new'); + + $this->assertEquals('/m/a/magento_image.jpg', $product->getData('image')); + $this->assertEquals(null, $product->getData('small_image')); + $this->assertEquals(null, $product->getData('thumbnail')); + $this->assertEquals(null, $product->getData('swatch_image')); + } + /** * Test that new images should be added after the existing ones. * diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_with_no_selection.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_with_no_selection.csv new file mode 100644 index 0000000000000..d603d512eeb7a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_with_no_selection.csv @@ -0,0 +1,2 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,deferred_stock_update,use_config_deferred_stock_update,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,giftcard_type,giftcard_allow_open_amount,giftcard_open_amount_min,giftcard_open_amount_max,giftcard_amount,use_config_is_redeemable,giftcard_is_redeemable,use_config_lifetime,giftcard_lifetime,use_config_allow_message,giftcard_allow_message,use_config_email_template,giftcard_email_template,associated_skus,downloadable_links,downloadable_samples,configurable_variations,configurable_variation_labels +simple_new,,Default,virtual,Default Category/cat1,base,simple_new,,,,1,Taxable Goods,"Catalog, Search",10,,,,simple_new,simple_new,simple_new,simple_new,magento_image.jpg,,no_selection,,no_selection,,no_selection,,"3/12/20, 3:02 PM","3/12/20, 3:02 PM",,,Block after Info Column,,,,Use config,,,,,,,Use config,,,123,0,1,0,0,1,1,1,10000,1,1,1,1,1,1,1,1,1,0,0,0,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, From 75af054d697377f5e144bd35fe09aeb709371108 Mon Sep 17 00:00:00 2001 From: Dmitry Tsymbal <d.tsymbal@atwix.com> Date: Thu, 19 Mar 2020 10:24:22 +0200 Subject: [PATCH 341/369] Re-using Exisitng Action Groups --- .../StorefrontCustomerLoginActionGroup.xml | 21 ------------------- .../StorefrontCustomerLoginFormSection.xml | 16 -------------- ...frontCustomerSubscribeToNewsletterTest.xml | 5 ++--- 3 files changed, 2 insertions(+), 40 deletions(-) delete mode 100644 app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLoginActionGroup.xml delete mode 100644 app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginFormSection.xml diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLoginActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLoginActionGroup.xml deleted file mode 100644 index 4105034e33988..0000000000000 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerLoginActionGroup.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="StorefrontCustomerLoginActionGroup"> - <arguments> - <argument name="customer" type="entity"/> - </arguments> - - <click selector="{{StorefrontPanelHeaderSection.customerLoginLink}}" stepKey="clickSignInLnk"/> - <fillField selector="{{StorefrontCustomerLoginFormSection.emailField}}" userInput="{{customer.email}}" stepKey="fillEmailField"/> - <fillField selector="{{StorefrontCustomerLoginFormSection.passwordField}}" userInput="{{customer.password}}" stepKey="fillPasswordField"/> - <click selector="{{StorefrontCustomerLoginFormSection.signInAccountButton}}" stepKey="clickSignInButton"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginFormSection.xml deleted file mode 100644 index 55f2835d584af..0000000000000 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginFormSection.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> - <section name="StorefrontCustomerLoginFormSection"> - <element name="emailField" type="input" selector=".fieldset.login #email.input-text"/> - <element name="passwordField" type="input" selector=".field.password.required #pass.input-text"/> - <element name="signInAccountButton" type="button" selector=".actions-toolbar #send2.action.login.primary" timeout="30"/> - </section> -</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerSubscribeToNewsletterTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerSubscribeToNewsletterTest.xml index be937ebe4d970..4658b162dcc55 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerSubscribeToNewsletterTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCustomerSubscribeToNewsletterTest.xml @@ -22,9 +22,8 @@ <deleteData createDataKey="createCustomer" stepKey="deleteCreatedCustomer"/> </after> - <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openHomePage"/> - <actionGroup ref="StorefrontCustomerLoginActionGroup" stepKey="loginAsCustomer"> - <argument name="customer" value="$$createCustomer$$"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> </actionGroup> <actionGroup ref="StorefrontCustomerNavigateToNewsletterPageActionGroup" stepKey="navigateToNewsletterPage"/> <actionGroup ref="StorefrontCustomerUpdateGeneralSubscriptionActionGroup" stepKey="subscribeToNewsletter"/> From b7f6d644399456fda2c12148cc79945aee2ee894 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Thu, 19 Mar 2020 10:35:35 +0200 Subject: [PATCH 342/369] MC-32402: [Magento Cloud] - tax calculation with b2b extension --- .../Product/Price/Plugin/CustomerGroup.php | 5 +- .../Price/Plugin/CustomerGroupTest.php | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Plugin/CustomerGroupTest.php diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php index 9b99ee8c8dc8c..e1f4cd28d0090 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php @@ -15,6 +15,9 @@ use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +/** + * Update catalog_product_index_price table after delete or save customer group + */ class CustomerGroup { /** @@ -80,7 +83,7 @@ public function aroundSave( \Closure $proceed, GroupInterface $group ) { - $isGroupNew = !$group->getId(); + $isGroupNew = $group->getId() === null; $group = $proceed($group); if ($isGroupNew) { foreach ($this->getAffectedDimensions((string)$group->getId()) as $dimensions) { diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Plugin/CustomerGroupTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Plugin/CustomerGroupTest.php new file mode 100644 index 0000000000000..8e12ba7670638 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Price/Plugin/CustomerGroupTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Price\Plugin; + +use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; +use Magento\Catalog\Model\Indexer\Product\Price\Plugin\CustomerGroup; +use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer; +use Magento\Customer\Api\GroupRepositoryInterface; +use Magento\Customer\Model\Data\Group; +use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; + +/** + * Test for CustomerGroup plugin + */ +class CustomerGroupTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var CustomerGroup + */ + private $model; + + /** + * @var DimensionFactory|MockObject + */ + private $dimensionFactory; + + /** + * @var TableMaintainer|MockObject + */ + private $tableMaintainerMock; + + /** + * @var DimensionModeConfiguration|MockObject + */ + private $dimensionModeConfiguration; + + /** + * @var \Callable + */ + private $proceedMock; + + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + + $this->dimensionFactory = $this->createPartialMock( + DimensionFactory::class, + ['create'] + ); + + $this->dimensionModeConfiguration = $this->createPartialMock( + DimensionModeConfiguration::class, + ['getDimensionConfiguration'] + ); + + $this->tableMaintainerMock = $this->createPartialMock( + TableMaintainer::class, + ['createTablesForDimensions'] + ); + + $this->model = $this->objectManager->getObject( + CustomerGroup::class, + [ + 'dimensionFactory' => $this->dimensionFactory, + 'dimensionModeConfiguration' => $this->dimensionModeConfiguration, + 'tableMaintainer' => $this->tableMaintainerMock, + ] + ); + } + + /** + * Check of call count createTablesForDimensions() method + * + * @param $customerGroupId + * @param $callTimes + * + * @dataProvider aroundSaveDataProvider + */ + public function testAroundSave($customerGroupId, $callTimes) + { + $subjectMock = $this->createMock(GroupRepositoryInterface::class); + $customerGroupMock = $this->createPartialMock( + Group::class, + ['getId'] + ); + $customerGroupMock->method('getId')->willReturn($customerGroupId); + $this->tableMaintainerMock->expects( + $this->exactly($callTimes) + )->method('createTablesForDimensions'); + $this->proceedMock = function ($customerGroupMock) { + return $customerGroupMock; + }; + $this->dimensionModeConfiguration->method('getDimensionConfiguration')->willReturn( + [CustomerGroupDimensionProvider::DIMENSION_NAME] + ); + $this->model->aroundSave($subjectMock, $this->proceedMock, $customerGroupMock); + } + + /** + * Data provider for testAroundSave + * + * @return array + */ + public function aroundSaveDataProvider() + { + return [ + 'customer_group_id = 0' => [ + 'customer_group_id' => '0', + 'create_tables_call_times' => 0 + ], + 'customer_group_id = 1' => [ + 'customer_group_id' => '1', + 'create_tables_call_times' => 0 + ], + 'customer_group_id = null' => [ + 'customer_group_id' => null, + 'create_tables_call_times' => 1 + ], + ]; + } +} From cad48befd6567944dbe6f2737c0d72ad756b3cc8 Mon Sep 17 00:00:00 2001 From: Viktor Sevch <svitja@ukr.net> Date: Thu, 19 Mar 2020 10:45:23 +0200 Subject: [PATCH 343/369] MC-29052: Magento\FunctionalTestingFramework.functional.AdminTaxReportGridTest fails randomly --- .../Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml index b8e0881c4431f..3e0998b79c3f5 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCheckingTaxReportGridTest.xml @@ -15,8 +15,8 @@ <title value="Checking Tax Report grid"/> <description value="Tax Report Grid displays Tax amount in rows 'Total' and 'Subtotal' is a sum of all tax amounts"/> <severity value="MAJOR"/> - <testCaseId value="MC-6230"/> - <useCaseId value="MC-25815"/> + <testCaseId value="MC-25815"/> + <useCaseId value="MAGETWO-91521"/> <group value="Tax"/> </annotations> <before> From 254a46c8a11683c3958b09b80e59343befa1e2b5 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Thu, 19 Mar 2020 11:32:17 +0200 Subject: [PATCH 344/369] MC-25206: Error during image import if "no_selection" is set --- .../Model/Import/_files/import_media_with_no_selection.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_with_no_selection.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_with_no_selection.csv index d603d512eeb7a..e194637867c1a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_with_no_selection.csv +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media_with_no_selection.csv @@ -1,2 +1,2 @@ -sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,deferred_stock_update,use_config_deferred_stock_update,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,giftcard_type,giftcard_allow_open_amount,giftcard_open_amount_min,giftcard_open_amount_max,giftcard_amount,use_config_is_redeemable,giftcard_is_redeemable,use_config_lifetime,giftcard_lifetime,use_config_allow_message,giftcard_allow_message,use_config_email_template,giftcard_email_template,associated_skus,downloadable_links,downloadable_samples,configurable_variations,configurable_variation_labels -simple_new,,Default,virtual,Default Category/cat1,base,simple_new,,,,1,Taxable Goods,"Catalog, Search",10,,,,simple_new,simple_new,simple_new,simple_new,magento_image.jpg,,no_selection,,no_selection,,no_selection,,"3/12/20, 3:02 PM","3/12/20, 3:02 PM",,,Block after Info Column,,,,Use config,,,,,,,Use config,,,123,0,1,0,0,1,1,1,10000,1,1,1,1,1,1,1,1,1,0,0,0,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,deferred_stock_update,use_config_deferred_stock_update,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,use_config_lifetime,use_config_email_template,associated_skus,downloadable_links,downloadable_samples,configurable_variations,configurable_variation_labels +simple_new,,Default,virtual,Default Category/cat1,base,simple_new,,,,1,Taxable Goods,"Catalog, Search",10,,,,simple_new,simple_new,simple_new,simple_new,magento_image.jpg,,no_selection,,no_selection,,no_selection,,"3/12/20, 3:02 PM","3/12/20, 3:02 PM",,,Block after Info Column,,,,,,,,,,Use config,,,123,0,1,0,0,1,1,1,10000,1,1,1,1,1,1,1,1,1,0,0,0,1,1,,,,,,,,,,,,,,,,,,,,,,, From b6f71cc4c3fb2a0e7f3370665add24e5fbc2720e Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Thu, 19 Mar 2020 11:39:48 +0200 Subject: [PATCH 345/369] Add xml declaration for layout file --- .../view/frontend/layout/catalog_widget_product_list.xml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml b/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml index db44d8b62dc1a..20d1891967318 100644 --- a/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml +++ b/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml @@ -1,8 +1,4 @@ -<!-- - ~ Copyright © Magento, Inc. All rights reserved. - ~ See COPYING.txt for license details. - --> - +<?xml version="1.0"?> <!-- ~ Copyright © Magento, Inc. All rights reserved. ~ See COPYING.txt for license details. @@ -14,4 +10,4 @@ <block class="Magento\Framework\View\Element\Template" name="category.product.type.details.renderers.default" as="default"/> </block> </body> -</page> \ No newline at end of file +</page> From c325160dc331e0358ab0a4d91220d12b015226f4 Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Thu, 19 Mar 2020 11:45:23 +0200 Subject: [PATCH 346/369] Refactored copyright block --- .../view/frontend/layout/catalog_widget_product_list.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml b/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml index 20d1891967318..4fe7af7f34683 100644 --- a/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml +++ b/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml @@ -1,9 +1,10 @@ <?xml version="1.0"?> <!-- - ~ Copyright © Magento, Inc. All rights reserved. - ~ See COPYING.txt for license details. - --> - +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <block class="Magento\Framework\View\Element\RendererList" name="category.product.type.widget.details.renderers"> From 143bd5b5bce5284d53a5e13f6a3b0863f6a2c66b Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <p.bystritsky@yandex.ru> Date: Thu, 19 Mar 2020 14:58:10 +0200 Subject: [PATCH 347/369] magento/magento2#25540: Refactoring. --- .../Model/Import/Product/Type/Bundle.php | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php index dcc6e52460793..e6522054d9f94 100644 --- a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php +++ b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php @@ -26,18 +26,11 @@ */ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType { - /** - * phpcs:disable Magento2.Commenting.ConstantsPHPDocFormatting - */ - /** * Delimiter before product option value. */ const BEFORE_OPTION_VALUE_DELIMITER = ';'; - /** - * Pair value separator. - */ const PAIR_VALUE_SEPARATOR = '='; /** @@ -50,25 +43,12 @@ class Bundle extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abst */ const VALUE_FIXED = 'fixed'; - /** - * Not fixed dynamic attribute. - */ const NOT_FIXED_DYNAMIC_ATTRIBUTE = 'price_view'; - /** - * Selection price type fixed. - */ const SELECTION_PRICE_TYPE_FIXED = 0; - /** - * Selection price type percent. - */ const SELECTION_PRICE_TYPE_PERCENT = 1; - /** - * phpcs:enable Magento2.Commenting.ConstantsPHPDocFormatting - */ - /** * Array of cached options. * From a38c352327732530a0652acd37e1548bac3d6336 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Thu, 19 Mar 2020 15:10:15 +0200 Subject: [PATCH 348/369] MC-24261: [MFTF TEST] Storefront Gallery - Configurable Product with several attributes: prepend variation media --- ...tPageCloseFullscreenGalleryActionGroup.xml | 18 + ...ductPageOpenImageFullscreenActionGroup.xml | 22 + .../Test/Mftf/Data/ProductAttributeData.xml | 4 - .../Section/AdminProductImagesSection.xml | 1 - .../Section/StorefrontProductMediaSection.xml | 5 +- ...rontRecentlyViewedAtStoreViewLevelTest.xml | 3 +- ...urableProductWithSeveralAttributesTest.xml | 383 ++++++++++++++++++ ...nAddProductVideoWithPreviewActionGroup.xml | 20 + .../Section/AdminProductNewVideoSection.xml | 3 +- .../Test/Mftf/Data/SwatchAttributeData.xml | 28 ++ .../Mftf/Data/SwatchAttributeOptionData.xml | 27 ++ ...watchProductAttributeFrontendLabelData.xml | 15 + .../swatch_product_attribute-meta.xml | 46 +++ .../swatch_product_attribute_option-meta.xml | 28 ++ .../StorefrontProductInfoMainSection.xml | 1 + 15 files changed, 596 insertions(+), 8 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageCloseFullscreenGalleryActionGroup.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageOpenImageFullscreenActionGroup.xml create mode 100644 app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontGalleryConfigurableProductWithSeveralAttributesTest.xml create mode 100644 app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AdminAddProductVideoWithPreviewActionGroup.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeOptionData.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/Data/SwatchProductAttributeFrontendLabelData.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/Metadata/swatch_product_attribute-meta.xml create mode 100644 app/code/Magento/Swatches/Test/Mftf/Metadata/swatch_product_attribute_option-meta.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageCloseFullscreenGalleryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageCloseFullscreenGalleryActionGroup.xml new file mode 100644 index 0000000000000..d7dde159f4bdd --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageCloseFullscreenGalleryActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontProductPageCloseFullscreenGalleryActionGroup"> + <annotations> + <description>Closes a product image gallery full-screen page.</description> + </annotations> + + <click selector="{{StorefrontProductMediaSection.closeFullscreenImage}}" stepKey="closeFullScreenPage"/> + <waitForPageLoad stepKey="waitsForCloseFullScreenPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageOpenImageFullscreenActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageOpenImageFullscreenActionGroup.xml new file mode 100644 index 0000000000000..14bd47a234fd2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageOpenImageFullscreenActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontProductPageOpenImageFullscreenActionGroup"> + <annotations> + <description>Finds image of the product in thumbnails and open a full-screen review.</description> + </annotations> + <arguments> + <argument name="imageNumber" type="string" defaultValue="2"/> + </arguments> + + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaImageThumbnail(imageNumber)}}" stepKey="waitThumbnailAppears"/> + <conditionalClick selector="{{StorefrontProductMediaSection.fotoramaImageThumbnail(imageNumber)}}" dependentSelector="{{StorefrontProductMediaSection.fotoramaImageThumbnailActive(imageNumber)}}" visible="false" stepKey="clickOnThumbnailImage"/> + <click selector="{{StorefrontProductMediaSection.gallery}}" stepKey="openFullScreenPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index e4a91902cb79b..abb4301fd8f9f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -394,10 +394,6 @@ <data key="used_for_sort_by">true</data> <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> </entity> - <entity name="VisualSwatchProductAttribute" type="ProductAttribute"> - <data key="frontend_input">swatch_visual</data> - <data key="attribute_code" unique="suffix">visual_swatch</data> - </entity> <entity name="ProductColorAttribute" type="ProductAttribute"> <data key="frontend_label">Color</data> <data key="attribute_code" unique="suffix">color_attr</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml index f20e9b3a11e5e..c2de91aadbc0c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml @@ -18,7 +18,6 @@ <element name="modalOkBtn" type="button" selector="button.action-primary.action-accept"/> <element name="uploadProgressBar" type="text" selector=".uploader .file-row"/> <element name="productImagesToggleState" type="button" selector="[data-index='gallery'] > [data-state-collapsible='{{status}}']" parameterized="true"/> - <element name="nthProductImage" type="button" selector="#media_gallery_content > div:nth-child({{var}}) img.product-image" parameterized="true"/> <element name="nthRemoveImageBtn" type="button" selector="#media_gallery_content > div:nth-child({{var}}) button.action-remove" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml index d3e43d9ea2dfa..447113ea65bb2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductMediaSection"> <element name="gallerySpinner" type="block" selector="#maincontent .fotorama__spinner--show" /> - <element name="gallery" type="block" selector="[data-gallery-role='gallery']" /> + <element name="gallery" type="block" selector="[data-gallery-role='gallery']" timeout="30"/> <element name="productImage" type="text" selector="//*[@data-gallery-role='gallery' and not(contains(@class, 'fullscreen'))]//img[contains(@src, '{{filename}}') and not(contains(@class, 'full'))]" parameterized="true" /> <element name="productImageFullscreen" type="text" selector="//*[@data-gallery-role='gallery' and contains(@class, 'fullscreen')]//img[contains(@src, '{{filename}}') and contains(@class, 'full')]" parameterized="true" /> <element name="closeFullscreenImage" type="button" selector="//*[@data-gallery-role='gallery' and contains(@class, 'fullscreen')]//*[@data-gallery-role='fotorama__fullscreen-icon']" /> @@ -19,5 +19,8 @@ <element name="productImageInFotorama" type="file" selector=".fotorama__nav__shaft img[src*='{{imageName}}']" parameterized="true"/> <element name="fotoramaPrevButton" type="button" selector="//*[@data-gallery-role='gallery']//*[@data-gallery-role='nav-wrap']//*[@data-gallery-role='arrow' and contains(@class, 'fotorama__thumb__arr--left')]"/> <element name="fotoramaNextButton" type="button" selector="//*[@data-gallery-role='gallery']//*[@data-gallery-role='nav-wrap']//*[@data-gallery-role='arrow' and contains(@class, 'fotorama__thumb__arr--right')]"/> + <element name="fotoramaAnyMedia" type="text" selector=".fotorama__nav__shaft img"/> + <element name="fotoramaImageThumbnail" type="block" selector="//div[contains(@class, 'fotorama__nav__shaft')]//div[contains(@class, 'fotorama__nav__frame--thumb')][{{imageNumber}}]" parameterized="true" timeout="30"/> + <element name="fotoramaImageThumbnailActive" type="block" selector="//div[contains(@class, 'fotorama__nav__shaft')]//div[contains(@class, 'fotorama__nav__frame--thumb') and contains(@class, 'fotorama__active')][{{imageNumber}}]" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml index f8ee9e562a6a9..0e09635489d9c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml @@ -12,9 +12,10 @@ <stories value="Recently Viewed Product"/> <title value="Recently Viewed Product at store view level"/> <description value="Recently Viewed Product should not be displayed on second store view, if configured as, Per Store View "/> - <testCaseId value="MC-31877"/> + <testCaseId value="MC-32112"/> <severity value="CRITICAL"/> <group value="catalog"/> + <group value="WYSIWYGDisabled"/> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontGalleryConfigurableProductWithSeveralAttributesTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontGalleryConfigurableProductWithSeveralAttributesTest.xml new file mode 100644 index 0000000000000..a39352ce16189 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontGalleryConfigurableProductWithSeveralAttributesTest.xml @@ -0,0 +1,383 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontGalleryConfigurableProductWithSeveralAttributesTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Verify functionality of updating Gallery items on 'view Product' Storefront page for Configurable Product with several attributes of different types"/> + <title value="Storefront Gallery - Configurable Product with several attributes: prepend variation media"/> + <description value="Storefront Gallery - Configurable Product with several attributes: prepend variation media"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-26396"/> + <group value="catalog"/> + <group value="configurableProduct"/> + <group value="swatch"/> + </annotations> + <before> + <createData entity="ProductVideoYoutubeApiKeyConfig" stepKey="setupYoutubeApiKey"/> + <!--Create 1 configurable product with 2 variations--> + <createData entity="ApiConfigurableProductWithDescription" stepKey="createConfigurableProduct"/> + <!--Create product drop down attribute--> + <createData entity="productDropDownAttribute" stepKey="createDropdownAttribute"/> + <createData entity="productAttributeOption1" stepKey="dropdownAttributeFirstOption"> + <requiredEntity createDataKey="createDropdownAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="dropdownAttributeSecondOption"> + <requiredEntity createDataKey="createDropdownAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getDropdownAttributeFirsOption"> + <requiredEntity createDataKey="createDropdownAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getDropdownAttributeSecondOption"> + <requiredEntity createDataKey="createDropdownAttribute"/> + </getData> + + <!-- Create product swatch attribute with 2 variations --> + <createData entity="VisualSwatchProductAttributeForm" stepKey="createVisualSwatchAttribute"/> + <createData entity="SwatchProductAttributeOption1" stepKey="swatchAttributeFirstOption"> + <requiredEntity createDataKey="createVisualSwatchAttribute"/> + </createData> + <createData entity="SwatchProductAttributeOption2" stepKey="swatchAttributeSecondOption"> + <requiredEntity createDataKey="createVisualSwatchAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getSwatchAttributeFirsOption"> + <requiredEntity createDataKey="createVisualSwatchAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getSwatchAttributeSecondOption"> + <requiredEntity createDataKey="createVisualSwatchAttribute"/> + </getData> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Open configurable product edit page --> + <amOnPage url="{{AdminProductEditPage.url($createConfigurableProduct.id$)}}" stepKey="goToProductIndex"/> + + <!-- Add attributes to configurable product--> + <conditionalClick selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection"/> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="openConfigurationPanel"/> + + <!-- Find Dropdown attribute in grid and select it --> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearAttributeGridFiltersToFindDropdownAttribute"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersPaneForDropdownAttribute"/> + <fillField selector="{{AdminDataGridHeaderSection.attributeCodeFilterInput}}" userInput="$createDropdownAttribute.attribute_code$" stepKey="fillAttributeCodeFilterFieldForDropdownAttribute"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButtonForDropdownAttribute"/> + <click selector="{{AdminDataGridTableSection.rowCheckbox('1')}}" stepKey="selectDropdownAttribute"/> + <!-- Find Swatch attribute in grid and select it --> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearAttributeGridFiltersToFindSwatchAttribute"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="openFiltersPaneForSwatchAttribute"/> + <fillField selector="{{AdminDataGridHeaderSection.attributeCodeFilterInput}}" userInput="$createVisualSwatchAttribute.attribute_code$" stepKey="fillAttributeCodeFilterFieldForSwatchAttribute"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButtonForSwatchAttribute"/> + <click selector="{{AdminDataGridTableSection.rowCheckbox('1')}}" stepKey="selectSwatchAttribute"/> + + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNextToSelectOptions"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute($createDropdownAttribute.default_frontend_label$)}}" stepKey="selectAllDropdownAttributeOptions"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute($createVisualSwatchAttribute.frontend_label[0]$)}}" stepKey="selectAllSwatchAttributeOptions"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNextToApplyQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="100" stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextToProceedToSummary"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickGenerateProductsButton"/> + + <!-- Load media for configurable product --> + <actionGroup ref="AddProductImageActionGroup" stepKey="addFirstImageToConfigurableProduct"> + <argument name="image" value="Magento2"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addSecondImageToConfigurableProduct"> + <argument name="image" value="Magento3"/> + </actionGroup> + <actionGroup ref="AdminAddProductVideoWithPreviewActionGroup" stepKey="addVideoToConfigurableProduct"> + <argument name="image" value="{{TestImageNew.file}}"/> + </actionGroup> + <actionGroup ref="AssertProductVideoAdminProductPageActionGroup" stepKey="assertVideoAddedToConfigurableProduct"/> + <actionGroup ref="SaveConfigurableProductAddToCurrentAttributeSetActionGroup" stepKey="saveConfigurableProduct"/> + + <!-- Load media for configurable product variation option1-option1--> + <actionGroup ref="FilterAndSelectProductActionGroup" stepKey="openConfigurableProductVariationOption1Option1"> + <argument name="productSku" value="$createConfigurableProduct.sku$-$dropdownAttributeFirstOption.option[store_labels][0][label]$-$swatchAttributeFirstOption.option[store_labels][0][label]$"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addFirstImageToConfigurableProductVariationOption1Option1"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addSecondImageToConfigurableProductVariationOption1Option1"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + <actionGroup ref="AdminAddProductVideoWithPreviewActionGroup" stepKey="addVideoToConfigurableProductVariationOption1Option1"> + <argument name="image" value="{{placeholderSmallImage.file}}"/> + </actionGroup> + <actionGroup ref="AssertProductVideoAdminProductPageActionGroup" stepKey="assertVideoAddedToConfigurableProductVariationOption1Option1"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveConfigurableProductVariationOption1Option1"/> + + <!-- Load media for configurable product variation option1-option2--> + <actionGroup ref="FilterAndSelectProductActionGroup" stepKey="openConfigurableProductVariationOption1Option2"> + <argument name="productSku" value="$createConfigurableProduct.sku$-$dropdownAttributeFirstOption.option[store_labels][0][label]$-$swatchAttributeSecondOption.option[store_labels][0][label]$"/> + </actionGroup> + <actionGroup ref="AdminAddProductVideoWithPreviewActionGroup" stepKey="addFirstVideoToConfigurableProductVariationOption1Option2"> + <argument name="image" value="{{Magento3.file}}"/> + </actionGroup> + <actionGroup ref="AssertProductVideoAdminProductPageActionGroup" stepKey="assertFirstVideoAddedToConfigurableProductVariationOption1Option2"/> + <actionGroup ref="AddProductImageActionGroup" stepKey="addFirstImageToConfigurableProductVariationOption1Option2"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + <actionGroup ref="AdminAddProductVideoWithPreviewActionGroup" stepKey="addSecondVideoToConfigurableProductVariationOption1Option2"> + <argument name="image" value="{{placeholderThumbnailImage.file}}"/> + </actionGroup> + <actionGroup ref="AssertProductVideoAdminProductPageActionGroup" stepKey="assertSecondVideoAddedToConfigurableProductVariationOption1Option2"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveConfigurableProductVariationOption1Option2"/> + + <!-- Load media for configurable product variation option2-option2--> + <actionGroup ref="FilterAndSelectProductActionGroup" stepKey="openConfigurableProductVariationOption2Option2"> + <argument name="productSku" value="$createConfigurableProduct.sku$-$dropdownAttributeSecondOption.option[store_labels][0][label]$-$swatchAttributeSecondOption.option[store_labels][0][label]$"/> + </actionGroup> + <actionGroup ref="AddProductImageActionGroup" stepKey="addFirstImageToConfigurableProductVariationOption2Option2"> + <argument name="image" value="ProductImage"/> + </actionGroup> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveConfigurableProductVariationOption2Option2"/> + + <!-- Reindex invalidated indices after product attribute has been created --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndicesAfterCreateAttributes"/> + </before> + + <after> + <createData entity="DefaultProductVideoConfig" stepKey="resetStoreDefaultVideoConfig"/> + <actionGroup ref="DeleteProductUsingProductGridActionGroup" stepKey="deleteConfigurableProductsWithAllVariations"> + <argument name="product" value="$createConfigurableProduct$"/> + </actionGroup> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForDeleteSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="A total of 5 record(s) have been deleted." stepKey="seeDeleteSuccessMessage"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductGridFilters"/> + <actionGroup ref="DeleteProductAttributeActionGroup" stepKey="deleteProductAttributeB"> + <argument name="ProductAttribute" value="$createDropdownAttribute$"/> + </actionGroup> + <actionGroup ref="DeleteProductAttributeActionGroup" stepKey="deleteProductAttributeF"> + <argument name="ProductAttribute" value="$createVisualSwatchAttribute$"/> + </actionGroup> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearProductAttributeGridFilters"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + <!-- Reindex invalidated indices after product attribute has been created --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndicesAfterDeleteAttributes"/> + </after> + + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openConfigurableProductPage"> + <argument name="productUrl" value="$$createConfigurableProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!--CASE 0: Selected options = none; Expected media : C1, C2, C3--> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase0"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase0"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase0[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case0"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase0[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case0"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase0[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case0"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase0"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase0"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase0"> + <expectedResult type="variable">getListOfThumbnailsCase0</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase0</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase0"/> + + <!--CASE 1: Selected options = F2; Expected media : E1, E2, E3, C1, C2, C3--> + <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel($swatchAttributeSecondOption.option[store_labels][0][label]$)}}" stepKey="chooseOptionF2Case1"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase1"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase1"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase1[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case1"/> + <assertRegExp expected="|{{MagentoLogo.filename}}.*.png|" actual="$getListOfThumbnailsCase1[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case1"/> + <assertRegExp expected="|{{placeholderThumbnailImage.name}}.*.jpg|" actual="$getListOfThumbnailsCase1[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case1"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase1[3]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage4Case1"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase1[4]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage5Case1"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase1[5]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage6Case1"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase1"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase1"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase1"> + <expectedResult type="variable">getListOfThumbnailsCase1</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase1</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase1"/> + + <!--CASE 2: Selected options = F1; Expected media : D1, D2, D3, C1, C2, C3--> + <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel($swatchAttributeFirstOption.option[store_labels][0][label]$)}}" stepKey="chooseOptionF1Case2"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase2"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase2"/> + <assertRegExp expected="|{{MagentoLogo.filename}}.*.png|" actual="$getListOfThumbnailsCase2[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case2"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase2[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case2"/> + <assertRegExp expected="|{{placeholderSmallImage.name}}.*.jpg|" actual="$getListOfThumbnailsCase2[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case2"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase2[3]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage4Case2"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase2[4]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage5Case2"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase2[5]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage6Case2"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase2"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase2"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase2"> + <expectedResult type="variable">getListOfThumbnailsCase2</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase2</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase2"/> + + <!--CASE 3: Selected options = B2,F1; Expected media : C1, C2, C3--> + <selectOption userInput="$dropdownAttributeSecondOption.option[store_labels][0][label]$" selector="{{StorefrontProductInfoMainSection.attributeSelectByAttributeID($createDropdownAttribute.default_frontend_label$)}}" stepKey="chooseOptionB2Case3"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase3"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase3"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase3[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case3"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase3[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case3"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase3[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case3"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase3"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase3"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase3"> + <expectedResult type="variable">getListOfThumbnailsCase3</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase3</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase3"/> + + <!--CASE 4: Selected options = B2,F2, Expected media : G1, C1, C2, C3--> + <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel($swatchAttributeSecondOption.option[store_labels][0][label]$)}}" stepKey="chooseOptionF2Case4"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase4"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase4"/> + <assertRegExp expected="|{{ProductImage.filename}}.*.png|" actual="$getListOfThumbnailsCase4[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case4"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase4[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case4"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase4[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case4"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase4[3]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage4Case4"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase4"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase4"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase4"> + <expectedResult type="variable">getListOfThumbnailsCase4</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase4</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase4"/> + + <!--CASE 5: Selected options = B2, Expected media : C1, C2, C3--> + <conditionalClick selector="{{StorefrontProductInfoMainSection.swatchAttributeSelectedOption}}" dependentSelector="{{StorefrontProductInfoMainSection.swatchAttributeSelectedOption}}" visible="true" stepKey="unchooseF2Case5"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase5"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase5"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase5[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case5"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase5[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case5"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase5[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case5"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase5"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase5"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase5"> + <expectedResult type="variable">getListOfThumbnailsCase5</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase5</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase5"/> + + <!--CASE 6: Selected options = B1, Expected media : D1, D2, D3, C1, C2, C3--> + <selectOption userInput="$dropdownAttributeFirstOption.option[store_labels][0][label]$" selector="{{StorefrontProductInfoMainSection.attributeSelectByAttributeID($createDropdownAttribute.default_frontend_label$)}}" stepKey="chooseOptionB1Case6"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase6"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase6"/> + <assertRegExp expected="|{{MagentoLogo.filename}}.*.png|" actual="$getListOfThumbnailsCase6[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case6"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase6[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case6"/> + <assertRegExp expected="|{{placeholderSmallImage.name}}.*.jpg|" actual="$getListOfThumbnailsCase6[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case6"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase6[3]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage4Case6"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase6[4]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage5Case6"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase6[5]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage6Case6"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase6"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase6"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase6"> + <expectedResult type="variable">getListOfThumbnailsCase6</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase6</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase6"/> + + <!--CASE 7: Selected options = B1,F2, Expected media : E1, E2, E3, C1, C2, C3--> + <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel($swatchAttributeSecondOption.option[store_labels][0][label]$)}}" stepKey="chooseOptionF2Case7"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase7"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase7"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase7[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case7"/> + <assertRegExp expected="|{{MagentoLogo.filename}}.*.png|" actual="$getListOfThumbnailsCase7[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case7"/> + <assertRegExp expected="|{{placeholderThumbnailImage.name}}.*.jpg|" actual="$getListOfThumbnailsCase7[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case7"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase7[3]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage4Case7"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase7[4]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage5Case7"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase7[5]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage6Case7"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase7"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase7"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase7"> + <expectedResult type="variable">getListOfThumbnailsCase7</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase7</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase7"/> + + <!--CASE 8: Selected options = B1,F1, Expected media : D1, D2, D3, C1, C2, C3--> + <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel($swatchAttributeFirstOption.option[store_labels][0][label]$)}}" stepKey="chooseOptionF1Case8"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase8"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase8"/> + <assertRegExp expected="|{{MagentoLogo.filename}}.*.png|" actual="$getListOfThumbnailsCase8[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case8"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase8[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case8"/> + <assertRegExp expected="|{{placeholderSmallImage.name}}.*.jpg|" actual="$getListOfThumbnailsCase8[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case8"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase8[3]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage4Case8"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase8[4]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage5Case8"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase8[5]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage6Case8"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase8"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase8"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase8"> + <expectedResult type="variable">getListOfThumbnailsCase8</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase8</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase8"/> + + <!--CASE 9: Selected options = none, Expected media : C1, C2, C3--> + <selectOption userInput="Choose an Option..." selector="{{StorefrontProductInfoMainSection.attributeSelectByAttributeID($createDropdownAttribute.default_frontend_label$)}}" stepKey="unselectB1Case9"/> + <conditionalClick selector="{{StorefrontProductInfoMainSection.swatchAttributeSelectedOption}}" dependentSelector="{{StorefrontProductInfoMainSection.swatchAttributeSelectedOption}}" visible="true" stepKey="unchooseF1Case9"/> + <waitForElementVisible selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="waitForThumbnailsAppearCase9"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsCase9"/> + <assertRegExp expected="|{{Magento2.filename}}.*.jpg|" actual="$getListOfThumbnailsCase9[0]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage1Case9"/> + <assertRegExp expected="|{{Magento3.filename}}.*.jpg|" actual="$getListOfThumbnailsCase9[1]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage2Case9"/> + <assertRegExp expected="|{{TestImageNew.filename}}.*.jpg|" actual="$getListOfThumbnailsCase9[2]" + expectedType="string" actualType="string" stepKey="checkPositionInThumbnailForImage3Case9"/> + <actionGroup ref="StorefrontProductPageOpenImageFullscreenActionGroup" stepKey="openFullScreenPageCase9"/> + <grabMultiple userInput="src" selector="{{StorefrontProductMediaSection.fotoramaAnyMedia}}" stepKey="getListOfThumbnailsFullScreenPageCase9"/> + <assertEquals stepKey="checkPositionInThumbnailForImagesFromFullScreenPageCase9"> + <expectedResult type="variable">getListOfThumbnailsCase9</expectedResult> + <actualResult type="variable">getListOfThumbnailsFullScreenPageCase9</actualResult> + </assertEquals> + <actionGroup ref="StorefrontProductPageCloseFullscreenGalleryActionGroup" stepKey="closeFullScreenPageCase9"/> + </test> +</tests> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AdminAddProductVideoWithPreviewActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AdminAddProductVideoWithPreviewActionGroup.xml new file mode 100644 index 0000000000000..e491a1676a402 --- /dev/null +++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AdminAddProductVideoWithPreviewActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAddProductVideoWithPreviewActionGroup" extends="AddProductVideoActionGroup"> + <annotations> + <description>Adds product video with a preview image on the Admin Product creation/edit page.</description> + </annotations> + <arguments> + <argument name="image" type="string" defaultValue="{{ImageUpload_1.file}}"/> + </arguments> + <attachFile selector="{{AdminProductNewVideoSection.previewImageUploader}}" userInput="{{image}}" after="waitForSaveButtonVisible" stepKey="addPreviewImage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductNewVideoSection.xml b/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductNewVideoSection.xml index 58a1c40a4e470..d1890f9490d98 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductNewVideoSection.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Section/AdminProductNewVideoSection.xml @@ -22,5 +22,6 @@ <element name="thumbnailCheckbox" type="checkbox" selector="#video_thumbnail"/> <element name="hideFromProductPageCheckbox" type="checkbox" selector="#new_video_disabled"/> <element name="errorElement" type="text" selector="#{{inputName}}-error" parameterized="true" /> + <element name="previewImageUploader" type="file" selector=".field-new_video_screenshot #new_video_screenshot"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml index 6070ae25f570f..97702b9deb9b6 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml @@ -21,4 +21,32 @@ <entity name="textSwatchProductAttribute" type="ProductAttribute" extends="productDropDownAttribute"> <data key="frontend_input">swatch_text</data> </entity> + <entity name="VisualSwatchProductAttribute" type="ProductAttribute"> + <data key="frontend_input">swatch_visual</data> + <data key="attribute_code" unique="suffix">visual_swatch</data> + </entity> + + <entity name="VisualSwatchProductAttributeForm" type="SwatchProductAttributeForm"> + <data key="frontend_label[0]" unique="suffix">VisualSwatchAttribute-</data> + <data key="frontend_input">swatch_visual</data> + <data key="is_required">0</data> + <data key="update_product_preview_image">1</data> + <data key="use_product_image_for_swatch">0</data> + <data key="attribute_code" unique="suffix">visual_swatch_attribute_</data> + <data key="is_global">1</data> + <data key="default_value_yesno">0</data> + <data key="is_unique">0</data> + <data key="is_used_in_grid">1</data> + <data key="is_visible_in_grid">1</data> + <data key="is_filterable_in_grid">1</data> + <data key="is_searchable">1</data> + <data key="is_comparable">1</data> + <data key="is_filterable">1</data> + <data key="is_filterable_in_search">1</data> + <data key="is_used_for_promo_rules">0</data> + <data key="is_html_allowed_on_front">1</data> + <data key="is_visible_on_front">1</data> + <data key="used_in_product_listing">1</data> + <data key="used_for_sort_by">0</data> + </entity> </entities> diff --git a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeOptionData.xml b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeOptionData.xml new file mode 100644 index 0000000000000..a46bc0544b642 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeOptionData.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SwatchProductAttributeOption1" type="ProductSwatchAttributeOption"> + <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="label" unique="suffix">swatch-option1-</data> + <data key="is_default">false</data> + <data key="sort_order">0</data> + <requiredEntity type="StoreLabel">Option1Store0</requiredEntity> + <requiredEntity type="StoreLabel">Option1Store1</requiredEntity> + </entity> + <entity name="SwatchProductAttributeOption2" type="ProductSwatchAttributeOption"> + <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="label" unique="suffix">swatch-option2-</data> + <data key="is_default">true</data> + <data key="sort_order">1</data> + <requiredEntity type="StoreLabel">Option2Store0</requiredEntity> + <requiredEntity type="StoreLabel">Option2Store1</requiredEntity> + </entity> +</entities> diff --git a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchProductAttributeFrontendLabelData.xml b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchProductAttributeFrontendLabelData.xml new file mode 100644 index 0000000000000..095fd65f3a99c --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchProductAttributeFrontendLabelData.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SwatchProductAttributeFrontendLabel" type="FrontendLabel"> + <data key="store_id">0</data> + <data key="label" unique="suffix">Swatch-Attribute-</data> + </entity> +</entities> diff --git a/app/code/Magento/Swatches/Test/Mftf/Metadata/swatch_product_attribute-meta.xml b/app/code/Magento/Swatches/Test/Mftf/Metadata/swatch_product_attribute-meta.xml new file mode 100644 index 0000000000000..795892dbb4d47 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Metadata/swatch_product_attribute-meta.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateSwatchProductAttributeForm" dataType="SwatchProductAttributeForm" type="create" + auth="adminFormKey" url="catalog/product_attribute/save" method="POST" successRegex="/messages-message-success/" returnRegex=""> + <contentType>application/x-www-form-urlencoded</contentType> + <field key="frontend_label[0]">string</field> + <field key="frontend_label[1]">string</field> + <field key="frontend_input">string</field> + <field key="is_required">string</field> + <field key="update_product_preview_image">string</field> + <field key="use_product_image_for_swatch">string</field> + <field key="visual_swatch_validation">string</field> + <field key="visual_swatch_validation_unique">string</field> + <field key="text_swatch_validation">string</field> + <field key="text_swatch_validation_unique">string</field> + <field key="dropdown_attribute_validation">string</field> + <field key="dropdown_attribute_validation_unique">string</field> + <field key="attribute_code">string</field> + <field key="is_global">string</field> + <field key="default_value_text">string</field> + <field key="default_value_yesno">string</field> + <field key="default_value_date">string</field> + <field key="default_value_textarea">string</field> + <field key="is_unique">string</field> + <field key="is_used_in_grid">string</field> + <field key="is_visible_in_grid">string</field> + <field key="is_filterable_in_grid">string</field> + <field key="is_searchable">string</field> + <field key="is_comparable">string</field> + <field key="is_filterable">string</field> + <field key="is_filterable_in_search">string</field> + <field key="is_used_for_promo_rules">string</field> + <field key="is_html_allowed_on_front">string</field> + <field key="is_visible_on_front">string</field> + <field key="used_in_product_listing">string</field> + <field key="used_for_sort_by">string</field> + </operation> +</operations> diff --git a/app/code/Magento/Swatches/Test/Mftf/Metadata/swatch_product_attribute_option-meta.xml b/app/code/Magento/Swatches/Test/Mftf/Metadata/swatch_product_attribute_option-meta.xml new file mode 100644 index 0000000000000..b70550f71400b --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Metadata/swatch_product_attribute_option-meta.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateProductSwatchAttributeOption" dataType="ProductSwatchAttributeOption" type="create" auth="adminOauth" url="/V1/products/attributes/{attribute_code}/options" method="POST"> + <contentType>application/json</contentType> + <object dataType="ProductSwatchAttributeOption" key="option"> + <field key="label">string</field> + <field key="sort_order">integer</field> + <field key="is_default">boolean</field> + <array key="store_labels"> + <value>StoreLabel</value> + </array> + </object> + </operation> + <operation name="DeleteProductSwatchAttributeOption" dataType="ProductSwatchAttributeOption" type="delete" auth="adminOauth" url="/V1/products/attributes/{attribute_code}/options/{option_id}" method="DELETE"> + <contentType>application/json</contentType> + </operation> + <operation name="GetProductSwatchAttributeOption" dataType="ProductSwatchAttributeOption" type="get" auth="adminOauth" url="/V1/products/attributes/{attribute_code}/options/" method="GET"> + <contentType>application/json</contentType> + </operation> +</operations> diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 5b714f01fd46f..18ea4152939ac 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -17,5 +17,6 @@ <element name="productSwatch" type="button" selector="//div[@class='swatch-option'][@aria-label='{{var1}}']" parameterized="true"/> <element name="visualSwatchOption" type="button" selector=".swatch-option[option-tooltip-value='#{{visualSwatchOption}}']" parameterized="true"/> <element name="swatchOptionTooltip" type="block" selector="div.swatch-option-tooltip"/> + <element name="swatchAttributeSelectedOption" type="text" selector="#product-options-wrapper .swatch-option.selected"/> </section> </sections> From 42576498389a27f9b1b9f6f9ae800f6e2aa80405 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Thu, 19 Mar 2020 15:18:30 +0200 Subject: [PATCH 349/369] MC-32306: Errors while trying to update downloadable product after MC-29952 --- .../Model/Import/Product/Type/Downloadable.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php index b408a77e0c95e..1d8e4954cbe54 100644 --- a/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php +++ b/app/code/Magento/DownloadableImportExport/Model/Import/Product/Type/Downloadable.php @@ -17,6 +17,7 @@ * * phpcs:disable Magento2.Commenting.ConstantsPHPDocFormatting * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class Downloadable extends \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType { From 270e3dc001716b180c1cbdf91288a6debdeac7fb Mon Sep 17 00:00:00 2001 From: root <greenspa@adobe.com> Date: Wed, 18 Mar 2020 14:54:24 +0000 Subject: [PATCH 350/369] COMOPS-910: Split MFTF tests by BLOCKER severity --- .../Test/Mftf/Test/AdminAddBundleItemsTest.xml | 2 +- .../AdminAssociateBundleProductToWebsitesTest.xml | 2 +- .../Mftf/Test/AdminProductBundleCreationTest.xml | 2 +- .../Test/Mftf/Test/BundleProductFixedPricingTest.xml | 2 +- .../Test/CurrencyChangingBundleProductInCartTest.xml | 2 +- .../Mftf/Test/StorefrontBundleProductDetailsTest.xml | 2 +- ...ntBundleProductShownInCategoryListAndGridTest.xml | 2 +- .../Mftf/Test/StorefrontEditBundleProductTest.xml | 2 +- ...StorefrontSpecialPriceBundleProductInCartTest.xml | 2 +- .../Test/Mftf/Test/AddToCartCrossSellTest.xml | 2 +- .../Test/AdminAddDefaultImageSimpleProductTest.xml | 2 +- .../Test/AdminAddDefaultVideoSimpleProductTest.xml | 2 +- ...AdminAssignProductAttributeToAttributeSetTest.xml | 2 +- .../AdminCreateAndEditSimpleProductSettingsTest.xml | 2 +- .../Mftf/Test/AdminCreateAttributeSetEntityTest.xml | 2 +- .../Test/Mftf/Test/AdminCreateCategoryTest.xml | 6 +++--- ...teCustomProductAttributeWithDropdownFieldTest.xml | 2 +- .../Test/AdminCreateDropdownProductAttributeTest.xml | 2 +- ...buteVisibleInStorefrontAdvancedSearchFormTest.xml | 2 +- ...buteVisibleInStorefrontAdvancedSearchFormTest.xml | 2 +- .../Test/AdminCreateNewGroupForAttributeSetTest.xml | 2 +- ...dminCreateProductAttributeFromProductPageTest.xml | 2 +- ...inCreateProductAttributeRequiredTextFieldTest.xml | 2 +- .../Test/Mftf/Test/AdminCreateSimpleProductTest.xml | 8 ++++---- ...eDropdownProductAttributeFromAttributeSetTest.xml | 2 +- .../Mftf/Test/AdminDeleteProductAttributeTest.xml | 2 +- .../Test/Mftf/Test/AdminDeleteRootCategoryTest.xml | 2 +- .../Test/Mftf/Test/AdminDeleteSimpleProductTest.xml | 2 +- .../Test/AdminDeleteSystemProductAttributeTest.xml | 2 +- ...TextFieldProductAttributeFromAttributeSetTest.xml | 2 +- .../Test/AdminEditTextEditorProductAttributeTest.xml | 2 +- .../Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml | 2 +- .../AdminMoveCategoryAndCheckUrlRewritesTest.xml | 2 +- ...veCategoryToAnotherPositionInCategoryTreeTest.xml | 2 +- .../AdminMultipleWebsitesUseDefaultValuesTest.xml | 2 +- .../Test/AdminNavigateMultipleUpSellProductsTest.xml | 2 +- ...ductCategoryIndexerInUpdateOnScheduleModeTest.xml | 2 +- .../Test/Mftf/Test/AdminSimpleProductImagesTest.xml | 4 ++-- .../Test/AdminSimpleSetEditRelatedProductsTest.xml | 2 +- ...nUnassignProductAttributeFromAttributeSetTest.xml | 2 +- ...eCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml | 2 +- .../Test/AdminUpdateCategoryAndMakeInactiveTest.xml | 2 +- .../Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml | 2 +- .../AdminUpdateCategoryUrlKeyWithStoreViewTest.xml | 2 +- .../Test/AdminUpdateCategoryWithProductsTest.xml | 2 +- ...AdminUpdateFlatCategoryNameAndDescriptionTest.xml | 2 +- ...uctWithRegularPriceInStockDisabledProductTest.xml | 2 +- ...ularPriceInStockVisibleInCatalogAndSearchTest.xml | 2 +- ...thRegularPriceInStockVisibleInCatalogOnlyTest.xml | 2 +- ...tWithRegularPriceInStockWithCustomOptionsTest.xml | 2 +- .../AdminUpdateTopCategoryUrlWithNoRedirectTest.xml | 2 +- .../AdminUpdateTopCategoryUrlWithRedirectTest.xml | 2 +- ...nfigurableOptionTextInputLengthValidationHint.xml | 2 +- .../Mftf/Test/CreateProductAttributeEntityTest.xml | 12 ++++++------ ...roductAvailableAfterEnablingSubCategoriesTest.xml | 2 +- ...SaveProductWithCustomOptionsSecondWebsiteTest.xml | 2 +- .../Mftf/Test/SimpleProductTwoCustomOptionsTest.xml | 2 +- .../Test/Mftf/Test/StorefrontFotoramaArrowsTest.xml | 2 +- .../StorefrontProductNameWithDoubleQuoteTest.xml | 2 +- .../Test/StorefrontProductWithEmptyAttributeTest.xml | 2 +- ...cialPriceForDifferentTimezonesForWebsitesTest.xml | 2 +- ...ryProductAndProductCategoryPartialReindexTest.xml | 2 +- .../Test/AdminApplyCatalogRuleByCategoryTest.xml | 2 +- .../RewriteStoreLevelUrlKeyOfChildCategoryTest.xml | 2 +- .../OnePageCheckoutAsCustomerUsingNewAddressTest.xml | 2 +- ...eCheckoutAsCustomerUsingNonDefaultAddressTest.xml | 2 +- .../Test/OnePageCheckoutWithAllProductTypesTest.xml | 2 +- ...ToTheShoppingCartWithoutAnySelectedOptionTest.xml | 2 +- ...ShippingRecalculationAfterCouponCodeAddedTest.xml | 2 +- ...thAllTypesOfCustomOptionToTheShoppingCartTest.xml | 2 +- .../Mftf/Test/StorefrontCustomerCheckoutTest.xml | 2 +- ...stomerRegistrationAndDisableGuestCheckoutTest.xml | 2 +- .../Test/Mftf/Test/StorefrontGuestCheckoutTest.xml | 2 +- ...tentDataForGuestCustomerWithPhysicalQuoteTest.xml | 2 +- ...UpdatePriceInShoppingCartAfterProductSaveTest.xml | 2 +- ...refrontUpdateShoppingCartSimpleProductQtyTest.xml | 2 +- ...pingCartSimpleWithCustomOptionsProductQtyTest.xml | 2 +- .../Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml | 2 +- .../Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml | 2 +- .../Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml | 2 +- .../Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml | 2 +- ...dminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml | 2 +- ...nAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml | 2 +- ...idgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml | 2 +- ...WidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml | 2 +- ...WidgetToWYSIWYGWithCatalogProductListTypeTest.xml | 2 +- ...ToWYSIWYGWithRecentlyComparedProductsTypeTest.xml | 2 +- ...etToWYSIWYGWithRecentlyViewedProductsTypeTest.xml | 2 +- .../Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml | 2 +- .../Mftf/Test/AdminConfigurableProductCreateTest.xml | 4 ++-- .../Mftf/Test/AdminConfigurableProductDeleteTest.xml | 4 ++-- .../Mftf/Test/AdminConfigurableProductUpdateTest.xml | 10 +++++----- ...erifyConfigurableProductLayeredNavigationTest.xml | 2 +- ...dNewDefaultBillingShippingCustomerAddressTest.xml | 2 +- .../Test/Mftf/Test/AdminCreateCustomerTest.xml | 2 +- .../AdminDeleteCustomerAddressesFromTheGridTest.xml | 2 +- .../Test/Mftf/Test/AdminDeleteCustomerTest.xml | 2 +- .../AdminDeleteDefaultBillingCustomerAddressTest.xml | 2 +- ...EditDefaultBillingShippingCustomerAddressTest.xml | 2 +- .../Test/Mftf/Test/AdminUpdateCustomerTest.xml | 4 ++-- .../Mftf/Test/StorefrontAddCustomerAddressTest.xml | 6 +++--- .../Test/Mftf/Test/StorefrontCreateCustomerTest.xml | 2 +- .../StorefrontResetCustomerPasswordSuccessTest.xml | 2 +- .../StorefrontUpdateCustomerAddressFranceTest.xml | 2 +- .../Test/StorefrontUpdateCustomerAddressUKTest.xml | 2 +- .../Test/StorefrontUpdateCustomerPasswordTest.xml | 4 ++-- .../ProductQuickSearchUsingElasticSearchTest.xml | 2 +- .../StorefrontElasticSearchForChineseLocaleTest.xml | 2 +- ...torefrontElasticsearch6SearchInvalidValueTest.xml | 2 +- .../AdminImportProductsWithAddUpdateBehaviorTest.xml | 2 +- .../AdminImportProductsWithDeleteBehaviorTest.xml | 2 +- ...ProductImportCSVFileCorrectDifferentFilesTest.xml | 2 +- ...tVisibilityDifferentStoreViewsAfterImportTest.xml | 2 +- ...rksWhenUpdatingProductThroughImportingCSVTest.xml | 2 +- .../Test/StoreFrontCheckingWithMultishipmentTest.xml | 2 +- ...gCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml | 2 +- .../Test/YoutubeVideoWindowOnProductPageTest.xml | 2 +- ...onfigurableProductToOrderFromShoppingCartTest.xml | 2 +- .../AddSimpleProductToOrderFromShoppingCartTest.xml | 2 +- ...edOrderWithProductQtyWithoutStockDecreaseTest.xml | 2 +- .../Test/AdminCreateCreditMemoPartialRefundTest.xml | 2 +- .../AdminCreateCreditMemoWithPurchaseOrderTest.xml | 2 +- ...tomerWithTwoAddressesTaxableAndNonTaxableTest.xml | 2 +- ...nCreateOrderWithSelectedShoppingCartItemsTest.xml | 2 +- .../AdminSubmitsOrderWithAndWithoutEmailTest.xml | 2 +- .../Test/CreateInvoiceAndCheckInvoiceOrderTest.xml | 2 +- .../Test/CreateOrderFromEditCustomerPageTest.xml | 2 +- ...ConfigurableProductsInComparedOnOrderPageTest.xml | 2 +- ...LastOrderedConfigurableProductOnOrderPageTest.xml | 2 +- .../MoveLastOrderedSimpleProductOnOrderPageTest.xml | 2 +- ...entlyViewedConfigurableProductOnOrderPageTest.xml | 2 +- .../MoveSimpleProductsInComparedOnOrderPageTest.xml | 2 +- .../Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml | 2 +- .../Test/StorefrontAutoGeneratedCouponCodeTest.xml | 2 +- .../Test/StorefrontCartPriceRuleSubtotalTest.xml | 2 +- .../Mftf/Test/StorefrontFilterByImageSwatchTest.xml | 2 +- .../Mftf/Test/StorefrontFilterByTextSwatchTest.xml | 2 +- .../Mftf/Test/StorefrontFilterByVisualSwatchTest.xml | 2 +- ...CategoryUrlRewriteAndAddPermanentRedirectTest.xml | 2 +- ...eProductUrLRewriteAndAddPermanentRedirectTest.xml | 2 +- ...eProductUrLRewriteAndAddTemporaryRedirectTest.xml | 2 +- ...CategoryUrlRewriteAndAddPermanentRedirectTest.xml | 2 +- ...CategoryUrlRewriteAndAddTemporaryRedirectTest.xml | 2 +- ...inUrlRewritesForProductInAnchorCategoriesTest.xml | 2 +- .../Test/Mftf/Test/NewProductsListWidgetTest.xml | 2 +- .../Widget/Test/Mftf/Test/ProductsListWidgetTest.xml | 2 +- 146 files changed, 167 insertions(+), 167 deletions(-) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml index 2b6b139520f97..c67a207ebf0b1 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddBundleItemsTest.xml @@ -14,7 +14,7 @@ <stories value="Create/Edit bundle product in Admin"/> <title value="Admin should be able to add/edit bundle items when creating/editing a bundle product"/> <description value="Admin should be able to add/edit bundle items when creating/editing a bundle product"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-223"/> <group value="Bundle"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml index 823ad303c5455..baf9652522909 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml @@ -15,7 +15,7 @@ <title value="Admin should be able to associate bundle product to websites"/> <description value="Admin should be able to associate bundle product to websites"/> <testCaseId value="MC-3344"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="bundle"/> <group value="catalog"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml index 7d82c6e5b43ad..0af44f64f8df1 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminProductBundleCreationTest.xml @@ -14,7 +14,7 @@ <stories value="Create/Edit bundle product in Admin"/> <title value="Admin should be able to save and publish a bundle product"/> <description value="Admin should be able to save and publish a bundle product"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-225"/> <group value="Bundle"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml index 80920c2e6d851..171ed1323a6b2 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml @@ -14,7 +14,7 @@ <stories value="Bundle Product Pricing"/> <title value="Admin should be able to apply fixed pricing for Bundled Product"/> <description value="Admin should be able to apply fixed pricing for Bundled Product"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-186"/> <group value="Bundle"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml index 4efacbb267a0b..14753da609967 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml @@ -13,7 +13,7 @@ <stories value="Check that after changing currency price of cart is correct when the bundle product added to the cart"/> <title value="User should be able change the currency and get right price in cart when the bundle product added to the cart"/> <description value="User should be able change the currency and add one more product in cart and get right price in previous currency"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-94467"/> <group value="Bundle"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml index b174ee2ee3c70..15d5ff7d3560b 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductDetailsTest.xml @@ -14,7 +14,7 @@ <stories value="Bundle product details page"/> <title value="Customer should be able to see basic bundle product details"/> <description value="Customer should be able to see basic bundle product details"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-230"/> <group value="Bundle"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml index 62a66b7d092ef..a7e34b4744b90 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontBundleProductShownInCategoryListAndGridTest.xml @@ -14,7 +14,7 @@ <stories value="Bundle products list on Storefront"/> <title value="Customer should be able to see bundle products in the category products list and grid views"/> <description value="Customer should be able to see bundle products in the category products list and grid views"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-226"/> <group value="Bundle"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml index 3a40a1b7eeb71..c0ea00167d329 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml @@ -14,7 +14,7 @@ <stories value="Bundle products list on Storefront"/> <title value="Customer should be able to change chosen options for Bundle Product when clicking Edit button in Shopping Cart page"/> <description value="Customer should be able to change chosen options for Bundle Product when clicking Edit button in Shopping Cart page"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-290"/> <group value="Bundle"/> </annotations> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml index 44ac68a2759f3..32662321a611e 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml @@ -13,7 +13,7 @@ <stories value="Add bundle product to cart on storefront"/> <title value="Customer should not be able to add a Bundle Product to the cart when added a special price for associated products"/> <description value="Customer should not be able to add a Bundle Product to the cart when added a special price for associated products"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-19134"/> <useCaseId value="MC-18963"/> <group value="bundle"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml index 9abad132d32db..faba11f2234bc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml @@ -14,7 +14,7 @@ <stories value="Promote Products as Cross-Sells"/> <title value="Admin should be able to add cross-sell to products."/> <description value="Create products, add products to cross sells, and check that they appear in the Shopping Cart page."/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-9143"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml index ac1f967b66e41..ce8ce5ead1153 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml @@ -14,7 +14,7 @@ <stories value="Add/remove images and videos for all product types and category"/> <title value="Admin should be able to add default image for a Simple Product"/> <description value="Admin should be able to add default images for a Simple Product"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-113"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml index ba91817a8dc6e..adf13e4e31435 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml @@ -14,7 +14,7 @@ <stories value="Add/remove images and videos for all product types and category"/> <title value="Admin should be able to add default product video for a Simple Product"/> <description value="Admin should be able to add default product video for a Simple Product"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-111"/> <group value="Catalog"/> <skip> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml index 83916d9d96027..980760699dc71 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAssignProductAttributeToAttributeSetTest.xml @@ -14,7 +14,7 @@ <stories value="Add/Update attribute set"/> <title value="Admin should be able to assign attributes to an attribute set"/> <description value="Admin should be able to assign attributes to an attribute set"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-168"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml index 2fddc667b60fd..9cb709a8c7e62 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml @@ -14,7 +14,7 @@ <stories value="Create/Edit simple product in Admin"/> <title value="Admin should be able to set/edit other product information when creating/editing a simple product"/> <description value="Admin should be able to set/edit product information when creating/editing a simple product"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-3241"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml index f2783a9fcf2cb..aba7bf6073117 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml @@ -15,7 +15,7 @@ <title value="Create attribute set with new product attribute"/> <description value="Admin should be able to create attribute set with new product attribute"/> <testCaseId value="MC-10884"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml index 44a83f2dbadd4..47069004e5009 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml @@ -14,7 +14,7 @@ <stories value="Create a Category via the Admin"/> <title value="Admin should be able to create a Category"/> <description value="Admin should be able to create a Category"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-72102"/> <group value="category"/> </annotations> @@ -43,7 +43,7 @@ <stories value="Default layout configuration MAGETWO-88793"/> <title value="Admin should be able to configure the default layout for Category Page from System Configuration"/> <description value="Admin should be able to configure the default layout for Category Page from System Configuration"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-89024"/> <group value="category"/> </annotations> @@ -75,7 +75,7 @@ <stories value="Default layout configuration MAGETWO-88793"/> <title value="Category should not be saved once layered navigation price step field is left empty"/> <description value="Once the Config setting is unchecked Category should not be saved with layered navigation price field left empty"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-95797"/> <group value="category"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml index 2b33f4a6bb1c0..06d7d1081d058 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml @@ -13,7 +13,7 @@ <stories value="Create product Attribute"/> <title value="Create Custom Product Attribute Dropdown Field (Not Required) from Product Page"/> <description value="login as admin and create configurable product attribute with Dropdown field"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10905"/> <group value="mtf_migrated"/> <skip> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml index 4b69123635852..34d7fc1c24c61 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml @@ -12,7 +12,7 @@ <stories value="Create/configure Dropdown product attribute"/> <title value="Admin should be able to create dropdown product attribute"/> <description value="Admin should be able to create dropdown product attribute"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-4982"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml index 7ae42948175b1..4d2250d650da2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml @@ -15,7 +15,7 @@ <title value="AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest"/> <description value="Admin should able to create product Dropdown attribute and check its visibility on frontend in Advanced Search form"/> <testCaseId value="MC-10827"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml index 62eea9d48ccc0..b5b5fa3b12dc7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml @@ -15,7 +15,7 @@ <title value="Create product attribute of type Multiple Select and check its visibility on frontend in Advanced Search form"/> <description value="Admin should be able to create product attribute of type Multiple Select and check its visibility on frontend in Advanced Search form"/> <testCaseId value="MC-10828"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml index 13a7974575640..6cd4ce6ac47c4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewGroupForAttributeSetTest.xml @@ -13,7 +13,7 @@ <stories value="Edit attribute set"/> <title value="Admin should be able to create new group in an Attribute Set"/> <description value="The test verifies creating a new group in an attribute set and a validation message in case of empty group name"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-170"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml index 18d1ec5b30f72..ae4720e19118d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml @@ -13,7 +13,7 @@ <stories value="Create product Attribute"/> <title value="Create Product Attribute from Product Page"/> <description value="Login as admin and create new product attribute from product page with Text Field"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10899"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml index a6632fa1ddabb..a75cb093ec8ee 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml @@ -13,7 +13,7 @@ <stories value="Manage products"/> <title value="Create Custom Product Attribute Text Field (Required) from Product Page"/> <description value="Login as admin and create product attribute with Text Field and Required option"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10906"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest.xml index 1c6ed551d3f72..9f479edb91ce6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest.xml @@ -14,7 +14,7 @@ <stories value="Create a Simple Product via Admin"/> <title value="Admin should be able to create a Simple Product"/> <description value="Admin should be able to create a Simple Product"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-23414"/> <group value="product"/> </annotations> @@ -46,7 +46,7 @@ <stories value="Default layout configuration MAGETWO-88793"/> <title value="Admin should be able to configure a default layout for Product Page from System Configuration"/> <description value="Admin should be able to configure a default layout for Product Page from System Configuration"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-89023"/> <group value="product"/> </annotations> @@ -76,7 +76,7 @@ <stories value="Create a Simple Product via Admin"/> <title value="Admin should be able to create a product with zero price"/> <description value="Admin should be able to create a product with zero price"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-89910"/> <group value="product"/> </annotations> @@ -97,7 +97,7 @@ <stories value="Create a Simple Product via Admin"/> <title value="Admin should not be able to create a product with a negative price"/> <description value="Admin should not be able to create a product with a negative price"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-89912"/> <group value="product"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml index db57b5e7d1181..9d52399f15a96 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml @@ -13,7 +13,7 @@ <stories value="Delete product attributes"/> <title value="Delete Product Attribute, Dropdown Type, from Attribute Set"/> <description value="Login as admin and delete dropdown type product attribute from attribute set"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10885"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml index d90bb162acca9..20d2a924954cb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml @@ -14,7 +14,7 @@ <title value="Delete Product Attribute"/> <description value="Admin should able to delete a product attribute"/> <testCaseId value="MC-10887"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml index 5e9e536203f1e..4974b983beeb9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml @@ -13,7 +13,7 @@ <title value="Can delete a root category not assigned to any store"/> <description value="Login as admin and delete a root category not assigned to any store"/> <testCaseId value="MC-6048"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="Catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml index 5c8b90a26594b..b5d7f413730aa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml @@ -14,7 +14,7 @@ <stories value="Delete products"/> <title value="Delete Simple Product"/> <description value="Admin should be able to delete a simple product"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-11013"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml index c3a550165de89..be54262987b05 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml @@ -14,7 +14,7 @@ <title value="Delete System Product Attribute"/> <description value="Admin should not be able to see Delete Attribute button"/> <testCaseId value="MC-10893"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml index 44996d167feaa..3d6eca36ec06d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml @@ -13,7 +13,7 @@ <stories value="Delete product attributes"/> <title value="Delete Product Attribute, Text Field, from Attribute Set"/> <description value="Login as admin and delete Text Field type product attribute from attribute set"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10886"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml index 30b06ac8e0b43..ebbfdc4d72f40 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml @@ -14,7 +14,7 @@ <group value="Catalog"/> <title value="Admin are able to change Input Type of Text Editor product attribute"/> <description value="Admin are able to change Input Type of Text Editor product attribute"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-6215"/> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml index 72ef78accb7fc..820a578e0252f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryTest.xml @@ -13,7 +13,7 @@ <stories value="Edit categories"/> <title value="Admin should be able to move a category via categories tree and changes should be applied on frontend without a forced cache cleaning"/> <description value="Admin should be able to move a category via categories tree and changes should be applied on frontend without a forced cache cleaning"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10022"/> <useCaseId value="MAGETWO-89248"/> <group value="category"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml index 77ae5dbf64840..ac766feef1742 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml @@ -14,7 +14,7 @@ <description value="Login as admin, move category from one to another and check category url rewrites"/> <testCaseId value="MC-6494"/> <features value="Catalog"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml index 801d925c0fd84..f49b9b5b5d3e3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml @@ -14,7 +14,7 @@ <title value="Move Category to Another Position in Category Tree"/> <description value="Test log in to Move Category and Move Category to Another Position in Category Tree"/> <testCaseId value="MC-13612"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMultipleWebsitesUseDefaultValuesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMultipleWebsitesUseDefaultValuesTest.xml index d56f64d72a1c1..c1cfcf7ebe10f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMultipleWebsitesUseDefaultValuesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMultipleWebsitesUseDefaultValuesTest.xml @@ -14,7 +14,7 @@ <stories value="Create websites"/> <title value="Use Default Value checkboxes should be checked for new website scope"/> <description value="Use Default Value checkboxes for product attribute should be checked for new website scope"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-92454"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml index e88645fdc3782..d0e466114a1d4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml @@ -14,7 +14,7 @@ <title value="Promote Multiple Products (Simple, Configurable) as Up-Sell Products"/> <description value="Login as admin and add simple and configurable Products as Up-Sell products"/> <testCaseId value="MC-8902"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml index b547fbe69fbf7..4cdbd75648d56 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCategoryIndexerInUpdateOnScheduleModeTest.xml @@ -14,7 +14,7 @@ <title value="Product Categories Indexer in Update on Schedule mode"/> <description value="The test verifies that in Update on Schedule mode if displaying of category products on Storefront changes due to product properties change, the changes are NOT applied immediately, but applied only after cron runs (twice)."/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-11146"/> <group value="catalog"/> <group value="indexer"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml index 3d505b9f893eb..cd80467c66d06 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml @@ -14,7 +14,7 @@ <stories value="Add/remove images and videos for all product types and category"/> <title value="Admin should be able to add images of different types and sizes to Simple Product"/> <description value="Admin should be able to add images of different types and sizes to Simple Product"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <testCaseId value="MC-189"/> <group value="Catalog"/> </annotations> @@ -172,7 +172,7 @@ <stories value="Add/remove images and videos for all product types and category"/> <title value="Admin should be able to remove Product Images assigned as Base, Small and Thumbnail from Simple Product"/> <description value="Admin should be able to remove Product Images assigned as Base, Small and Thumbnail from Simple Product"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-191"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml index 84eb3a843aa1f..76e0bdd5d46ad 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml @@ -14,7 +14,7 @@ <stories value="Create/edit simple product"/> <title value="Admin should be able to set/edit Related Products information when editing a simple product"/> <description value="Admin should be able to set/edit Related Products information when editing a simple product"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-3411"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml index 831444c924ec1..307eceddae3ef 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml @@ -14,7 +14,7 @@ <stories value="Add/Update attribute set"/> <title value="Admin should be able to unassign attributes from an attribute set"/> <description value="Admin should be able to unassign attributes from an attribute set"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-194"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml index 0f4e6e2854948..4b1cd1f674425 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml @@ -13,7 +13,7 @@ <title value="Update category, check default URL key on the custom store view"/> <description value="Login as admin and update category and check default URL Key on custom store view"/> <testCaseId value="MC-6063"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="Catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml index b121ba46410e4..4eab0ca8f0694 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml @@ -13,7 +13,7 @@ <title value="Update category, make inactive"/> <description value="Login as admin and update category and make it Inactive"/> <testCaseId value="MC-6060"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="Catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml index 299298266d061..3c99e86dba2c9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryStoreUrlKeyTest.xml @@ -14,7 +14,7 @@ <stories value="Update SEO-friendly URL via the Admin"/> <title value="SEO-friendly URL should update regardless of scope or redirect change."/> <description value="SEO-friendly URL should update regardless of scope or redirect change."/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-92338"/> <group value="category"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml index c0c8f53307b20..6ecb7e09d5a2c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml @@ -13,7 +13,7 @@ <title value="Update category, URL key with custom store view"/> <description value="Login as admin and update category URL Key with store view"/> <testCaseId value="MC-6062"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="Catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml index 065ebb74785d4..6dde1567b68f8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml @@ -13,7 +13,7 @@ <title value="Update category, sort products by default sorting"/> <description value="Login as admin, update category and sort products"/> <testCaseId value="MC-6059"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="Catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml index 2ae3c67cb222d..2c356636df56c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml @@ -13,7 +13,7 @@ <title value="Flat Catalog - Update Category Name and Description"/> <description value="Login as admin and update flat category name and description"/> <testCaseId value="MC-11010"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="Catalog"/> <group value="mtf_migrated"/> <group value="WYSIWYGDisabled"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml index 270b95b7e52c5..d0935948e88bd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml @@ -14,7 +14,7 @@ <title value="Update Simple Product with Regular Price (In Stock), Disabled Product"/> <description value="Test log in to Update Simple Product and Update Simple Product with Regular Price (In Stock), Disabled Product"/> <testCaseId value="MC-10816"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml index 2490782d86b8b..423a7d23d7d4a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml @@ -14,7 +14,7 @@ <title value="Update Simple Product with Regular Price (In Stock) Visible in Catalog and Search"/> <description value="Test log in to Update Simple Product and Update Simple Product with Regular Price (In Stock) Visible in Catalog and Search"/> <testCaseId value="MC-10802"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml index 2f0ef84d4be0d..29a4c5e009d07 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml @@ -14,7 +14,7 @@ <title value="Update Simple Product with Regular Price (In Stock) Visible in Catalog Only"/> <description value="Test log in to Update Simple Product and Update Simple Product with Regular Price (In Stock) Visible in Catalog Only"/> <testCaseId value="MC-10804"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml index 4b13323afdc44..00b6a5def6169 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml @@ -14,7 +14,7 @@ <title value="Update Simple Product with Regular Price (In Stock) with Custom Options"/> <description value="Test log in to Update Simple Product and Update Simple Product with Regular Price (In Stock) with Custom Options"/> <testCaseId value="MC-10819"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml index df3f0529b1bd4..f499d87c84682 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml @@ -13,7 +13,7 @@ <title value="Update top category url and do not create redirect"/> <description value="Login as admin and update top category url and do not create redirect"/> <testCaseId value="MC-6056"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="Catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml index fddaced13fa4d..eabedebaeab83 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml @@ -13,7 +13,7 @@ <title value="Update top category url and create redirect"/> <description value="Login as admin and update top category url and create redirect"/> <testCaseId value="MC-6057"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="Catalog"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHint.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHint.xml index a4785e25d39bb..850c0e98c45f3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHint.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHint.xml @@ -14,7 +14,7 @@ <stories value="Customizable text option input-length validation hint changes dynamically"/> <title value="You should have a dynamic length validation hint when using text option has max char limit"/> <description value="You should have a dynamic length validation hint when using text option has max char limit"/> - <severity value="MINOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-92229"/> <group value="product"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml index d5dee9e462560..ac6c34a8cc1f1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml @@ -14,7 +14,7 @@ <stories value="Create Product Attributes"/> <title value="Admin should be able to create a TextField product attribute"/> <description value="Admin should be able to create a TextField product attribute"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10894"/> <group value="Catalog"/> <group value="mtf_migrated"/> @@ -70,7 +70,7 @@ <stories value="Create Product Attributes"/> <title value="Admin should be able to create a Date product attribute"/> <description value="Admin should be able to create a Date product attribute"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10895"/> <group value="Catalog"/> <skip> @@ -133,7 +133,7 @@ <stories value="Create Product Attributes"/> <title value="Admin should be able to create a Price product attribute"/> <description value="Admin should be able to create a Price product attribute"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10897"/> <group value="Catalog"/> <group value="mtf_migrated"/> @@ -187,7 +187,7 @@ <stories value="Create Product Attributes"/> <title value="Admin should be able to create a Dropdown product attribute"/> <description value="Admin should be able to create a Dropdown product attribute"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10896"/> <group value="Catalog"/> <group value="mtf_migrated"/> @@ -274,7 +274,7 @@ <stories value="Create Product Attributes"/> <title value="Admin should be able to create a Dropdown product attribute containing a single quote"/> <description value="Admin should be able to create a Dropdown product attribute containing a single quote"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10898"/> <group value="Catalog"/> <group value="mtf_migrated"/> @@ -343,7 +343,7 @@ <stories value="Create Product Attributes"/> <title value="Admin should be able to create a MultiSelect product attribute"/> <description value="Admin should be able to create a MultiSelect product attribute"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10888"/> <group value="Catalog"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml index 9e0dea7e06ded..da7165600d64d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml @@ -14,7 +14,7 @@ <stories value="Categories"/> <title value="Check that parent categories are showing products after enabling subcategories after fully reindex"/> <description value="Check that parent categories are showing products after enabling subcategories after fully reindex"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-97370"/> <useCaseId value="MAGETWO-96846"/> <group value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml index f2c5750a2b18e..783054c3ffb5a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml @@ -14,7 +14,7 @@ <stories value="Purchase a product with Custom Options of different types"/> <title value="You should be able to save a product with custom options assigned to a different website"/> <description value="Custom Options should not be split when saving the product after assigning to a different website"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-91436"/> <group value="product"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml index 41bacc69baca4..f5ac8b243979f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml @@ -14,7 +14,7 @@ <stories value="Create simple product with two custom options" /> <title value="Admin should be able to create simple product with two custom options"/> <description value="Admin should be able to create simple product with two custom options"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <testCaseId value="MC-248"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontFotoramaArrowsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontFotoramaArrowsTest.xml index 120fa30832075..68d847907a448 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontFotoramaArrowsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontFotoramaArrowsTest.xml @@ -13,7 +13,7 @@ <features value="Catalog"/> <title value="Storefront Fotorama arrows test"/> <description value="Check arrows next to the thumbs are not visible than there is room for all pictures."/> - <severity value="MINOR"/> + <severity value="BLOCKER"/> <group value="Catalog"/> </annotations> <before> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml index 1615f75395fed..64b8d10b75419 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest.xml @@ -14,7 +14,7 @@ <stories value="Create products"/> <title value="Product with double quote in name"/> <description value="Product with a double quote in the name should appear correctly on the storefront"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="product"/> <testCaseId value="MAGETWO-92384"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml index cd97d64227e83..40733b120f1e8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml @@ -14,7 +14,7 @@ <stories value="Create products"/> <title value="Product attribute is not visible on storefront if it is empty"/> <description value="Product attribute should not be visible on storefront if it is empty"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-91893"/> <group value="product"/> </annotations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml index 59f0b2f5dd76e..f28029d44b9e0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml @@ -14,7 +14,7 @@ <stories value="Special price"/> <title value="Check that special price displayed when 'default config' scope timezone does not match 'website' scope timezone"/> <description value="Check that special price displayed when 'default config' scope timezone does not match 'website' scope timezone"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-97508"/> <useCaseId value="MAGETWO-96847"/> <group value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml index 7ef1619319289..e4446abf6624e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml @@ -14,7 +14,7 @@ <stories value="Product Categories Indexer"/> <title value="Verify Category Product and Product Category partial reindex"/> <description value="Verify that Merchant Developer can use console commands to perform partial reindex for Category Products, Product Categories, and Catalog Search"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-11386"/> <group value="catalog"/> <group value="indexer"/> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml index d7d7da58c27fc..b2ce64fe651c0 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml @@ -13,7 +13,7 @@ <stories value="Apply catalog price rule"/> <title value="Admin should be able to apply the catalog rule by category"/> <description value="Admin should be able to apply the catalog rule by category"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-74"/> <group value="CatalogRule"/> </annotations> diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/RewriteStoreLevelUrlKeyOfChildCategoryTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/RewriteStoreLevelUrlKeyOfChildCategoryTest.xml index e176fba9fd189..d67ff6fe0bace 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/RewriteStoreLevelUrlKeyOfChildCategoryTest.xml +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/RewriteStoreLevelUrlKeyOfChildCategoryTest.xml @@ -12,7 +12,7 @@ <stories value="MAGETWO-91649: #13513: Magento ignore store-level url_key of child category in URL rewrite process for global scope"/> <description value="Rewriting Store-level URL key of child category"/> <features value="CatalogUrlRewrite"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-94934"/> <group value="CatalogUrlRewrite"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml index 600163501b556..4065b3691b250 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNewAddressTest.xml @@ -14,7 +14,7 @@ <stories value="OnePageCheckout within Offline Payment Methods"/> <title value="OnePageCheckout as customer using new address test"/> <description value="Checkout as customer using new address"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-14740"/> <group value="checkout"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml index 661957a1de980..571fb8c4cf3a5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml @@ -14,7 +14,7 @@ <stories value="OnePageCheckout within Offline Payment Methods"/> <title value="OnePageCheckout as customer using non default address test"/> <description value="Checkout as customer using non default address"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-14739"/> <group value="checkout"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml index 2b2316b20396e..ce686fc5974dd 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutWithAllProductTypesTest.xml @@ -14,7 +14,7 @@ <stories value="OnePageCheckout within Offline Payment Methods"/> <title value="OnePageCheckout with all product types test"/> <description value="Checkout with all product types"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-14742"/> <group value="checkout"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml index 450bfff27125a..9e5542838745a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartWithoutAnySelectedOptionTest.xml @@ -13,7 +13,7 @@ <title value="Add simple product with all types of custom options to cart without selecting any options"/> <description value="Add simple product with all types of custom options to cart without selecting any options"/> <testCaseId value="MC-14725"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml index d7b462d7f649b..ba26b44c11ba3 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml @@ -13,7 +13,7 @@ <stories value="Checkout Free Shipping Recalculation after Coupon Code Added"/> <description value="User should be able to do checkout free shipping recalculation after adding coupon code"/> <features value="Checkout"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-96537"/> <useCaseId value="MAGETWO-96431"/> <group value="Checkout"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml index 319183d4641e6..8081abcff307b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddProductWithAllTypesOfCustomOptionToTheShoppingCartTest.xml @@ -13,7 +13,7 @@ <title value="Create a simple products with all types of oprtions and add it to the cart"/> <description value="Create a simple products with all types of oprtions and add it to the cart"/> <testCaseId value="MC-14726"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml index 3a686c8efdd5f..08de5a9e6daa7 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest.xml @@ -14,7 +14,7 @@ <stories value="Checkout via Storefront"/> <title value="Customer Checkout via Storefront"/> <description value="Should be able to place an order as a customer."/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-30274"/> <group value="checkout"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml index 2b96d385487bc..c616faf716f03 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithNewCustomerRegistrationAndDisableGuestCheckoutTest.xml @@ -12,7 +12,7 @@ <stories value="Checkout"/> <title value="Verify customer checkout by following new customer registration when guest checkout is disabled"/> <description value="Customer is redirected to checkout on login, follow the new Customer registration when guest checkout is disabled"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-14704"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml index 53d7904ffdc38..28bdd1d536d43 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml @@ -14,7 +14,7 @@ <stories value="Checkout via Guest Checkout"/> <title value="Guest Checkout - guest should be able to place an order"/> <description value="Should be able to place an order as a Guest"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-12825"/> <group value="checkout"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml index c106ec9c552ff..391cfd254101a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontPersistentDataForGuestCustomerWithPhysicalQuoteTest.xml @@ -14,7 +14,7 @@ <stories value="Checkout via Guest Checkout"/> <title value="Persistent Data for Guest Customer with physical quote"/> <description value="One can use Persistent Data for Guest Customer with physical quote"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-13479"/> <group value="checkout"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml index 46c4abf4eab1a..6208b43bcb39b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml @@ -14,7 +14,7 @@ <stories value="Checkout via the Storefront"/> <title value="Update price in shopping cart after product save"/> <description value="Price in shopping cart should be updated after product save with changed price"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-58179"/> <group value="checkout"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml index d0d75317531b7..d166bfdac0ba4 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml @@ -15,7 +15,7 @@ <title value="Check updating shopping cart while updating items qty"/> <description value="Check updating shopping cart while updating items qty"/> <testCaseId value="MC-14731" /> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <group value="shoppingCart"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml index 0b52b08980ded..91a601dc6ef52 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml @@ -15,7 +15,7 @@ <title value="Check updating shopping cart while updating qty of items with custom options"/> <description value="Check updating shopping cart while updating qty of items with custom options"/> <testCaseId value="MC-14732" /> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <group value="shoppingCart"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml index f54547015eb9c..a577f9853ea1d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml @@ -15,7 +15,7 @@ <group value="Cms"/> <title value="Verify that admin is able to upload image to a CMS Page with TinyMCE3 enabled"/> <description value="Verify that admin is able to upload image to CMS Page with TinyMCE3 enabled"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-95725"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml index e22f6e085a32b..162c9a60fd6b1 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml @@ -14,7 +14,7 @@ <group value="Cms"/> <title value="Admin should be able to add image to WYSIWYG content of Block"/> <description value="Admin should be able to add image to WYSIWYG content of Block"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-84376"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml index 51afa7b59d366..0476ecf99ad36 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml @@ -14,7 +14,7 @@ <group value="Cms"/> <title value="Admin should be able to add image to WYSIWYG content of CMS Page"/> <description value="Admin should be able to add image to WYSIWYG content of CMS Page"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-85825"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml index e5aecd0f3da81..887fe88533f74 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGBlockTest.xml @@ -14,7 +14,7 @@ <group value="Cms"/> <title value="Admin should be able to add widget to WYSIWYG content of Block"/> <description value="Admin should be able to add widget to WYSIWYG content Block"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-84654"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml index 5f9bfaf47a157..450003db465a8 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml @@ -15,7 +15,7 @@ <group value="Cms"/> <title value="Admin should be able to create a CMS page with widget type: CMS page link"/> <description value="Admin should be able to create a CMS page with widget type: CMS page link"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-83781"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml index 81826eeab5e10..633dd4dbc3388 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml @@ -15,7 +15,7 @@ <group value="Cms"/> <title value="Admin should be able to create a CMS page with widget type: CMS Static Block"/> <description value="Admin should be able to create a CMS page with widget type: CMS Static Block"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-83787"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml index 5d745c625ac10..6b634282eaf37 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml @@ -14,7 +14,7 @@ <group value="Cms"/> <title value="Admin should be able to create a CMS page with widget type: Catalog category link"/> <description value="Admin should be able to create a CMS page with widget type: Catalog category link"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-83611"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml index 940c1979710e1..e8c2fc7f3cbf8 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml @@ -15,7 +15,7 @@ <group value="Cms"/> <title value="Admin should be able to create a CMS page with widget type: Catalog product link"/> <description value="Admin should be able to create a CMS page with widget type: Catalog product link"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-83788"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml index f849c31948c3c..2124206466c2d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml @@ -14,7 +14,7 @@ <group value="Cms"/> <title value="Admin should be able to create a CMS page with widget type: Catalog product list"/> <description value="Admin should be able to create a CMS page with widget type: Catalog product list"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-67091"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml index f4f1da8763fbb..85ae0380d4b43 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml @@ -15,7 +15,7 @@ <group value="Cms"/> <title value="Admin should be able to create a CMS page with widget type: Recently Compared Products"/> <description value="Admin should be able to create a CMS page with widget type: Recently Compared Products"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-83792"/> </annotations> <!--Main test--> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml index d9c080e034dd2..14182a4c33549 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml @@ -15,7 +15,7 @@ <group value="Cms"/> <title value="Admin should be able to create a CMS page with widget type: Recently Viewed Products"/> <description value="Admin should be able to create a CMS page with widget type: Recently Viewed Products"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-83789"/> </annotations> <before> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml index 385616dcca9b9..8d4326040c919 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml @@ -14,7 +14,7 @@ <stories value="MAGETWO-91559 - Static blocks with same ID appear in place of correct block"/> <title value="Check static blocks: ID should be unique per Store View"/> <description value="Check static blocks: ID should be unique per Store View"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-94229"/> <group value="Cms"/> </annotations> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml index 692ba32c6db28..99cd0cd4242a7 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml @@ -14,7 +14,7 @@ <stories value="Create, Read, Update, Delete"/> <title value="admin should be able to create a configurable product with attributes"/> <description value="admin should be able to create a configurable product with attributes"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-84"/> <group value="ConfigurableProduct"/> </annotations> @@ -76,7 +76,7 @@ <stories value="Create, Read, Update, Delete"/> <title value="admin should be able to create a configurable product after incorrect sku"/> <description value="admin should be able to create a configurable product after incorrect sku"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-96365"/> <useCaseId value="MAGETWO-94556"/> <group value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml index 0d945ebecf73a..1016a46ebc213 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductDeleteTest.xml @@ -16,7 +16,7 @@ <description value="admin should be able to delete a configurable product"/> <testCaseId value="MC-87"/> <group value="ConfigurableProduct"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> </annotations> <before> @@ -110,7 +110,7 @@ <description value="admin should be able to mass delete configurable products"/> <testCaseId value="MC-99"/> <group value="ConfigurableProduct"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> </annotations> <before> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml index fdc467728451a..e50a851589239 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml @@ -16,7 +16,7 @@ <description value="admin should be able to bulk update attributes of configurable products"/> <testCaseId value="MC-88"/> <group value="ConfigurableProduct"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> </annotations> <before> @@ -91,7 +91,7 @@ <description value="Admin should be able to remove a product configuration"/> <testCaseId value="MC-63"/> <group value="ConfigurableProduct"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> </annotations> <before> @@ -187,7 +187,7 @@ <description value="Admin should be able to disable a product configuration"/> <testCaseId value="MC-119"/> <group value="ConfigurableProduct"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> </annotations> <before> @@ -274,7 +274,7 @@ <stories value="Edit a configurable product in admin"/> <title value="Admin should be able to remove a configuration from a Configurable Product"/> <description value="Admin should be able to remove a configuration from a Configurable Product"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <testCaseId value="MC-86"/> <group value="ConfigurableProduct"/> </annotations> @@ -323,7 +323,7 @@ <stories value="Edit a configurable product in admin"/> <title value="Admin should be able to edit configuration to add a value to an existing attribute"/> <description value="Admin should be able to edit configuration to add a value to an existing attribute"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <testCaseId value="MC-95"/> <group value="ConfigurableProduct"/> </annotations> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml index 65db458e07a04..618881906c47d 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml @@ -14,7 +14,7 @@ <title value="Out of stock configurable attribute option doesn't show in Layered Navigation"/> <description value=" Login as admin and verify out of stock configurable attribute option doesn't show in Layered Navigation"/> <testCaseId value="MC-13734"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="ConfigurableProduct"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml index 5600b6088cfe5..c8072dcc0a299 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminAddNewDefaultBillingShippingCustomerAddressTest.xml @@ -13,7 +13,7 @@ <stories value="Add new default billing/shipping customer address"/> <title value="Add new default billing/shipping customer address"/> <description value="Add new default billing/shipping customer address on customer addresses tab"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-94814"/> <group value="customer"/> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml index 2021b589790cf..1cf73e97f91df 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml @@ -14,7 +14,7 @@ <stories value="Create a Customer via the Admin"/> <title value="Admin should be able to create a customer"/> <description value="Admin should be able to create a customer"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-72095"/> <group value="customer"/> <group value="create"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml index d4af9ab58a299..b21f2a795bde7 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerAddressesFromTheGridTest.xml @@ -12,7 +12,7 @@ <title value="Admin delete customer addresses from the grid"/> <description value="Admin delete customer addresses from the grid"/> <features value="Module/ Customer"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-94850"/> <stories value="MAGETWO-94346: Implement handling of large number of addresses on admin edit customer page"/> <group value="customer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml index b5ade97dbb968..14a89df5eb0a5 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml @@ -13,7 +13,7 @@ <stories value="Delete customer"/> <title value="DeleteCustomerBackendEntityTestVariation1"/> <description value="Login as admin and delete the customer"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-14587"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml index 54ea673f7249f..98a9414f29885 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteDefaultBillingCustomerAddressTest.xml @@ -12,7 +12,7 @@ <title value="Admin delete default billing customer address"/> <description value="Admin delete default billing customer address"/> <features value="Module/ Customer"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-94816"/> <stories value="MAGETWO-94346: Implement handling of large number of addresses on admin edit customer page"/> <group value="customer"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml index 4f69a9bbfb695..cf1ff53225603 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminEditDefaultBillingShippingCustomerAddressTest.xml @@ -13,7 +13,7 @@ <stories value="Edit default billing/shipping customer address"/> <title value="Edit default billing/shipping customer address"/> <description value="Edit default billing/shipping customer address on customer addresses tab"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-94815"/> <group value="customer"/> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest.xml index dc357976887d9..426015b75fdb9 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest.xml @@ -14,7 +14,7 @@ <stories value="Update Customer Information in Admin"/> <title value="Update Customer Info from Default to Non-Default in Admin"/> <description value="Update Customer Info from Default to Non-Default in Admin"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-13619"/> <group value="Customer"/> <group value="mtf_migrated"/> @@ -209,7 +209,7 @@ <stories value="Delete Customer Address in Admin"/> <title value="Delete Customer Address in Admin"/> <description value="Delete Customer Address in Admin"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-13623"/> <group value="Customer"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml index ac6612184e32c..e9b52fd64135b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml @@ -14,7 +14,7 @@ <stories value="Implement handling of large number of addresses on storefront Address book"/> <title value="Storefront - My account - Address Book - add new address"/> <description value="Storefront user should be able to create a new address via the storefront"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-97364"/> <group value="customer"/> <group value="create"/> @@ -53,7 +53,7 @@ <stories value="Implement handling of large number of addresses on storefront Address book"/> <title value="Storefront - My account - Address Book - add new default billing/shipping address"/> <description value="Storefront user should be able to create a new default address via the storefront"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-97364"/> <group value="customer"/> <group value="create"/> @@ -91,7 +91,7 @@ <stories value="Implement handling of large number of addresses on storefront Address book"/> <title value="Storefront - My account - Address Book - add new non default billing/shipping address"/> <description value="Storefront user should be able to create a new non default address via the storefront"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-97500"/> <group value="customer"/> <group value="create"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml index 0bc46e8717f33..0d64ceb545831 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCreateCustomerTest.xml @@ -14,7 +14,7 @@ <stories value="Create a Customer via the Storefront"/> <title value="As a Customer I should be able to register an account on Storefront"/> <description value="As a Customer I should be able to register an account on Storefront"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-23546"/> <group value="customer"/> <group value="create"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordSuccessTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordSuccessTest.xml index 7ea49f3684afc..63f372a080acc 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordSuccessTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordSuccessTest.xml @@ -14,7 +14,7 @@ <stories value="Customer Login"/> <title value="Forgot Password on Storefront validates customer email input"/> <description value="Forgot Password on Storefront validates customer email input"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-13679"/> <group value="Customer"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml index 2f6f4fb5e2dca..6a4ed633fd413 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml @@ -14,7 +14,7 @@ <title value="Update Customer Address (France) in Storefront"/> <description value="Test log in to Storefront and Update Customer Address (France) in Storefront"/> <testCaseId value="MC-10912"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="customer"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml index d51bc1dcc9b18..b3ad6cc96aae1 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml @@ -14,7 +14,7 @@ <title value="Update Customer Address (UK) in Storefront"/> <description value="Test log in to Storefront and Update Customer Address (UK) in Storefront"/> <testCaseId value="MC-10911"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="customer"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerPasswordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerPasswordTest.xml index 9bc253c91af92..58c13898de3ee 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerPasswordTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerPasswordTest.xml @@ -14,7 +14,7 @@ <stories value="Customer Update Password"/> <title value="Update Customer Password on Storefront, Valid Current Password"/> <description value="Update Customer Password on Storefront, Valid Current Password"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-10916"/> <group value="Customer"/> <group value="mtf_migrated"/> @@ -78,4 +78,4 @@ <remove keyForRemoval="loginWithNewPassword"/> <remove keyForRemoval="seeMyEmail"/> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml b/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml index 9fcc1909ab42c..e4430f6850660 100644 --- a/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml +++ b/app/code/Magento/Elasticsearch/Test/Mftf/Test/ProductQuickSearchUsingElasticSearchTest.xml @@ -14,7 +14,7 @@ <stories value="Quick Search of products on Storefront when ES 5.x is enabled"/> <title value="Product quick search doesn't throw exception after ES is chosen as search engine"/> <description value="Verify no elastic search exception is thrown when searching for product before catalogsearch reindexing"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-94995"/> <group value="Catalog"/> </annotations> diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml index 71e0401a1c30a..4f438237d073e 100644 --- a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml +++ b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticSearchForChineseLocaleTest.xml @@ -14,7 +14,7 @@ <stories value="Elasticsearch6 for Chinese"/> <title value="Elastic search for Chinese locale"/> <description value="Elastic search for Chinese locale"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-6310"/> <useCaseId value="MAGETWO-91625"/> <group value="elasticsearch"/> diff --git a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearch6SearchInvalidValueTest.xml b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearch6SearchInvalidValueTest.xml index 622b78fce01b9..49aef41d7f31c 100644 --- a/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearch6SearchInvalidValueTest.xml +++ b/app/code/Magento/Elasticsearch6/Test/Mftf/Test/StorefrontElasticsearch6SearchInvalidValueTest.xml @@ -14,7 +14,7 @@ <stories value="Search Product on Storefront"/> <title value="Elasticsearch: try to search by invalid value of 'Searchable' attribute"/> <description value="Elasticsearch: try to search by invalid value of 'Searchable' attribute"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-17906"/> <useCaseId value="MC-15759"/> <group value="elasticsearch"/> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml index 3eebb9def9c7a..0bd447905fb47 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml @@ -14,7 +14,7 @@ <stories value="Import Products"/> <features value="Import/Export"/> <title value="Verify Magento native import products with add/update behavior."/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-14077"/> <group value="importExport"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml index 9934ac2e0c8c2..800e8203d19ce 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml @@ -14,7 +14,7 @@ <stories value="Verify Magento native import products with delete behavior."/> <features value="Import/Export"/> <title value="Verify Magento native import products with delete behavior."/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-30587"/> <group value="importExport"/> </annotations> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml index 593282b9bb867..c5926428daaa7 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml @@ -14,7 +14,7 @@ <features value="Import/Export"/> <stories value="Product Import"/> <title value="Product import from CSV file correct from different files."/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-17104"/> <useCaseId value="MAGETWO-70803"/> <group value="importExport"/> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductVisibilityDifferentStoreViewsAfterImportTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductVisibilityDifferentStoreViewsAfterImportTest.xml index de3b52c3c3a98..e176052d7a280 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductVisibilityDifferentStoreViewsAfterImportTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductVisibilityDifferentStoreViewsAfterImportTest.xml @@ -14,7 +14,7 @@ <stories value="Import Products"/> <title value="Checking product visibility in different store views after product importing"/> <description value="Checking product visibility in different store views after product importing"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-6406"/> <useCaseId value="MAGETWO-59265"/> <group value="importExport"/> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminURLKeyWorksWhenUpdatingProductThroughImportingCSVTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminURLKeyWorksWhenUpdatingProductThroughImportingCSVTest.xml index 4d4e87f9387cc..b23e3703b5cfd 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminURLKeyWorksWhenUpdatingProductThroughImportingCSVTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminURLKeyWorksWhenUpdatingProductThroughImportingCSVTest.xml @@ -14,7 +14,7 @@ <stories value="Import Products"/> <title value="Check that new URL Key works after updating a product through importing CSV file"/> <description value="Check that new URL Key works after updating a product through importing CSV file"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-6317"/> <useCaseId value="MAGETWO-91544"/> <group value="importExport"/> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml index a054649c5365c..811af2baef56f 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml @@ -14,7 +14,7 @@ <stories value="Checking multi shipment with multiple shipment adresses on front end order page"/> <title value="Checking multi shipment with multiple shipment adresses on front end order page"/> <description value="Shipping price shows 0 when you return from multiple checkout to cart"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-18519"/> <group value="Multishipment"/> </annotations> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontProcessMultishippingCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontProcessMultishippingCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml index 81b536746616e..f37b45c639263 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontProcessMultishippingCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontProcessMultishippingCheckoutWhenCartPageIsOpenedInAnotherTabTest.xml @@ -14,7 +14,7 @@ <stories value="Multishipping"/> <title value="Process multishipping checkout when Cart page is opened in another tab"/> <description value="Process multishipping checkout when Cart page is opened in another tab"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MC-17871"/> <useCaseId value="MC-17469"/> <group value="multishipping"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml index 862831c09d1d7..dc9b74a8be36b 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/YoutubeVideoWindowOnProductPageTest.xml @@ -15,7 +15,7 @@ <testCaseId value="MAGETWO-95254"/> <title value="Youtube video window on the product page"/> <description value="Check Youtube video window on the product page"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <group value="ProductVideo"/> <skip> <issueId value="MC-32197"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml index 49ed69d76196a..1c6c2d1494e2c 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml @@ -14,7 +14,7 @@ <stories value="Add Products to Order from Shopping Cart"/> <title value="Add configurable product to order from shopping cart test"/> <description value="Add configurable product to order from shopping cart"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16008"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AddSimpleProductToOrderFromShoppingCartTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AddSimpleProductToOrderFromShoppingCartTest.xml index 1485613f4e4c2..e6bcbc3b08028 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AddSimpleProductToOrderFromShoppingCartTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AddSimpleProductToOrderFromShoppingCartTest.xml @@ -14,7 +14,7 @@ <stories value="Add Products to Order from Shopping Cart"/> <title value="Add simple product to order from shopping cart test"/> <description value="Add simple product to order from shopping cart"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16007"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml index d64af533b04e0..ee69d9fd1ca46 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml @@ -14,7 +14,7 @@ <stories value="Cancel Created Order"/> <title value="Cancel the created order with product quantity without stock decrease"/> <description value="Create an order with product quantity without stock decrease, cancel the order and verify product quantity in backend"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16071"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoPartialRefundTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoPartialRefundTest.xml index 2cacfe934427c..8114c04a2926e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoPartialRefundTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoPartialRefundTest.xml @@ -13,7 +13,7 @@ <stories value="Credit memo entity"/> <title value="Create Credit Memo for Offline Payment Methods"/> <description value="Assert items return to stock (partial refund)"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-15861"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml index 2d5b2d3c66906..0d19d10bd84d2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml @@ -13,7 +13,7 @@ <stories value="Credit memo entity"/> <title value="Create Credit Memo with purchase order payment method"/> <description value="Create Credit Memo with purchase order payment payment and assert 0 shipping refund"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-15864"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml index b687e63fbc328..72080c3170c48 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderForCustomerWithTwoAddressesTaxableAndNonTaxableTest.xml @@ -14,7 +14,7 @@ <description value="Tax should not be displayed for non taxable address when switching from taxable address"/> <testCaseId value="MC-21721"/> <features value="Sales"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <group value="Sales"/> </annotations> <before> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSelectedShoppingCartItemsTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSelectedShoppingCartItemsTest.xml index 60ade9ebe01e7..841db82adefb7 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSelectedShoppingCartItemsTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithSelectedShoppingCartItemsTest.xml @@ -13,7 +13,7 @@ <stories value="MC-17838: Shopping cart items added to the order created in the admin"/> <description value="Shopping cart items must not be added to the order unless they were moved manually"/> <features value="Sales"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <group value="Sales"/> </annotations> <before> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml index b9e2d475f9ff6..d28b636770856 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutEmailTest.xml @@ -13,7 +13,7 @@ <stories value="Create orders"/> <title value="Email is required to create an order from Admin Panel"/> <description value="Admin should not be able to submit orders without an email address"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-92980"/> <group value="sales"/> </annotations> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml index 6cfb2fa5ee911..73f3b5310731e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateInvoiceAndCheckInvoiceOrderTest.xml @@ -13,7 +13,7 @@ <stories value="Create Invoice for Offline Payment Methods"/> <title value="Create invoice and check invoice order test"/> <description value="Create invoice for offline payment methods and check invoice order on admin dashboard"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-15868"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml index 3bd6e9656ebc0..e8eea2019c941 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml @@ -14,7 +14,7 @@ <stories value="Create Order"/> <title value="Create order from edit customer page and add products to wish list and shopping cart"/> <description value="Create an order from edit customer page and add products to the wish list and shopping cart "/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16161"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml index e126e7eab0abc..1f655f319076b 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveConfigurableProductsInComparedOnOrderPageTest.xml @@ -14,7 +14,7 @@ <stories value="Add Products to Order from Products in Comparison List Section"/> <title value="Move configurable products in compared on order page test"/> <description value="Move configurable products in compared on order page test"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16104"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml index 90b18266b22c4..f9215ff5019ff 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml @@ -14,7 +14,7 @@ <stories value="Add Products to Order from Last Ordered Products Section"/> <title value="Move last ordered configurable product on order page test"/> <description value="Move last ordered configurable product on order page"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16155"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml index 5355dba260060..4596faedbbe6c 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml @@ -14,7 +14,7 @@ <stories value="Add Products to Order from Last Ordered Products Section"/> <title value="Move last ordered simple product on order page test"/> <description value="Move last ordered simple product on order page"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16154"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml index 16e44fbb8842f..4fafd3d163930 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml @@ -14,7 +14,7 @@ <stories value="Add Products to Order from Recently Viewed Products Section"/> <title value="Move recently viewed configurable product on order page test"/> <description value="Move recently viewed configurable product on order page"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16163"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml index 9790f03abed5a..9ee96e2b64678 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveSimpleProductsInComparedOnOrderPageTest.xml @@ -14,7 +14,7 @@ <stories value="Add Products to Order from Products in Comparison List Section"/> <title value="Move simple products in compared on order page test"/> <description value="Move simple products in compared on order page test"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16103"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml index 04dadd95f9f43..0a2f6f4aba4e2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontPrintOrderGuestTest.xml @@ -13,7 +13,7 @@ <stories value="Print Order"/> <title value="Print Order from Guest on Frontend"/> <description value="Print Order from Guest on Frontend"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16225"/> <group value="sales"/> <group value="mtf_migrated"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml index 60ece859dde96..96ba6208a7518 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml @@ -15,7 +15,7 @@ <title value="[Cart Price Rule] Auto generated coupon code considers 'Uses per Coupon' and 'Uses per Customer' options"/> <description value="[Cart Price Rule] Auto generated coupon code considers 'Uses per Coupon' and 'Uses per Customer' options"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-59323"/> <group value="salesRule"/> </annotations> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml index a32d42e26d15f..7d343cd6dafd8 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCartPriceRuleSubtotalTest.xml @@ -14,7 +14,7 @@ <stories value="Create cart price rule"/> <title value="Customer should only see cart price rule discount if condition subtotal equals or greater than"/> <description value="Customer should only see cart price rule discount if condition subtotal equals or greater than"/> - <severity value="AVERAGE"/> + <severity value="BLOCKER"/> <testCaseId value="MC-235"/> <group value="SalesRule"/> </annotations> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml index 39b3ca51327ba..1fe46c20a542d 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByImageSwatchTest.xml @@ -14,7 +14,7 @@ <stories value="View swatches in product listing"/> <title value="Customers can filter products using image swatches"/> <description value="Customers can filter products using image swatches"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-3461"/> <group value="Swatches"/> </annotations> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml index 1d1c5c9c4e683..820150b10d06a 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByTextSwatchTest.xml @@ -14,7 +14,7 @@ <stories value="View swatches in product listing"/> <title value="Customers can filter products using text swatches"/> <description value="Customers can filter products using text swatches"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-3462"/> <group value="Swatches"/> </annotations> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml index 0b6238d7d46be..76ec3658243bf 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontFilterByVisualSwatchTest.xml @@ -14,7 +14,7 @@ <stories value="View swatches in product listing"/> <title value="Customers can filter products using visual swatches"/> <description value="Customers can filter products using visual swatches "/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-3082"/> <group value="Swatches"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCategoryUrlRewriteAndAddPermanentRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCategoryUrlRewriteAndAddPermanentRedirectTest.xml index c70112c0953b3..676c7bd95bbdb 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCategoryUrlRewriteAndAddPermanentRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCategoryUrlRewriteAndAddPermanentRedirectTest.xml @@ -13,7 +13,7 @@ <title value="Create custom URL rewrite, permanent"/> <description value="Login as Admin and create custom UrlRewrite and add redirect type permenent"/> <testCaseId value="MC-5343"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddPermanentRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddPermanentRedirectTest.xml index b03912728a3d9..b7e0d3bb6b4dc 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddPermanentRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddPermanentRedirectTest.xml @@ -13,7 +13,7 @@ <title value="Create product URL rewrite, with permanent redirect"/> <description value="Login as admin, create product UrlRewrite and add Permanent redirect"/> <testCaseId value="MC-5341"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> <before> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddTemporaryRedirectTest.xml index 4b03d28d44867..ce17e0de10d56 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddTemporaryRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddTemporaryRedirectTest.xml @@ -13,7 +13,7 @@ <title value="Create product URL rewrite, with temporary redirect"/> <description value="Login as admin, create product UrlRewrite and add Temporary redirect"/> <testCaseId value="MC-5340"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddPermanentRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddPermanentRedirectTest.xml index a91da90581dda..b226925748f78 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddPermanentRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddPermanentRedirectTest.xml @@ -13,7 +13,7 @@ <title value="Update Category URL Rewrites, permanent"/> <description value="Login as Admin and update category UrlRewrite and add Permanent redirect type"/> <testCaseId value="MC-5357"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml index e49318af53639..3147fcc848b0b 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml @@ -13,7 +13,7 @@ <title value="Update Category URL Rewrites, Temporary redirect type"/> <description value="Login as Admin and update category UrlRewrite and add Temporary redirect type"/> <testCaseId value="MC-5356"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <group value="mtf_migrated"/> </annotations> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml index 98c85114631aa..32795649c9375 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml @@ -13,7 +13,7 @@ <stories value="Url-rewrites for product in anchor categories"/> <title value="Url-rewrites for product in anchor categories"/> <description value="For a product with category that has parent anchor categories, the rewrites is created when the category/product is saved."/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MC-16568"/> <group value="urlRewrite"/> </annotations> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/NewProductsListWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/NewProductsListWidgetTest.xml index e3e0b957cf550..0550c1cc2ca42 100644 --- a/app/code/Magento/Widget/Test/Mftf/Test/NewProductsListWidgetTest.xml +++ b/app/code/Magento/Widget/Test/Mftf/Test/NewProductsListWidgetTest.xml @@ -17,7 +17,7 @@ <stories value="New products list widget"/> <title value="Admin should be able to set products as new so that they show up in the Catalog New Products List Widget"/> <description value="Admin should be able to set products as new so that they show up in the Catalog New Products List Widget"/> - <severity value="MAJOR"/> + <severity value="BLOCKER"/> <group value="Widget"/> </annotations> diff --git a/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml b/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml index df9b724783372..d7be487382c56 100644 --- a/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml +++ b/app/code/Magento/Widget/Test/Mftf/Test/ProductsListWidgetTest.xml @@ -13,7 +13,7 @@ <stories value="Products list widget"/> <title value="Admin should be able to set Products List Widget"/> <description value="Admin should be able to set Products List Widget"/> - <severity value="CRITICAL"/> + <severity value="BLOCKER"/> <testCaseId value="MAGETWO-97041"/> <group value="Widget"/> <group value="WYSIWYGDisabled"/> From 5b71e66ceabc1f74aed0b3919167117942b19a16 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Thu, 19 Mar 2020 17:44:27 +0200 Subject: [PATCH 351/369] MC-32306: Errors while trying to update downloadable product after MC-29952 --- .../Import/Product/Type/DownloadableTest.php | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php b/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php index 5c084f4588e07..2c5b91486d347 100644 --- a/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php +++ b/app/code/Magento/DownloadableImportExport/Test/Unit/Model/Import/Product/Type/DownloadableTest.php @@ -553,7 +553,7 @@ public function isRowValidData() { return [ [ - [ + 'row_data' => [ 'sku' => 'downloadablesku1', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 1', @@ -563,13 +563,13 @@ public function isRowValidData() . 'downloads=unlimited, file=media/file_link.mp4,sortorder=1|group_title=Group Title, ' . 'title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', ], - 0, - true, - true, - true + 'row_num' => 0, + 'is_new_product' => true, + 'is_domain_valid' => true, + 'expected_result' => true ], [ - [ + 'row_data' => [ 'sku' => 'downloadablesku12', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 2', @@ -579,29 +579,29 @@ public function isRowValidData() . ' downloads=unlimited, file=media/file.mp4,sortorder=1|group_title=Group Title,' . ' title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', ], - 1, - true, - true, - true + 'row_num' => 1, + 'is_new_product' => true, + 'is_domain_valid' => true, + 'expected_result' => true ], [ - [ + 'row_data' => [ 'sku' => 'downloadablesku12', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 2', - 'downloadable_samples' => 'title=Title 1, file=media/file.mp4,sortorder=1|title=Title 2,' - . ' url=media/file2.mp4,sortorder=0', - 'downloadable_links' => 'title=Title 1, price=10, downloads=unlimited, file=media/file.mp4,' - . 'sortorder=1|group_title=Group Title, title=Title 2, price=10, downloads=unlimited,' - . ' url=media/file2.mp4,sortorder=0', + 'downloadable_samples' => 'group_title=Group Title Samples, title=Title 1, file=media/file.mp4' + .',sortorder=1|group_title=Group Title, title=Title 2, url=media/file2.mp4,sortorder=0', + 'downloadable_links' => 'group_title=Group Title Links, title=Title 1, price=10,' + .' downloads=unlimited, file=media/file.mp4,sortorder=1|group_title=Group Title,' + .' title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', ], - 3, - true, - true, - true + 'row_num' => 3, + 'is_new_product' => true, + 'is_domain_valid' => true, + 'expected_result' => true ], [ - [ + 'row_data' => [ 'sku' => 'downloadablesku12', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 2', @@ -611,66 +611,66 @@ public function isRowValidData() . 'sortorder=1|group_title=Group Title, title=Title 2, price=10, downloads=unlimited,' . ' url=media/file2.mp4,sortorder=0', ], - 4, - true, - true, - true + 'row_num' => 4, + 'is_new_product' => true, + 'is_domain_valid' => true, + 'expected_result' => true ], [ //empty group title samples - [ + 'row_data' => [ 'sku' => 'downloadablesku12', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 2', - 'downloadable_samples' => 'group_title=, title=Title 1, file=media/file.mp4,sortorder=1' - . '|group_title=, title=Title 2, url=media/file2.mp4,sortorder=0', + 'downloadable_samples' => 'group_title=Group Title Samples, title=Title 1, file=media/file.mp4' + .',sortorder=1|group_title=Group Title, title=Title 2, url=media/file2.mp4,sortorder=0', 'downloadable_links' => 'group_title=Group Title Links, title=Title 1, price=10,' - . ' downloads=unlimited, file=media/file_link.mp4,sortorder=1|group_title=Group Title,' - . ' title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', + .' downloads=unlimited, file=media/file.mp4,sortorder=1|group_title=Group Title,' + .' title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', ], - 5, - true, - true, - true + 'row_num' => 5, + 'is_new_product' => true, + 'is_domain_valid' => true, + 'expected_result' => true ], [ //empty group title links - [ + 'row_data' => [ 'sku' => 'downloadablesku12', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 2', - 'downloadable_samples' => 'group_title=Group Title Samples, title=Title 1, file=media/file.mp4,' - . 'sortorder=1|group_title=Group Title, title=Title 2, url=media/file2.mp4,sortorder=0', - 'downloadable_links' => 'group_title=, title=Title 1, price=10, downloads=unlimited, ' - . 'file=media/file_link.mp4,sortorder=1|group_title=, title=Title 2, price=10, ' - . 'downloads=unlimited, url=media/file2.mp4,sortorder=0', + 'downloadable_samples' => 'group_title=Group Title Samples, title=Title 1, file=media/file.mp4' + .',sortorder=1|group_title=Group Title, title=Title 2, url=media/file2.mp4,sortorder=0', + 'downloadable_links' => 'group_title=Group Title Links, title=Title 1, price=10,' + .' downloads=unlimited, file=media/file.mp4,sortorder=1|group_title=Group Title,' + .' title=Title 2, price=10, downloads=unlimited, url=media/file2.mp4,sortorder=0', ], - 6, - true, - true, - true + 'row_num' => 6, + 'is_new_product' => true, + 'is_domain_valid' => true, + 'expected_result' => true ], [ - [ + 'row_data' => [ 'sku' => 'downloadablesku12', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 2', ], - 2, - false, - true, - true + 'row_num' => 2, + 'is_new_product' => false, + 'is_domain_valid' => true, + 'expected_result' => true ], [ - [ + 'row_data' => [ 'sku' => 'downloadablesku12', 'product_type' => 'downloadable', 'name' => 'Downloadable Product 2', 'downloadable_samples' => '', 'downloadable_links' => '', ], - 7, - true, - true, - false + 'row_num' => 7, + 'is_new_product' => true, + 'is_domain_valid' => true, + 'expected_result' => false ], ]; } From ecd5c9b942a19485b965122b6c40b8c78b9df551 Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Thu, 19 Mar 2020 11:04:03 -0500 Subject: [PATCH 352/369] MC-13825: [2.4.x] Migrate ZF2 components to Laminas -- fix merge conflict --- .../HTTP/PhpEnvironment/Response.php | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php index d1168d746ba68..2816d1f2c4848 100644 --- a/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php +++ b/lib/internal/Magento/Framework/HTTP/PhpEnvironment/Response.php @@ -19,11 +19,6 @@ class Response extends \Laminas\Http\PhpEnvironment\Response implements \Magento */ protected $isRedirect = false; - /** - * @var bool - */ - private $headersSent; - /** * @inheritdoc */ @@ -192,27 +187,4 @@ public function __sleep() { return ['content', 'isRedirect', 'statusCode']; } - - /** - * Sending provided headers. - * - * Had to be overridden because the original did not work correctly with multi-headers. - */ - public function sendHeaders() - { - if ($this->headersSent()) { - return $this; - } - - $status = $this->renderStatusLine(); - header($status); - - /** @var \Laminas\Http\Header\HeaderInterface $header */ - foreach ($this->getHeaders() as $header) { - header($header->toString(), false); - } - - $this->headersSent = true; - return $this; - } } From 2c8d8878c7509ef1faca6b4aa84b2d5a06a56e86 Mon Sep 17 00:00:00 2001 From: Oleh Usik <o.usik@atwix.com> Date: Thu, 19 Mar 2020 18:16:45 +0200 Subject: [PATCH 353/369] added xml declaration for one more file --- .../frontend/layout/catalog_widget_product_list.xml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml b/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml index ce31f588c6c8c..571155185693a 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml @@ -1,8 +1,10 @@ +<?xml version="1.0"?> <!-- - ~ Copyright © Magento, Inc. All rights reserved. - ~ See COPYING.txt for license details. - --> - +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> From d6d666bde98af324f1f8d4a087b8f960f1177420 Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Thu, 19 Mar 2020 15:11:14 -0500 Subject: [PATCH 354/369] MC-32378: Set up ElasticSearch so that an "AND" query is performed when searching --- .../FieldMapper/AddDefaultSearchField.php | 2 +- .../CopySearchableFieldsToSearchField.php | 2 +- app/code/Magento/Elasticsearch/i18n/en_US.csv | 2 +- .../Elasticsearch7/etc/adminhtml/system.xml | 2 +- .../Controller/QuickSearchTest.php | 31 +++++ .../_files/products_for_search.php | 108 ++++++++++++++++++ .../_files/products_for_search_rollback.php | 36 ++++++ 7 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch/Controller/QuickSearchTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search.php create mode 100644 dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search_rollback.php diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/AddDefaultSearchField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/AddDefaultSearchField.php index b503dbbc91678..472a0e490d024 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/AddDefaultSearchField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/AddDefaultSearchField.php @@ -21,7 +21,7 @@ class AddDefaultSearchField implements FieldsMappingPreprocessorInterface /** * Add default search field (catch all field) to the fields. * - * Emulates catch all field (_all) for elasticsearch version 6.0+ + * Emulates catch all field (_all) for elasticsearch * * @param array $mapping * @return array diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php index 248adc9ed4e1f..fc653ec953dc7 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/CopySearchableFieldsToSearchField.php @@ -21,7 +21,7 @@ class CopySearchableFieldsToSearchField implements FieldsMappingPreprocessorInte /** * Add "copy_to" parameter for default search field to index fields. * - * Emulates catch all field (_all) for elasticsearch version 6.0+ + * Emulates catch all field (_all) for elasticsearch * * @param array $mapping * @return array diff --git a/app/code/Magento/Elasticsearch/i18n/en_US.csv b/app/code/Magento/Elasticsearch/i18n/en_US.csv index 1c49a7f97c5b5..3a0ec556dbf8d 100644 --- a/app/code/Magento/Elasticsearch/i18n/en_US.csv +++ b/app/code/Magento/Elasticsearch/i18n/en_US.csv @@ -10,4 +10,4 @@ "Elasticsearch HTTP Password","Elasticsearch HTTP Password" "Elasticsearch Server Timeout","Elasticsearch Server Timeout" "Test Connection","Test Connection" -"Minimum terms to match","Minimum terms to match" +"Minimum Terms to Match","Minimum Terms to Match" diff --git a/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml index 1703f1af47ee5..8a6f991eaa5a0 100644 --- a/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml +++ b/app/code/Magento/Elasticsearch7/etc/adminhtml/system.xml @@ -79,7 +79,7 @@ <field id="engine">elasticsearch7</field> </depends> </field> - <field id="elasticsearch7_minimum_should_match" translate="label" type="text" sortOrder="93" showInDefault="1"> + <field id="elasticsearch7_minimum_should_match" translate="label" type="text" sortOrder="89" showInDefault="1"> <label>Minimum Terms to Match</label> <depends> <field id="engine">elasticsearch7</field> diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/Controller/QuickSearchTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/Controller/QuickSearchTest.php new file mode 100644 index 0000000000000..4bbbf396d0e4d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/Controller/QuickSearchTest.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\Controller; + +use Magento\TestFramework\TestCase\AbstractController; + +class QuickSearchTest extends AbstractController +{ + /** + * Tests quick search with "Minimum Terms to Match" sets to "100%". + * + * @magentoAppArea frontend + * @magentoDbIsolation disabled + * @magentoConfigFixture current_store catalog/search/elasticsearch7_minimum_should_match 100% + * @magentoConfigFixture current_store catalog/search/elasticsearch6_minimum_should_match 100% + * @magentoDataFixture Magento/Elasticsearch/_files/products_for_search.php + * @magentoDataFixture Magento/CatalogSearch/_files/full_reindex.php + */ + public function testQuickSearchWithImprovedPriceRangeCalculation() + { + $this->dispatch('/catalogsearch/result/?q=24+MB04'); + $responseBody = $this->getResponse()->getBody(); + $this->assertContains('search product 2', $responseBody); + $this->assertNotContains('search product 1', $responseBody); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search.php new file mode 100644 index 0000000000000..8792a1060ebf2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search.php @@ -0,0 +1,108 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +include __DIR__ . '/../../Catalog/_files/category.php'; + +use Magento\Catalog\Api\CategoryLinkManagementInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; + +$products = [ + [ + 'type' => 'simple', + 'id' => 201, + 'name' => 'search product 1', + 'sku' => '24 MB06', + 'status' => Status::STATUS_ENABLED, + 'visibility' => Visibility::VISIBILITY_BOTH, + 'attribute_set' => 4, + 'website_ids' => [1], + 'price' => 10, + 'category_id' => 333, + 'meta_title' => 'Key Title', + 'meta_keyword' => 'meta keyword', + 'meta_description' => 'meta description', + ], + [ + 'type' => 'simple', + 'id' => 202, + 'name' => 'search product 2', + 'sku' => '24 MB04', + 'status' => Status::STATUS_ENABLED, + 'visibility' => Visibility::VISIBILITY_BOTH, + 'attribute_set' => 4, + 'website_ids' => [1], + 'price' => 10, + 'category_id' => 333, + 'meta_title' => 'Last Title', + 'meta_keyword' => 'meta keyword', + 'meta_description' => 'meta description', + ], + [ + 'type' => 'simple', + 'id' => 203, + 'name' => 'search product 3', + 'sku' => '24 MB02', + 'status' => Status::STATUS_ENABLED, + 'visibility' => Visibility::VISIBILITY_BOTH, + 'attribute_set' => 4, + 'website_ids' => [1], + 'price' => 20, + 'category_id' => 333, + 'meta_title' => 'First Title', + 'meta_keyword' => 'meta keyword', + 'meta_description' => 'meta description', + ], + [ + 'type' => 'simple', + 'id' => 204, + 'name' => 'search product 4', + 'sku' => '24 MB01', + 'status' => Status::STATUS_ENABLED, + 'visibility' => Visibility::VISIBILITY_BOTH, + 'attribute_set' => 4, + 'website_ids' => [1], + 'price' => 30, + 'category_id' => 333, + 'meta_title' => 'A title', + 'meta_keyword' => 'meta keyword', + 'meta_description' => 'meta description', + ], +]; + +/** @var CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(CategoryLinkManagementInterface::class); + +$categoriesToAssign = []; + +foreach ($products as $data) { + /** @var $product Product */ + $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(Product::class); + $product + ->setTypeId($data['type']) + ->setId($data['id']) + ->setAttributeSetId($data['attribute_set']) + ->setWebsiteIds($data['website_ids']) + ->setName($data['name']) + ->setSku($data['sku']) + ->setPrice($data['price']) + ->setMetaTitle($data['meta_title']) + ->setMetaKeyword($data['meta_keyword']) + ->setMetaDescription($data['meta_keyword']) + ->setVisibility($data['visibility']) + ->setStatus($data['status']) + ->setStockData(['use_config_manage_stock' => 0]) + ->save(); + + $categoriesToAssign[$data['sku']][] = $data['category_id']; +} + +foreach ($categoriesToAssign as $sku => $categoryIds) { + $categoryLinkManagement->assignProductToCategories($sku, $categoryIds); +} diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search_rollback.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search_rollback.php new file mode 100644 index 0000000000000..b0cfcd7a3e5e3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search_rollback.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + +$productSkus = ['24 MB06', '24 MB04', '24 MB02', '24 MB01']; +foreach ($productSkus as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + $productRepository->delete($product); + } catch (NoSuchEntityException $e) { + } +} + +include dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); From a7001ce3a4612d9090d07dd705bff098def2d66c Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 20 Mar 2020 09:49:18 +0200 Subject: [PATCH 355/369] MC-32593: var/resource_config.json is regenerated each time an image is requested by get.php --- app/code/Magento/MediaStorage/App/Media.php | 40 ++++++--- .../MediaStorage/Test/Unit/App/MediaTest.php | 84 +++++++++++-------- 2 files changed, 81 insertions(+), 43 deletions(-) diff --git a/app/code/Magento/MediaStorage/App/Media.php b/app/code/Magento/MediaStorage/App/Media.php index 15bf7bb62e970..f4a2125149c1d 100644 --- a/app/code/Magento/MediaStorage/App/Media.php +++ b/app/code/Magento/MediaStorage/App/Media.php @@ -20,6 +20,7 @@ use Magento\Framework\AppInterface; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Filesystem\Driver\File; use Magento\MediaStorage\Model\File\Storage\Config; use Magento\MediaStorage\Model\File\Storage\ConfigFactory; use Magento\MediaStorage\Model\File\Storage\Response; @@ -27,8 +28,9 @@ use Magento\MediaStorage\Model\File\Storage\SynchronizationFactory; use Magento\MediaStorage\Service\ImageResize; + /** - * Media Storage + * The class resize original images * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -70,7 +72,12 @@ class Media implements AppInterface /** * @var WriteInterface */ - private $directory; + private $directoryPub; + + /** + * @var \Magento\Framework\Filesystem\Directory\WriteInterface + */ + private $directoryMedia; /** * @var ConfigFactory @@ -109,6 +116,7 @@ class Media implements AppInterface * @param PlaceholderFactory $placeholderFactory * @param State $state * @param ImageResize $imageResize + * @param File $file * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -122,15 +130,17 @@ public function __construct( Filesystem $filesystem, PlaceholderFactory $placeholderFactory, State $state, - ImageResize $imageResize + ImageResize $imageResize, + File $file ) { $this->response = $response; $this->isAllowed = $isAllowed; - $this->directory = $filesystem->getDirectoryWrite(DirectoryList::PUB); + $this->directoryPub = $filesystem->getDirectoryWrite(DirectoryList::PUB); + $this->directoryMedia = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); $mediaDirectory = trim($mediaDirectory); if (!empty($mediaDirectory)) { // phpcs:ignore Magento2.Functions.DiscouragedFunction - $this->mediaDirectoryPath = str_replace('\\', '/', realpath($mediaDirectory)); + $this->mediaDirectoryPath = str_replace('\\', '/', $file->getRealPath($mediaDirectory)); } $this->configCacheFile = $configCacheFile; $this->relativeFileName = $relativeFileName; @@ -151,7 +161,7 @@ public function launch(): ResponseInterface { $this->appState->setAreaCode(Area::AREA_GLOBAL); - if ($this->mediaDirectoryPath !== $this->directory->getAbsolutePath()) { + if ($this->checkMediaDirectoryChanged()) { // Path to media directory changed or absent - update the config /** @var Config $config */ $config = $this->configFactory->create(['cacheFile' => $this->configCacheFile]); @@ -166,11 +176,11 @@ public function launch(): ResponseInterface try { /** @var Synchronization $sync */ - $sync = $this->syncFactory->create(['directory' => $this->directory]); + $sync = $this->syncFactory->create(['directory' => $this->directoryPub]); $sync->synchronize($this->relativeFileName); $this->imageResize->resizeFromImageName($this->getOriginalImage($this->relativeFileName)); - if ($this->directory->isReadable($this->relativeFileName)) { - $this->response->setFilePath($this->directory->getAbsolutePath($this->relativeFileName)); + if ($this->directoryPub->isReadable($this->relativeFileName)) { + $this->response->setFilePath($this->directoryPub->getAbsolutePath($this->relativeFileName)); } else { $this->setPlaceholderImage(); } @@ -182,7 +192,17 @@ public function launch(): ResponseInterface } /** - * Set Placeholder as a response + * Check if media directory changed + * + * @return bool + */ + private function checkMediaDirectoryChanged(): bool + { + return rtrim($this->mediaDirectoryPath, '/') !== rtrim($this->directoryMedia->getAbsolutePath(), '/'); + } + + /** + * Set placeholder image into response * * @return void */ diff --git a/app/code/Magento/MediaStorage/Test/Unit/App/MediaTest.php b/app/code/Magento/MediaStorage/Test/Unit/App/MediaTest.php index 8d3211684d377..13ff664bf8064 100644 --- a/app/code/Magento/MediaStorage/Test/Unit/App/MediaTest.php +++ b/app/code/Magento/MediaStorage/Test/Unit/App/MediaTest.php @@ -15,6 +15,7 @@ use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\Read; use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Filesystem\DriverPool; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\MediaStorage\App\Media; use Magento\MediaStorage\Model\File\Storage\Config; @@ -74,7 +75,12 @@ class MediaTest extends TestCase /** * @var Read|MockObject */ - private $directoryMock; + private $directoryMediaMock; + + /** + * @var \Magento\Framework\Filesystem\Directory\Read|\PHPUnit_Framework_MockObject_MockObject + */ + private $directoryPubMock; protected function setUp() { @@ -96,12 +102,30 @@ protected function setUp() ->will($this->returnValue($this->sync)); $this->filesystemMock = $this->createMock(Filesystem::class); - $this->directoryMock = $this->getMockForAbstractClass(WriteInterface::class); - + $this->directoryPubMock = $this->getMockForAbstractClass( + WriteInterface::class, + [], + '', + false, + true, + true, + ['isReadable', 'getAbsolutePath'] + ); + $this->directoryMediaMock = $this->getMockForAbstractClass( + WriteInterface::class, + [], + '', + false, + true, + true, + ['getAbsolutePath'] + ); $this->filesystemMock->expects($this->any()) ->method('getDirectoryWrite') - ->with(DirectoryList::PUB) - ->will($this->returnValue($this->directoryMock)); + ->willReturnMap([ + [DirectoryList::PUB, DriverPool::FILE, $this->directoryPubMock], + [DirectoryList::MEDIA, DriverPool::FILE, $this->directoryMediaMock], + ]); $this->responseMock = $this->createMock(Response::class); } @@ -116,17 +140,17 @@ public function testProcessRequestCreatesConfigFileMediaDirectoryIsNotProvided() $this->mediaModel = $this->getMediaModel(); $filePath = '/absolute/path/to/test/file.png'; - $this->directoryMock->expects($this->any()) + $this->directoryMediaMock->expects($this->once()) + ->method('getAbsolutePath') + ->with(null) + ->will($this->returnValue(self::MEDIA_DIRECTORY)); + $this->directoryPubMock->expects($this->once()) ->method('getAbsolutePath') - ->will($this->returnValueMap( - [ - [null, self::MEDIA_DIRECTORY], - [self::RELATIVE_FILE_PATH, $filePath], - ] - )); + ->with(self::RELATIVE_FILE_PATH) + ->will($this->returnValue($filePath)); $this->configMock->expects($this->once())->method('save'); $this->sync->expects($this->once())->method('synchronize')->with(self::RELATIVE_FILE_PATH); - $this->directoryMock->expects($this->once()) + $this->directoryPubMock->expects($this->once()) ->method('isReadable') ->with(self::RELATIVE_FILE_PATH) ->will($this->returnValue(true)); @@ -140,18 +164,18 @@ public function testProcessRequestReturnsFileIfItsProperlySynchronized() $filePath = '/absolute/path/to/test/file.png'; $this->sync->expects($this->once())->method('synchronize')->with(self::RELATIVE_FILE_PATH); - $this->directoryMock->expects($this->once()) + $this->directoryMediaMock->expects($this->once()) + ->method('getAbsolutePath') + ->with(null) + ->will($this->returnValue(self::MEDIA_DIRECTORY)); + $this->directoryPubMock->expects($this->once()) ->method('isReadable') ->with(self::RELATIVE_FILE_PATH) ->will($this->returnValue(true)); - $this->directoryMock->expects($this->any()) + $this->directoryPubMock->expects($this->once()) ->method('getAbsolutePath') - ->will($this->returnValueMap( - [ - [null, self::MEDIA_DIRECTORY], - [self::RELATIVE_FILE_PATH, $filePath], - ] - )); + ->with(self::RELATIVE_FILE_PATH) + ->will($this->returnValue($filePath)); $this->responseMock->expects($this->once())->method('setFilePath')->with($filePath); $this->assertSame($this->responseMock, $this->mediaModel->launch()); } @@ -161,11 +185,11 @@ public function testProcessRequestReturnsNotFoundIfFileIsNotSynchronized() $this->mediaModel = $this->getMediaModel(); $this->sync->expects($this->once())->method('synchronize')->with(self::RELATIVE_FILE_PATH); - $this->directoryMock->expects($this->once()) + $this->directoryMediaMock->expects($this->once()) ->method('getAbsolutePath') - ->with() + ->with(null) ->will($this->returnValue(self::MEDIA_DIRECTORY)); - $this->directoryMock->expects($this->once()) + $this->directoryPubMock->expects($this->once()) ->method('isReadable') ->with(self::RELATIVE_FILE_PATH) ->will($this->returnValue(false)); @@ -205,16 +229,10 @@ public function testCatchException($isDeveloper, $setBodyCalls) public function testExceptionWhenIsAllowedReturnsFalse() { $this->mediaModel = $this->getMediaModel(false); - - $filePath = '/absolute/path/to/test/file.png'; - $this->directoryMock->expects($this->any()) + $this->directoryMediaMock->expects($this->once()) ->method('getAbsolutePath') - ->will($this->returnValueMap( - [ - [null, self::MEDIA_DIRECTORY], - [self::RELATIVE_FILE_PATH, $filePath], - ] - )); + ->with(null) + ->will($this->returnValue(self::MEDIA_DIRECTORY)); $this->configMock->expects($this->once())->method('save'); $this->expectException(LogicException::class); From fc034eb473f654b066d8d416de387d8207ea65b8 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 20 Mar 2020 09:51:35 +0200 Subject: [PATCH 356/369] MC-32593: var/resource_config.json is regenerated each time an image is requested by get.php --- app/code/Magento/MediaStorage/App/Media.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/MediaStorage/App/Media.php b/app/code/Magento/MediaStorage/App/Media.php index f4a2125149c1d..ca5ff458c52e9 100644 --- a/app/code/Magento/MediaStorage/App/Media.php +++ b/app/code/Magento/MediaStorage/App/Media.php @@ -28,7 +28,6 @@ use Magento\MediaStorage\Model\File\Storage\SynchronizationFactory; use Magento\MediaStorage\Service\ImageResize; - /** * The class resize original images * From f0146faa05fcf3eae59fabf266ff3fe55527d60f Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Fri, 20 Mar 2020 11:09:44 +0200 Subject: [PATCH 357/369] MC-32578: [MFTF] StoreFrontRecentlyViewedAtStoreViewLevelTest fails because of bad design --- .../Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml index f8ee9e562a6a9..0e09635489d9c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml @@ -12,9 +12,10 @@ <stories value="Recently Viewed Product"/> <title value="Recently Viewed Product at store view level"/> <description value="Recently Viewed Product should not be displayed on second store view, if configured as, Per Store View "/> - <testCaseId value="MC-31877"/> + <testCaseId value="MC-32112"/> <severity value="CRITICAL"/> <group value="catalog"/> + <group value="WYSIWYGDisabled"/> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> From a102e6f81fd397afbf9f6f1ca53e39a4024a343b Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Fri, 20 Mar 2020 12:50:44 +0200 Subject: [PATCH 358/369] MC-24238: [MFTF Test] Using Instant Purchase --- .../Test/Mftf/Data/BraintreeData.xml | 16 +- ...antPurchaseConfirmationDataActionGroup.xml | 25 +++ .../StorefrontInstantPurchasePopupSection.xml | 3 + ...efrontInstantPurchaseFunctionalityTest.xml | 173 ++++++++++++++++++ .../Mftf/Section/ModalConfirmationSection.xml | 2 +- .../Page/StorefrontOnePageCheckoutPage.xml | 14 ++ .../StorefrontOnePageCheckoutSection.xml | 14 ++ 7 files changed, 243 insertions(+), 4 deletions(-) create mode 100644 app/code/Magento/InstantPurchase/Test/Mftf/ActionGroup/AssertStorefrontInstantPurchaseConfirmationDataActionGroup.xml create mode 100644 app/code/Magento/InstantPurchase/Test/Mftf/Test/StorefrontInstantPurchaseFunctionalityTest.xml create mode 100644 app/code/Magento/Vault/Test/Mftf/Page/StorefrontOnePageCheckoutPage.xml create mode 100644 app/code/Magento/Vault/Test/Mftf/Section/StorefrontOnePageCheckoutSection.xml diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml index f95ba2eb590dc..cba402f8f43fc 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml @@ -53,13 +53,13 @@ <data key="value">sandbox</data> </entity> <entity name="MerchantId" type="merchant_id"> - <data key="value">MERCH_ID</data> + <data key="value">{{_CREDS.magento/braintree_enabled_fraud_merchant_id}}</data> </entity> <entity name="PublicKey" type="public_key"> - <data key="value">PUBLIC_KEY</data> + <data key="value">{{_CREDS.magento/braintree_enabled_fraud_public_key}}</data> </entity> <entity name="PrivateKey" type="private_key"> - <data key="value">PRIVATE_KEY</data> + <data key="value">{{_CREDS.magento/braintree_enabled_fraud_private_key}}</data> </entity> <!-- default configuration used to restore Magento config --> @@ -141,6 +141,16 @@ <data key="cardNumberEnding">5100</data> <data key="cardExpire">12/2020</data> </entity> + <entity name="VisaDefaultCard" type="data"> + <data key="cardNumber">4111111111111111</data> + <data key="month">01</data> + <data key="year">30</data> + <data key="cvv">123</data> + </entity> + <entity name="VisaDefaultCardInfo"> + <data key="cardNumberEnding">1111</data> + <data key="cardExpire">01/2030</data> + </entity> <entity name="BraintreeConfigurationData" type="data"> <data key="title">Credit Card (Braintree)</data> diff --git a/app/code/Magento/InstantPurchase/Test/Mftf/ActionGroup/AssertStorefrontInstantPurchaseConfirmationDataActionGroup.xml b/app/code/Magento/InstantPurchase/Test/Mftf/ActionGroup/AssertStorefrontInstantPurchaseConfirmationDataActionGroup.xml new file mode 100644 index 0000000000000..0245ba309e3b2 --- /dev/null +++ b/app/code/Magento/InstantPurchase/Test/Mftf/ActionGroup/AssertStorefrontInstantPurchaseConfirmationDataActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontInstantPurchaseConfirmationDataActionGroup"> + <annotations> + <description>Click on "Instant Purchase" button and assert shipping and billing information</description> + </annotations> + <arguments> + <argument name="shippingStreet" type="string" defaultValue="{{US_Address_TX.street[0]}}"/> + <argument name="billingStreet" type="string" defaultValue="{{US_Address_TX_Default_Billing.street[0]}}"/> + <argument name="cardEnding" type="string" defaultValue="{{StoredPaymentMethods.cardNumberEnding}}"/> + </arguments> + <click selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="clickInstantPurchaseButton"/> + <waitForElementVisible selector="{{ModalConfirmationSection.OkButton}}" stepKey="waitForButtonAppears"/> + <seeElement selector="{{StorefrontInstantPurchasePopupSection.shippingAddress(shippingStreet)}}" stepKey="assertShippingAddress"/> + <seeElement selector="{{StorefrontInstantPurchasePopupSection.billingAddress(billingStreet)}}" stepKey="assertBillingAddress"/> + <seeElement selector="{{StorefrontInstantPurchasePopupSection.paymentMethod(cardEnding)}}" stepKey="assertCardEnding"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/InstantPurchase/Test/Mftf/Section/StorefrontInstantPurchasePopupSection.xml b/app/code/Magento/InstantPurchase/Test/Mftf/Section/StorefrontInstantPurchasePopupSection.xml index 8e93d049ea141..2336868086a69 100644 --- a/app/code/Magento/InstantPurchase/Test/Mftf/Section/StorefrontInstantPurchasePopupSection.xml +++ b/app/code/Magento/InstantPurchase/Test/Mftf/Section/StorefrontInstantPurchasePopupSection.xml @@ -11,5 +11,8 @@ <section name="StorefrontInstantPurchasePopupSection"> <element name="modalTitle" type="text" selector=".modal-popup .modal-title"/> <element name="cancel" type="button" selector=".modal-popup.confirm button.action-dismiss"/> + <element name="shippingAddress" type="text" selector="//aside[contains(@class, 'modal-popup')]//strong[contains(text(),'Shipping Address:')]/following-sibling::p[contains(text(),'{{Data}}')][1]" parameterized="true"/> + <element name="billingAddress" type="text" selector="//aside[contains(@class, 'modal-popup')]//strong[contains(text(),'Billing Address:')]/following-sibling::p[contains(text(),'{{Data}}')]" parameterized="true"/> + <element name="paymentMethod" type="text" selector="//aside[contains(@class, 'modal-popup')]//strong[contains(text(),'Payment Method:')]/following-sibling::p[contains(text(),'{{Data}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/InstantPurchase/Test/Mftf/Test/StorefrontInstantPurchaseFunctionalityTest.xml b/app/code/Magento/InstantPurchase/Test/Mftf/Test/StorefrontInstantPurchaseFunctionalityTest.xml new file mode 100644 index 0000000000000..b0235fe32ca27 --- /dev/null +++ b/app/code/Magento/InstantPurchase/Test/Mftf/Test/StorefrontInstantPurchaseFunctionalityTest.xml @@ -0,0 +1,173 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontInstantPurchaseFunctionalityTest"> + <annotations> + <features value="InstantPurchase"/> + <stories value="Using Instant Purchase"/> + <title value="Checks that Instant Purchase functionality works fine"/> + <description value="Checks that customer with different billing and shipping addresses work with Instant Purchase functionality fine"/> + <useCaseId value="MAGETWO-90898"/> + <testCaseId value="MC-25924"/> + <severity value="CRITICAL"/> + <group value="instant_purchase"/> + <group value="vault"/> + <group value="braintree"/> + </annotations> + <before> + <magentoCLI command="downloadable:domains:add" arguments="example.com static.magento.com" stepKey="addDownloadableDomain"/> + <!-- Configure Braintree payment method --> + <createData entity="BraintreeConfig" stepKey="configureBraintreePayment"/> + <!-- Enable Braintree with Vault --> + <createData entity="CustomBraintreeConfigurationData" stepKey="enableBraintreeAndVault"/> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + <!-- Create all product variations --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"/> + <actionGroup ref="AdminCreateApiConfigurableProductActionGroup" stepKey="createConfigurableProduct"/> + <!-- Create Bundle Product --> + <createData entity="ApiFixedBundleProduct" stepKey="createBundleProduct"/> + <createData entity="DropDownBundleOption" stepKey="createBundleOption"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + <!-- Create Downloadable Product --> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="downloadableLink1" stepKey="addDownloadableLink"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <!-- Create Grouped Product --> + <createData entity="ApiGroupedProduct" stepKey="createGroupedProduct"/> + <createData entity="OneSimpleProductLink" stepKey="createLinkForGroupedProduct"> + <requiredEntity createDataKey="createGroupedProduct"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + <!-- Log in as a customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLoginToStorefront"> + <argument name="Customer" value="$createCustomer$"/> + </actionGroup> + <!-- Customer placed order from storefront with payment method --> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="$createSimpleProduct$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <actionGroup ref="StorefrontSetShippingMethodActionGroup" stepKey="setShippingMethodFlatRate"/> + <actionGroup ref="StorefrontCheckoutClickNextOnShippingStepActionGroup" stepKey="goToCheckoutPaymentStep"/> + <!-- Fill Braintree card data --> + <click selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="selectBraintreePaymentMethod"/> + <waitForPageLoad stepKey="waitForBraintreeFormLoad"/> + <scrollTo selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="scrollToCreditCardSection"/> + <actionGroup ref="StorefrontFillCartDataActionGroup" stepKey="fillCardData"> + <argument name="cartData" value="PaymentAndShippingInfo"/> + </actionGroup> + <waitForPageLoad stepKey="waitForFillCardData"/> + <checkOption selector="{{StorefrontOnePageCheckoutPaymentSection.saveForLaterUse}}" stepKey="checkSaveForLaterUse"/> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="clickOnPlaceOrder"> + <argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage"/> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage"/> + </actionGroup> + </before> + <after> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <!-- Set configs to default --> + <createData entity="DefaultBraintreeConfig" stepKey="defaultBraintreeConfig"/> + <createData entity="RollBackCustomBraintreeConfigurationData" stepKey="rollBackCustomBraintreeConfigurationData"/> + <!-- Remove created products/attributes --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/> + <!-- Remove Downloadable Product --> + <magentoCLI command="downloadable:domains:remove static.magento.com" stepKey="removeDownloadableDomain"/> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> + <!-- Remove Configurable Product --> + <deleteData createDataKey="createConfigProductCreateConfigurableProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProductAttributeCreateConfigurableProduct" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createConfigChildProduct1CreateConfigurableProduct" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2CreateConfigurableProduct" stepKey="deleteConfigChildProduct2"/> + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <actionGroup ref="CliRunReindexUsingCronJobsActionGroup" stepKey="reindexInvalidatedIndices"/> + </after> + <!-- 1. Browse all product page and verify that the "Instant Purchase" button appears --> + <!-- Virtual product --> + <amOnPage url="{{StorefrontProductPage.url($createVirtualProduct.custom_attributes[url_key]$)}}" stepKey="openVirtualProductPage"/> + <waitForElementVisible selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="waitForButtonOnVirtualProductPage"/> + <!-- Downloadable Product --> + <amOnPage url="{{StorefrontProductPage.url($createDownloadableProduct.custom_attributes[url_key]$)}}" stepKey="openDownloadableProductPage"/> + <waitForElementVisible selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="waitForButtonOnDownloadableProductPage"/> + <!-- Bundle Product --> + <amOnPage url="{{StorefrontProductPage.url($createBundleProduct.custom_attributes[url_key]$)}}" stepKey="openBundleProductPage"/> + <waitForElementVisible selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="waitForCustomizeAndAddToCartButton"/> + <click selector="{{StorefrontBundleProductActionSection.customizeAndAddToCartButton}}" stepKey="clickCustomizeAndAddToCart"/> + <waitForElementVisible selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="waitForButtonOnBundleProductPage"/> + <!-- Grouped product --> + <amOnPage url="{{StorefrontProductPage.url($createGroupedProduct.custom_attributes[url_key]$)}}" stepKey="openGroupedProductPage"/> + <waitForElementVisible selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="waitForButtonOnGroupedProductPage"/> + <!-- Configurable Product --> + <amOnPage url="{{StorefrontProductPage.url($createConfigProductCreateConfigurableProduct.custom_attributes[url_key]$)}}" stepKey="openConfigurableProductPage"/> + <waitForElementVisible selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="waitForButtonOnConfigurableProductPage"/> + <!-- 2. Click on "Instant Purchase" and assert information --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct.custom_attributes[url_key]$)}}" stepKey="openSimpleProductPage"/> + <waitForElementVisible selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="waitForInstantPurchaseButton"/> + <actionGroup ref="AssertStorefrontInstantPurchaseConfirmationDataActionGroup" stepKey="assertInstantPurchasePopupData"> + <argument name="shippingStreet" value="{{US_Address_NY.street[0]}}"/> + <argument name="billingStreet" value="{{US_Address_NY.street[0]}}"/> + <argument name="cardEnding" value="{{StoredPaymentMethods.cardNumberEnding}}"/> + </actionGroup> + <!-- 3. Confirm Instant Purchase --> + <click selector="{{ModalConfirmationSection.OkButton}}" stepKey="placeOrderAgain"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <see userInput="Your order number is:" selector="{{StorefrontMessagesSection.success}}" stepKey="seePlaceOrderSuccessMessage"/> + <!-- 4. Customer changes his default address --> + <amOnPage url="{{StorefrontCustomerAddressesPage.url}}" stepKey="goToAddressPage"/> + <click selector="{{StorefrontCustomerAddressesSection.editAdditionalAddress('1')}}" stepKey="clickOnEditAdditionalAddressButton"/> + <checkOption selector="{{StorefrontCustomerAddressFormSection.useAsDefaultBillingAddressCheckBox}}" stepKey="checkUseAsDefaultBillingAddressCheckbox"/> + <actionGroup ref="AdminSaveCustomerAddressActionGroup" stepKey="saveAddress"/> + <!-- 5.1 Customer places a new order from the storefront with new payment credentials --> + <actionGroup ref="AddSimpleProductToCartActionGroup" stepKey="addProductToCartAgain"> + <argument name="product" value="$createSimpleProduct$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicartAgain"/> + <actionGroup ref="StorefrontSetShippingMethodActionGroup" stepKey="setShippingMethodFlatRateAgain"/> + <click selector="{{CheckoutShippingMethodsSection.shipHereButton}}" stepKey="changeShippingAddress"/> + <actionGroup ref="StorefrontCheckoutClickNextOnShippingStepActionGroup" stepKey="goToCheckoutPaymentStepAgain"/> + <!-- Fill Braintree card data --> + <click selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="selectBraintreePaymentMethodAgain"/> + <waitForPageLoad stepKey="waitForBraintreeFormLoadAgain"/> + <scrollTo selector="{{BraintreeConfigurationPaymentSection.creditCart}}" stepKey="scrollToCreditCardSectionAgain"/> + <actionGroup ref="StorefrontFillCartDataActionGroup" stepKey="fillCardDataAgain"> + <argument name="cartData" value="VisaDefaultCard"/> + </actionGroup> + <waitForPageLoad stepKey="waitForFillCardDataAgain"/> + <!-- 5.2 Customer save this payment method --> + <checkOption selector="{{StorefrontOnePageCheckoutPaymentSection.saveForLaterUse}}" stepKey="checkSaveForLaterUseAgain"/> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="clickOnPlaceOrderAgain"> + <argument name="orderNumberMessage" value="CONST.successCheckoutOrderNumberMessage"/> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage"/> + </actionGroup> + <!-- 6. Customer opens simple product page --> + <amOnPage url="{{StorefrontProductPage.url($createSimpleProduct.custom_attributes[url_key]$)}}" stepKey="openSimpleProductPageAgain"/> + <waitForElementVisible selector="{{StorefrontInstantPurchaseSection.instantPurchaseButton}}" stepKey="waitForInstantPurchaseButtonAgain"/> + <!-- 7. Click on "Instant Purchase" and verify that information are different from previous --> + <actionGroup ref="AssertStorefrontInstantPurchaseConfirmationDataActionGroup" stepKey="assertInstantPurchasePopupDataAgain"> + <argument name="shippingStreet" value="{{US_Address_NY.street[0]}}"/> + <argument name="billingStreet" value="{{UK_Not_Default_Address.street[0]}}"/> + <argument name="cardEnding" value="{{VisaDefaultCardInfo.cardNumberEnding}}"/> + </actionGroup> + <!-- 8. Confirm Instant Purchase --> + <click selector="{{ModalConfirmationSection.OkButton}}" stepKey="placeOrderFinalTime"/> + <waitForElementVisible selector="{{StorefrontMessagesSection.success}}" stepKey="waitForSuccessMessageAgain"/> + <see userInput="Your order number is:" selector="{{StorefrontMessagesSection.success}}" stepKey="seePlaceOrderSuccessMessageAgain"/> + </test> +</tests> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/ModalConfirmationSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/ModalConfirmationSection.xml index 4bf84d9ee63da..07c4498efdafc 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/ModalConfirmationSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/ModalConfirmationSection.xml @@ -11,6 +11,6 @@ <section name="ModalConfirmationSection"> <element name="modalContent" type="text" selector="aside.confirm div.modal-content"/> <element name="CancelButton" type="button" selector="//footer[@class='modal-footer']/button[contains(@class, 'action-dismiss')]"/> - <element name="OkButton" type="button" selector="//footer[@class='modal-footer']/button[contains(@class, 'action-accept')]"/> + <element name="OkButton" type="button" selector="//footer[@class='modal-footer']/button[contains(@class, 'action-accept')]" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Vault/Test/Mftf/Page/StorefrontOnePageCheckoutPage.xml b/app/code/Magento/Vault/Test/Mftf/Page/StorefrontOnePageCheckoutPage.xml new file mode 100644 index 0000000000000..f767dcedc13b6 --- /dev/null +++ b/app/code/Magento/Vault/Test/Mftf/Page/StorefrontOnePageCheckoutPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="CheckoutPage" url="/checkout" area="storefront" module="Magento_Checkout"> + <section name="StorefrontOnePageCheckoutPaymentSection"/> + </page> +</pages> diff --git a/app/code/Magento/Vault/Test/Mftf/Section/StorefrontOnePageCheckoutSection.xml b/app/code/Magento/Vault/Test/Mftf/Section/StorefrontOnePageCheckoutSection.xml new file mode 100644 index 0000000000000..53ad03bd01ef8 --- /dev/null +++ b/app/code/Magento/Vault/Test/Mftf/Section/StorefrontOnePageCheckoutSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontOnePageCheckoutPaymentSection"> + <element name="saveForLaterUse" type="checkbox" selector="fieldset.payment > div.choice > input[name='vault[is_enabled]']"/> + </section> +</sections> From 22e21276540d4b4f0aed6985aa7ce1eef72519c2 Mon Sep 17 00:00:00 2001 From: Fil Maj <maj.fil@gmail.com> Date: Fri, 20 Mar 2020 11:23:13 -0400 Subject: [PATCH 359/369] Updating link to Adobe CLA in contributing.md --- .github/CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 89aea7299855c..37b7bc2ca8c3a 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -31,7 +31,7 @@ If you are a new GitHub user, we recommend that you create your own [free github This will allow you to collaborate with the Magento 2 development team, fork the Magento 2 project and send pull requests. 1. Search current [listed issues](https://github.com/magento/magento2/issues) (open or closed) for similar proposals of intended contribution before starting work on a new contribution. -2. Review the [Contributor License Agreement](https://magento.com/legaldocuments/mca) if this is your first time contributing. +2. Review the [Contributor License Agreement](https://opensource.adobe.com/cla.html) if this is your first time contributing. 3. Create and test your work. 4. Fork the Magento 2 repository according to the [Fork A Repository instructions](https://devdocs.magento.com/guides/v2.3/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow the [Create A Pull Request instructions](https://devdocs.magento.com/guides/v2.3/contributor-guide/contributing.html#pull_request). 5. Once your contribution is received the Magento 2 development team will review the contribution and collaborate with you as needed. From b38b4ecd52dcdd9d288a923ed0841ebec51823ef Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Fri, 20 Mar 2020 17:44:43 +0200 Subject: [PATCH 360/369] MC-32578: [MFTF] StoreFrontRecentlyViewedAtStoreViewLevelTest fails because of bad design --- .../Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml index 8243f21cb6510..c0c142fe2cba1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml @@ -12,9 +12,10 @@ <stories value="Recently Viewed Product"/> <title value="Recently Viewed Product at store level"/> <description value="Recently Viewed Product should not be displayed on second store , if configured as, Per Store "/> - <testCaseId value="MC-32018"/> + <testCaseId value="MC-32226"/> <severity value="CRITICAL"/> <group value="catalog"/> + <group value="WYSIWYGDisabled"/> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> @@ -163,4 +164,4 @@ <assertNotContains expected="$$createSimpleProduct1.name$$" actual="$grabDontSeeHomeProduct1" stepKey="assertNotSeeProduct1"/> </test> -</tests> \ No newline at end of file +</tests> From 1ac6b5dcfe1b222822c1dcc374d1ef2d6a930535 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Fri, 20 Mar 2020 19:50:56 +0200 Subject: [PATCH 361/369] remove unstable cas on tests --- .../testsuite/Magento/Backend/Model/Dashboard/ChartTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php index c7d2e3ef17ec0..e496a0f07a653 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php @@ -60,11 +60,6 @@ public function getChartDataProvider(): array '7d', 'quantity' ], - [ - 19, - '1m', - 'quantity' - ], [ 16, '1y', From c724d3c4d436e6c293fb76a3557f61329ae9c1d7 Mon Sep 17 00:00:00 2001 From: Nazar Klovanych <nazarn96@gmail.com> Date: Fri, 20 Mar 2020 21:11:50 +0200 Subject: [PATCH 362/369] refactor assertion to avoid unstable data --- .../Magento/Backend/Model/Dashboard/ChartTest.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php index e496a0f07a653..284c3c9b5046e 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php @@ -39,7 +39,7 @@ protected function setUp() */ public function testGetByPeriodWithParam(int $expectedDataQty, string $period, string $chartParam): void { - $this->assertCount($expectedDataQty, $this->model->getByPeriod($period, $chartParam)); + $this->assertGreaterThan($expectedDataQty, $this->model->getByPeriod($period, $chartParam)); } /** @@ -51,22 +51,27 @@ public function getChartDataProvider(): array { return [ [ - 24, + 10, '24h', 'quantity' ], [ - 8, + 4, '7d', 'quantity' ], [ - 16, + 10, + '1m', + 'quantity' + ], + [ + 8, '1y', 'quantity' ], [ - 28, + 15, '2y', 'quantity' ] From a46f9458d4c89e9df130500c3734add210d98325 Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Fri, 20 Mar 2020 15:08:30 -0500 Subject: [PATCH 363/369] MC-32378: Implement control over minimum_should_match for elasticsearch queries --- .../Magento/Elasticsearch7/etc/config.xml | 2 +- .../_files/products_for_search.php | 40 +++++++++---------- .../_files/products_for_search_rollback.php | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/code/Magento/Elasticsearch7/etc/config.xml b/app/code/Magento/Elasticsearch7/etc/config.xml index c13586bb9e357..72657975faebb 100644 --- a/app/code/Magento/Elasticsearch7/etc/config.xml +++ b/app/code/Magento/Elasticsearch7/etc/config.xml @@ -14,7 +14,7 @@ <elasticsearch7_index_prefix>magento2</elasticsearch7_index_prefix> <elasticsearch7_enable_auth>0</elasticsearch7_enable_auth> <elasticsearch7_server_timeout>15</elasticsearch7_server_timeout> - <elasticsearch7_minimum_should_match></elasticsearch7_minimum_should_match> + <elasticsearch7_minimum_should_match/> </search> </catalog> </default> diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search.php index 8792a1060ebf2..4f271d2a78a39 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search.php @@ -12,63 +12,65 @@ use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Catalog\Model\Product\Visibility; +$categoryId = 333; +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$attributeSet = $objectManager->create(\Magento\Eav\Model\Entity\Attribute\Set::class); +$entityType = $objectManager->create(\Magento\Eav\Model\Entity\Type::class)->loadByCode('catalog_product'); +$defaultSetId = $objectManager->create(\Magento\Catalog\Model\Product::class)->getDefaultAttributeSetid(); + $products = [ [ 'type' => 'simple', - 'id' => 201, 'name' => 'search product 1', 'sku' => '24 MB06', 'status' => Status::STATUS_ENABLED, 'visibility' => Visibility::VISIBILITY_BOTH, - 'attribute_set' => 4, - 'website_ids' => [1], + 'attribute_set' => $defaultSetId, + 'website_ids' => [\Magento\Store\Model\Store::DISTRO_STORE_ID], 'price' => 10, - 'category_id' => 333, + 'category_id' => $categoryId, 'meta_title' => 'Key Title', 'meta_keyword' => 'meta keyword', 'meta_description' => 'meta description', ], [ 'type' => 'simple', - 'id' => 202, 'name' => 'search product 2', 'sku' => '24 MB04', 'status' => Status::STATUS_ENABLED, 'visibility' => Visibility::VISIBILITY_BOTH, - 'attribute_set' => 4, - 'website_ids' => [1], + 'attribute_set' => $defaultSetId, + 'website_ids' => [\Magento\Store\Model\Store::DISTRO_STORE_ID], 'price' => 10, - 'category_id' => 333, + 'category_id' => $categoryId, 'meta_title' => 'Last Title', 'meta_keyword' => 'meta keyword', 'meta_description' => 'meta description', ], [ 'type' => 'simple', - 'id' => 203, 'name' => 'search product 3', 'sku' => '24 MB02', 'status' => Status::STATUS_ENABLED, 'visibility' => Visibility::VISIBILITY_BOTH, - 'attribute_set' => 4, - 'website_ids' => [1], + 'attribute_set' => $defaultSetId, + 'website_ids' => [\Magento\Store\Model\Store::DISTRO_STORE_ID], 'price' => 20, - 'category_id' => 333, + 'category_id' => $categoryId, 'meta_title' => 'First Title', 'meta_keyword' => 'meta keyword', 'meta_description' => 'meta description', ], [ 'type' => 'simple', - 'id' => 204, 'name' => 'search product 4', 'sku' => '24 MB01', 'status' => Status::STATUS_ENABLED, 'visibility' => Visibility::VISIBILITY_BOTH, - 'attribute_set' => 4, - 'website_ids' => [1], + 'attribute_set' => $defaultSetId, + 'website_ids' => [\Magento\Store\Model\Store::DISTRO_STORE_ID], 'price' => 30, - 'category_id' => 333, + 'category_id' => $categoryId, 'meta_title' => 'A title', 'meta_keyword' => 'meta keyword', 'meta_description' => 'meta description', @@ -76,17 +78,15 @@ ]; /** @var CategoryLinkManagementInterface $categoryLinkManagement */ -$categoryLinkManagement = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(CategoryLinkManagementInterface::class); +$categoryLinkManagement = $objectManager->create(CategoryLinkManagementInterface::class); $categoriesToAssign = []; foreach ($products as $data) { /** @var $product Product */ - $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(Product::class); + $product = $objectManager->create(Product::class); $product ->setTypeId($data['type']) - ->setId($data['id']) ->setAttributeSetId($data['attribute_set']) ->setWebsiteIds($data['website_ids']) ->setName($data['name']) diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search_rollback.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search_rollback.php index b0cfcd7a3e5e3..120816c38232d 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/_files/products_for_search_rollback.php @@ -30,7 +30,7 @@ } } -include dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; +include __DIR__ . '/../../Catalog/_files/category_rollback.php'; $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); From efd31af7111c1a92f29f9ba7105d642a6a7157a5 Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Fri, 20 Mar 2020 18:43:37 -0500 Subject: [PATCH 364/369] MC-13825: [2.4.x] Migrate ZF2 components to Laminas -- fix merge conflict --- .../Controller/Adminhtml/IndexTest.php | 360 +----------------- 1 file changed, 15 insertions(+), 345 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 789f9a56b68ce..e2ba275a5a438 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -6,44 +6,32 @@ namespace Magento\Customer\Controller\Adminhtml; -use Magento\Customer\Api\AccountManagementInterface; -use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Backend\Model\Session; +use Magento\Customer\Api\CustomerNameGenerationInterface; use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Customer\Controller\RegistryConstants; use Magento\Customer\Model\EmailNotification; use Magento\TestFramework\Helper\Bootstrap; use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\TestFramework\TestCase\AbstractBackendController; /** * @magentoAppArea adminhtml * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class IndexTest extends \Magento\TestFramework\TestCase\AbstractBackendController +class IndexTest extends AbstractBackendController { /** * Base controller URL * * @var string */ - protected $_baseControllerUrl; + private $baseControllerUrl = 'backend/customer/index/'; /** @var CustomerRepositoryInterface */ - protected $customerRepository; + private $customerRepository; - /** @var AddressRepositoryInterface */ - protected $addressRepository; - - /** @var AccountManagementInterface */ - protected $accountManagement; - - /** @var \Magento\Framework\Data\Form\FormKey */ - protected $formKey; - - /**@var \Magento\Customer\Helper\View */ - protected $customerViewHelper; - - /** @var \Magento\TestFramework\ObjectManager */ - protected $objectManager; + /** @var CustomerNameGenerationInterface */ + private $customerViewHelper; /** * @inheritDoc @@ -51,24 +39,8 @@ class IndexTest extends \Magento\TestFramework\TestCase\AbstractBackendControlle protected function setUp() { parent::setUp(); - $this->_baseControllerUrl = 'http://localhost/index.php/backend/customer/index/'; - $this->customerRepository = Bootstrap::getObjectManager()->get( - \Magento\Customer\Api\CustomerRepositoryInterface::class - ); - $this->addressRepository = Bootstrap::getObjectManager()->get( - \Magento\Customer\Api\AddressRepositoryInterface::class - ); - $this->accountManagement = Bootstrap::getObjectManager()->get( - \Magento\Customer\Api\AccountManagementInterface::class - ); - $this->formKey = Bootstrap::getObjectManager()->get( - \Magento\Framework\Data\Form\FormKey::class - ); - - $this->objectManager = Bootstrap::getObjectManager(); - $this->customerViewHelper = $this->objectManager->get( - \Magento\Customer\Helper\View::class - ); + $this->customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $this->customerViewHelper = $this->_objectManager->get(CustomerNameGenerationInterface::class); } /** @@ -79,144 +51,12 @@ protected function tearDown() /** * Unset customer data */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->setCustomerData(null); + $this->_objectManager->get(Session::class)->setCustomerData(null); /** * Unset messages */ - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getMessages(true); - } - - /** - * @magentoDbIsolation enabled - */ - public function testSaveActionWithEmptyPostData() - { - $this->getRequest()->setPostValue([])->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); - } - - /** - * @magentoDbIsolation enabled - */ - public function testSaveActionWithInvalidFormData() - { - $post = ['account' => ['middlename' => 'test middlename', 'group_id' => 1]]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - /** - * Check that errors was generated and set to session - */ - $this->assertSessionMessages( - $this->logicalNot($this->isEmpty()), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - /** @var \Magento\Backend\Model\Session $session */ - $session = $this->objectManager->get(\Magento\Backend\Model\Session::class); - /** - * Check that customer data were set to session - */ - $this->assertNotEmpty($session->getCustomerFormData()); - $this->assertArraySubset($post, $session->getCustomerFormData()); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new')); - } - - /** - * @magentoDataFixture Magento/Newsletter/_files/subscribers.php - */ - public function testSaveActionExistingCustomerUnsubscribeNewsletter() - { - $customerId = 1; - $websiteId = 1; - - /** @var \Magento\Newsletter\Model\Subscriber $subscriber */ - $subscriber = $this->objectManager->get(\Magento\Newsletter\Model\SubscriberFactory::class)->create(); - $this->assertEmpty($subscriber->getId()); - $subscriber->loadByCustomerId($customerId); - $this->assertNotEmpty($subscriber->getId()); - $this->assertEquals(1, $subscriber->getStatus()); - - $post = [ - 'customer' => [ - 'entity_id' => $customerId, - 'email' => 'customer@example.com', - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'sendemail_store_id' => 1 - ], - 'subscription_status' => [$websiteId => '0'] - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->getRequest()->setParam('id', 1); - $this->dispatch('backend/customer/index/save'); - - /** @var \Magento\Newsletter\Model\Subscriber $subscriber */ - $subscriber = $this->objectManager->get(\Magento\Newsletter\Model\SubscriberFactory::class)->create(); - $this->assertEmpty($subscriber->getId()); - $subscriber->loadByCustomerId($customerId); - $this->assertNotEmpty($subscriber->getId()); - $this->assertEquals(3, $subscriber->getStatus()); - - /** - * Check that success message is set - */ - $this->assertSessionMessages( - $this->equalTo(['You saved the customer.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); - } - - /** - * Ensure that an email is sent during save action - * - * @magentoConfigFixture current_store customer/account_information/change_email_template change_email_template - * @magentoConfigFixture current_store customer/password/forgot_email_identity support - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testSaveActionExistingCustomerChangeEmail() - { - $customerId = 1; - $newEmail = 'newcustomer@example.com'; - $transportBuilderMock = $this->prepareEmailMock( - 2, - 'change_email_template', - [ - 'name' => 'CustomerSupport', - 'email' => 'support@example.com', - ], - $customerId, - $newEmail - ); - $this->addEmailMockToClass($transportBuilderMock, EmailNotification::class); - $post = [ - 'customer' => [ - 'entity_id' => $customerId, - 'middlename' => 'test middlename', - 'group_id' => 1, - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => $newEmail, - 'new_password' => 'auto', - 'sendemail_store_id' => '1', - 'sendemail' => '1', - 'created_at' => '2000-01-01 00:00:00', - 'default_shipping' => '_item1', - 'default_billing' => 1, - ] - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->getRequest()->setParam('id', 1); - $this->dispatch('backend/customer/index/save'); - - /** - * Check that no errors were generated and set to session - */ - $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'index/key/')); + $this->_objectManager->get(Session::class)->getMessages(true); } /** @@ -265,83 +105,6 @@ public function testInlineEditChangeEmail() $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); } - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testSaveActionCoreException() - { - $post = [ - 'customer' => [ - 'middlename' => 'test middlename', - 'group_id' => 1, - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'customer@example.com', - 'password' => 'password', - ], - ]; - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - /* - * Check that error message is set - */ - $this->assertSessionMessages( - $this->equalTo(['A customer with the same email address already exists in an associated website.']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - $this->assertArraySubset( - $post, - Bootstrap::getObjectManager()->get(\Magento\Backend\Model\Session::class)->getCustomerFormData() - ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new/key/')); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testSaveActionCoreExceptionFormatFormData() - { - $post = [ - 'customer' => [ - 'middlename' => 'test middlename', - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'customer@example.com', - 'dob' => '12/3/1996', - ], - ]; - $postCustomerFormatted = [ - 'middlename' => 'test middlename', - 'website_id' => 1, - 'firstname' => 'test firstname', - 'lastname' => 'test lastname', - 'email' => 'customer@example.com', - 'dob' => '1996-12-03', - ]; - - $this->getRequest()->setPostValue($post)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/save'); - /* - * Check that error message is set - */ - $this->assertSessionMessages( - $this->equalTo(['A customer with the same email address already exists in an associated website.']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - - $customerFormData = Bootstrap::getObjectManager() - ->get(\Magento\Backend\Model\Session::class) - ->getCustomerFormData(); - $this->assertEquals( - $postCustomerFormatted, - $customerFormData['customer'], - 'Customer form data should be formatted' - ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'new/key/')); - } - /** * @magentoDataFixture Magento/Customer/_files/customer_sample.php */ @@ -390,42 +153,6 @@ public function te1stNewActionWithCustomerData() $this->testNewAction(); } - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testDeleteAction() - { - $this->getRequest()->setParam('id', 1); - $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()); - - $this->getRequest()->setMethod(\Laminas\Http\Request::METHOD_POST); - - $this->dispatch('backend/customer/index/delete'); - $this->assertRedirect($this->stringContains('customer/index')); - $this->assertSessionMessages( - $this->equalTo(['You deleted the customer.']), - \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS - ); - } - - /** - * @magentoDataFixture Magento/Customer/_files/customer_sample.php - */ - public function testNotExistingCustomerDeleteAction() - { - $this->getRequest()->setParam('id', 2); - $this->getRequest()->setParam('form_key', $this->formKey->getFormKey()); - - $this->getRequest()->setMethod(\Laminas\Http\Request::METHOD_POST); - - $this->dispatch('backend/customer/index/delete'); - $this->assertRedirect($this->stringContains('customer/index')); - $this->assertSessionMessages( - $this->equalTo(['No such entity with customerId = 2']), - \Magento\Framework\Message\MessageInterface::TYPE_ERROR - ); - } - /** * @magentoDataFixture Magento/Customer/_files/customer_sample.php */ @@ -437,63 +164,6 @@ public function testCartAction() $this->assertContains('<div id="customer_cart_grid"', $body); } - /** - * @magentoDataFixture Magento/Customer/_files/customer.php - * @magentoDataFixture Magento/Customer/_files/customer_address.php - */ - public function testValidateCustomerWithAddressSuccess() - { - $customerData = [ - 'customer' => [ - 'entity_id' => '1', - 'middlename' => 'new middlename', - 'group_id' => 1, - 'website_id' => 1, - 'firstname' => 'new firstname', - 'lastname' => 'new lastname', - 'email' => 'example@domain.com', - 'default_shipping' => '_item1', - 'new_password' => 'auto', - 'sendemail_store_id' => '1', - 'sendemail' => '1', - ], - 'address' => [ - '_item1' => [ - 'firstname' => 'update firstname', - 'lastname' => 'update lastname', - 'street' => ['update street'], - 'city' => 'update city', - 'country_id' => 'US', - 'region_id' => 10, - 'postcode' => '01001', - 'telephone' => '+7000000001', - ], - '_template_' => [ - 'firstname' => '', - 'lastname' => '', - 'street' => [], - 'city' => '', - 'country_id' => 'US', - 'postcode' => '', - 'telephone' => '', - ], - ], - ]; - /** - * set customer data - */ - $this->getRequest()->setParams($customerData)->setMethod(HttpRequest::METHOD_POST); - $this->dispatch('backend/customer/index/validate'); - $body = $this->getResponse()->getBody(); - - /** - * Check that no errors were generated and set to session - */ - $this->assertSessionMessages($this->isEmpty(), \Magento\Framework\Message\MessageInterface::TYPE_ERROR); - - $this->assertEquals('{"error":0}', $body); - } - /** * @magentoDbIsolation enabled */ @@ -502,7 +172,7 @@ public function testResetPasswordActionNoCustomerId() // No customer ID in post, will just get redirected to base $this->getRequest()->setMethod(HttpRequest::METHOD_GET); $this->dispatch('backend/customer/index/resetPassword'); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); + $this->assertRedirect($this->stringContains($this->baseControllerUrl)); } /** @@ -514,7 +184,7 @@ public function testResetPasswordActionBadCustomerId() $this->getRequest()->setMethod(HttpRequest::METHOD_GET); $this->getRequest()->setPostValue(['customer_id' => '789']); $this->dispatch('backend/customer/index/resetPassword'); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl)); + $this->assertRedirect($this->stringContains($this->baseControllerUrl)); } /** @@ -529,7 +199,7 @@ public function testResetPasswordActionSuccess() $this->equalTo(['The customer will receive an email with a link to reset password.']), \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS ); - $this->assertRedirect($this->stringStartsWith($this->_baseControllerUrl . 'edit')); + $this->assertRedirect($this->stringContains($this->baseControllerUrl . 'edit')); } /** From 7fc4e5c079270e8ff6586eb54df96d458447fd6b Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Fri, 20 Mar 2020 19:31:17 -0500 Subject: [PATCH 365/369] Integration test fix --- .../Backend/Model/Dashboard/ChartTest.php | 16 ++++++++++------ .../Sales/_files/order_list_with_invoice.php | 6 +++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php index 284c3c9b5046e..25ac15342e13f 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/Dashboard/ChartTest.php @@ -39,7 +39,11 @@ protected function setUp() */ public function testGetByPeriodWithParam(int $expectedDataQty, string $period, string $chartParam): void { - $this->assertGreaterThan($expectedDataQty, $this->model->getByPeriod($period, $chartParam)); + $ordersData = $this->model->getByPeriod($period, $chartParam); + $ordersCount = array_sum(array_map(function ($item) { + return $item['y']; + }, $ordersData)); + $this->assertGreaterThanOrEqual($expectedDataQty, $ordersCount); } /** @@ -51,27 +55,27 @@ public function getChartDataProvider(): array { return [ [ - 10, + 2, '24h', 'quantity' ], [ - 4, + 3, '7d', 'quantity' ], [ - 10, + 4, '1m', 'quantity' ], [ - 8, + 5, '1y', 'quantity' ], [ - 15, + 6, '2y', 'quantity' ] diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list_with_invoice.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list_with_invoice.php index 06ddb18b009d1..d1f74c746b64f 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list_with_invoice.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list_with_invoice.php @@ -55,7 +55,7 @@ 'base_grand_total' => 140.00, 'grand_total' => 140.00, 'subtotal' => 140.00, - 'created_at' => $dateTime->modify('-1 month')->format(DateTime::DATETIME_PHP_FORMAT), + 'created_at' => $dateTime->modify('first day of this month')->format(DateTime::DATETIME_PHP_FORMAT), ], [ 'increment_id' => '100000005', @@ -65,7 +65,7 @@ 'base_grand_total' => 150.00, 'grand_total' => 150.00, 'subtotal' => 150.00, - 'created_at' => $dateTime->modify('-1 year')->format(DateTime::DATETIME_PHP_FORMAT), + 'created_at' => $dateTime->modify('first day of january this year')->format(DateTime::DATETIME_PHP_FORMAT), ], [ 'increment_id' => '100000006', @@ -75,7 +75,7 @@ 'base_grand_total' => 160.00, 'grand_total' => 160.00, 'subtotal' => 160.00, - 'created_at' => $dateTime->modify('-2 year')->format(DateTime::DATETIME_PHP_FORMAT), + 'created_at' => $dateTime->modify('first day of january last year')->format(DateTime::DATETIME_PHP_FORMAT), ], ]; From b112b54ce14a89fa74e122e7c45a1a6d61a1bdd3 Mon Sep 17 00:00:00 2001 From: Andrii Beziazychnyi <a.beziazychnyi@atwix.com> Date: Sun, 22 Mar 2020 17:15:21 +0200 Subject: [PATCH 366/369] magento/magento2: fixes PHPDocs for module Magento_reports --- .../Model/ResourceModel/Product/Sold/Collection.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Sold/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Sold/Collection.php index bca9b8662715a..bc8a67c2addff 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Product/Sold/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Sold/Collection.php @@ -12,6 +12,7 @@ namespace Magento\Reports\Model\ResourceModel\Product\Sold; use Magento\Framework\DB\Select; +use Zend_Db_Select_Exception; /** * Data collection. @@ -25,9 +26,10 @@ class Collection extends \Magento\Reports\Model\ResourceModel\Order\Collection /** * Set Date range to collection. * - * @param int $from - * @param int $to + * @param string $from + * @param string $to * @return $this + * @throws Zend_Db_Select_Exception */ public function setDateRange($from, $to) { @@ -49,6 +51,7 @@ public function setDateRange($from, $to) * @param string $from * @param string $to * @return $this + * @throws Zend_Db_Select_Exception */ public function addOrderedQty($from = '', $to = '') { From 7c5185423f8e595f0589b36d1f61b9c58975790b Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Mon, 23 Mar 2020 10:43:05 +0200 Subject: [PATCH 367/369] MC-32638: [MFTF] AdminCreateSimpleProductWithDatetimeAttributeTest fails because of bad design --- ...CreateSimpleProductWithDatetimeAttributeTest.xml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithDatetimeAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithDatetimeAttributeTest.xml index fe5b70b8e4ca7..2141f44113057 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithDatetimeAttributeTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithDatetimeAttributeTest.xml @@ -19,8 +19,9 @@ </annotations> <before> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> + <after> <deleteData createDataKey="createDatetimeAttribute" stepKey="deleteDatetimeAttribute"/> <actionGroup ref="DeleteProductBySkuActionGroup" stepKey="deleteCreatedProduct"> @@ -45,13 +46,15 @@ <actionGroup ref="AddProductAttributeInProductModalActionGroup" stepKey="addDatetimeAttribute"> <argument name="attributeCode" value="$createDatetimeAttribute.attribute_code$"/> </actionGroup> - <!-- Flush config cache to reset product attributes in attribute set --> - <magentoCLI command="cache:flush" arguments="config" stepKey="flushConfigCache"/> <!-- Save the product --> <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> + <!-- Flush config cache to reset product attributes in attribute set --> + <magentoCLI command="cache:flush" arguments="config" stepKey="flushConfigCache"/> + <reloadPage stepKey="reloadProductEditPage"/> <!-- Check default value --> - <scrollTo selector="{{AdminProductAttributesSection.sectionHeader}}" stepKey="goToAttributesSection"/> - <click selector="{{AdminProductAttributesSection.sectionHeader}}" stepKey="openAttributesSection"/> + <waitForElementVisible selector="{{AdminProductAttributesSection.sectionHeader}}" stepKey="waitAttributesSectionAppears"/> + <conditionalClick selector="{{AdminProductAttributesSection.sectionHeader}}" dependentSelector="{{AdminProductAttributesSection.attributeTextInputByCode($createDatetimeAttribute.attribute_code$)}}" visible="false" stepKey="openAttributesSection"/> + <scrollTo selector="{{AdminProductAttributesSection.sectionHeader}}" stepKey="scrollToAttributesSection"/> <waitForElementVisible selector="{{AdminProductAttributesSection.attributeTextInputByCode($createDatetimeAttribute.attribute_code$)}}" stepKey="waitForSlideOutAttributes"/> <seeInField selector="{{AdminProductAttributesSection.attributeTextInputByCode($createDatetimeAttribute.attribute_code$)}}" userInput="$generateDefaultValue" stepKey="checkDefaultValue"/> <!-- Check datetime grid filter --> From eb26773b3353119234c4010d830ccc57076160db Mon Sep 17 00:00:00 2001 From: Iryna Lagno <ilagno@adobe.com> Date: Fri, 20 Mar 2020 17:26:54 -0500 Subject: [PATCH 368/369] MC-23540: Elasticsearch 7.x.x Upgrade --- .../Elasticsearch/SearchAdapter/Mapper.php | 65 ++++++ .../Test/Unit/SearchAdapter/MapperTest.php | 204 ++++++++++++++++++ .../Test/Legacy/_files/obsolete_classes.php | 1 - 3 files changed, 269 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Elasticsearch/SearchAdapter/Mapper.php create mode 100644 app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/MapperTest.php diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Mapper.php b/app/code/Magento/Elasticsearch/SearchAdapter/Mapper.php new file mode 100644 index 0000000000000..d76086ee2f809 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Mapper.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter; + +use Magento\Framework\Search\RequestInterface; +use Magento\Framework\Search\Request\Query\BoolExpression as BoolQuery; +use Magento\Elasticsearch\SearchAdapter\Query\Builder as QueryBuilder; +use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder; +use Magento\Elasticsearch\SearchAdapter\Filter\Builder as FilterBuilder; +use Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Mapper as Elasticsearch5Mapper; + +/** + * Mapper class for Elasticsearch2 + * + * @api + * @since 100.1.0 + * @deprecated because of EOL for Elasticsearch2 + */ +class Mapper extends Elasticsearch5Mapper +{ + /** + * @param QueryBuilder $queryBuilder + * @param MatchQueryBuilder $matchQueryBuilder + * @param FilterBuilder $filterBuilder + */ + public function __construct( + QueryBuilder $queryBuilder, + MatchQueryBuilder $matchQueryBuilder, + FilterBuilder $filterBuilder + ) { + $this->queryBuilder = $queryBuilder; + $this->matchQueryBuilder = $matchQueryBuilder; + $this->filterBuilder = $filterBuilder; + } + + /** + * Build adapter dependent query + * + * @param RequestInterface $request + * @return array + * @since 100.1.0 + */ + public function buildQuery(RequestInterface $request) + { + $searchQuery = $this->queryBuilder->initQuery($request); + $searchQuery['body']['query'] = array_merge( + $searchQuery['body']['query'], + $this->processQuery( + $request->getQuery(), + [], + BoolQuery::QUERY_CONDITION_MUST + ) + ); + + $searchQuery['body']['query']['bool']['minimum_should_match'] = 1; + + $searchQuery = $this->queryBuilder->initAggregations($request, $searchQuery); + return $searchQuery; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/MapperTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/MapperTest.php new file mode 100644 index 0000000000000..1e552e395c37f --- /dev/null +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/MapperTest.php @@ -0,0 +1,204 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\Test\Unit\SearchAdapter; + +use Magento\Elasticsearch\SearchAdapter\Mapper; +use Magento\Elasticsearch\SearchAdapter\Query\Builder as QueryBuilder; +use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder; +use Magento\Elasticsearch\SearchAdapter\Filter\Builder as FilterBuilder; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class MapperTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Mapper + */ + protected $model; + + /** + * @var QueryBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $queryBuilder; + + /** + * @var MatchQueryBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $matchQueryBuilder; + + /** + * @var FilterBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + protected $filterBuilder; + + /** + * Setup method + * @return void + */ + protected function setUp() + { + $this->queryBuilder = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\Query\Builder::class) + ->setMethods([ + 'initQuery', + 'initAggregations', + ]) + ->disableOriginalConstructor() + ->getMock(); + $this->matchQueryBuilder = $this->getMockBuilder( + \Magento\Elasticsearch\SearchAdapter\Query\Builder\Match::class + ) + ->setMethods(['build']) + ->disableOriginalConstructor() + ->getMock(); + $this->filterBuilder = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\Filter\Builder::class) + ->disableOriginalConstructor() + ->getMock(); + $this->queryBuilder->expects($this->any()) + ->method('initQuery') + ->willReturn([ + 'body' => [ + 'query' => [], + ], + ]); + $this->queryBuilder->expects($this->any()) + ->method('initAggregations') + ->willReturn([ + 'body' => [ + 'query' => [], + ], + ]); + $this->matchQueryBuilder->expects($this->any()) + ->method('build') + ->willReturn([]); + + $objectManagerHelper = new ObjectManagerHelper($this); + $this->model = $objectManagerHelper->getObject( + \Magento\Elasticsearch\SearchAdapter\Mapper::class, + [ + 'queryBuilder' => $this->queryBuilder, + 'matchQueryBuilder' => $this->matchQueryBuilder, + 'filterBuilder' => $this->filterBuilder + ] + ); + } + + /** + * Test buildQuery() method with exception + * @expectedException \InvalidArgumentException + */ + public function testBuildQueryFailure() + { + $request = $this->getMockBuilder(\Magento\Framework\Search\RequestInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $query = $this->getMockBuilder(\Magento\Framework\Search\Request\QueryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->once()) + ->method('getQuery') + ->willReturn($query); + $query->expects($this->atLeastOnce()) + ->method('getType') + ->willReturn('unknown'); + + $this->model->buildQuery($request); + } + + /** + * Test buildQuery() method + * + * @param string $queryType + * @param string $queryMock + * @param string $referenceType + * @param string $filterMock + * @dataProvider buildQueryDataProvider + */ + public function testBuildQuery($queryType, $queryMock, $referenceType, $filterMock) + { + $request = $this->getMockBuilder(\Magento\Framework\Search\RequestInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $query = $this->getMockBuilder($queryMock) + ->setMethods(['getMust', 'getMustNot', 'getType', 'getShould', 'getReferenceType', 'getReference']) + ->disableOriginalConstructor() + ->getMock(); + $matchQuery = $this->getMockBuilder(\Magento\Framework\Search\Request\Query\Match::class) + ->disableOriginalConstructor() + ->getMock(); + $filterQuery = $this->getMockBuilder($filterMock) + ->disableOriginalConstructor() + ->getMock(); + $request->expects($this->once()) + ->method('getQuery') + ->willReturn($query); + + $query->expects($this->atLeastOnce()) + ->method('getType') + ->willReturn($queryType); + $query->expects($this->any()) + ->method('getMust') + ->willReturn([$matchQuery]); + $query->expects($this->any()) + ->method('getShould') + ->willReturn([]); + $query->expects($this->any()) + ->method('getMustNot') + ->willReturn([]); + $query->expects($this->any()) + ->method('getReferenceType') + ->willReturn($referenceType); + $query->expects($this->any()) + ->method('getReference') + ->willReturn($filterQuery); + $matchQuery->expects($this->any()) + ->method('getType') + ->willReturn('matchQuery'); + $filterQuery->expects($this->any()) + ->method('getType') + ->willReturn('matchQuery'); + $filterQuery->expects($this->any()) + ->method('getType') + ->willReturn('matchQuery'); + $this->filterBuilder->expects(($this->any())) + ->method('build') + ->willReturn([ + 'bool' => [ + 'must' => [], + ], + ]); + + $this->model->buildQuery($request); + } + + /** + * @return array + */ + public function buildQueryDataProvider() + { + return [ + [ + 'matchQuery', \Magento\Framework\Search\Request\Query\Match::class, + 'query', \Magento\Framework\Search\Request\QueryInterface::class, + ], + [ + 'boolQuery', \Magento\Framework\Search\Request\Query\BoolExpression::class, + 'query', \Magento\Framework\Search\Request\QueryInterface::class, + ], + [ + 'filteredQuery', \Magento\Framework\Search\Request\Query\Filter::class, + 'query', \Magento\Framework\Search\Request\QueryInterface::class, + ], + [ + 'filteredQuery', \Magento\Framework\Search\Request\Query\Filter::class, + 'filter', \Magento\Framework\Search\Request\FilterInterface::class, + ], + ]; + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php index 885a84e790b70..40009eb10a6c7 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_classes.php @@ -4249,7 +4249,6 @@ ['Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper'], ['Magento\Elasticsearch\Model\Client\Elasticsearch'], ['Magento\Elasticsearch\SearchAdapter\Aggregation\Interval'], - ['Magento\Elasticsearch\SearchAdapter\Mapper'], ['Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldType'], ['Magento\Elasticsearch\Model\Adapter\DataMapperInterface'], ['Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapperProxy'], From 5482cdfb366c45cd4d8704ace4634c34117226c6 Mon Sep 17 00:00:00 2001 From: Max Lesechko <mlesechko@magento.com> Date: Mon, 23 Mar 2020 22:37:35 -0500 Subject: [PATCH 369/369] MC-13825: [2.4.x] Migrate ZF2 components to Laminas -- fix merge conflict --- composer.json | 3 - composer.lock | 820 ++++++++++++++++++++------------------------------ 2 files changed, 322 insertions(+), 501 deletions(-) diff --git a/composer.json b/composer.json index 421e29123151b..0ecf3878fa7ff 100644 --- a/composer.json +++ b/composer.json @@ -65,9 +65,6 @@ "laminas/laminas-uri": "^2.5.1", "laminas/laminas-validator": "^2.6.0", "laminas/laminas-view": "~2.11.2", - "league/flysystem": "^1.0", - "league/flysystem-aws-s3-v3": "^1.0", - "league/flysystem-azure-blob-storage": "^0.1.6", "magento/composer": "1.6.x-dev", "magento/magento-composer-installer": ">=0.1.11", "magento/zendframework1": "~1.14.2", diff --git a/composer.lock b/composer.lock index 39e6fe4845513..51bceb357611d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,92 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "92b62c12f24800168ad73c8c41bb31e1", + "content-hash": "60eacd9a8d639c5c851fa8d443b3433f", "packages": [ - { - "name": "aws/aws-sdk-php", - "version": "3.133.32", - "source": { - "type": "git", - "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "c4bd227436446f02d2b9963f3ea4ae6dc380420b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/c4bd227436446f02d2b9963f3ea4ae6dc380420b", - "reference": "c4bd227436446f02d2b9963f3ea4ae6dc380420b", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-pcre": "*", - "ext-simplexml": "*", - "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", - "guzzlehttp/promises": "^1.0", - "guzzlehttp/psr7": "^1.4.1", - "mtdowling/jmespath.php": "^2.5", - "php": ">=5.5" - }, - "require-dev": { - "andrewsville/php-token-reflection": "^1.4", - "aws/aws-php-sns-message-validator": "~1.0", - "behat/behat": "~3.0", - "doctrine/cache": "~1.4", - "ext-dom": "*", - "ext-openssl": "*", - "ext-pcntl": "*", - "ext-sockets": "*", - "nette/neon": "^2.3", - "phpunit/phpunit": "^4.8.35|^5.4.3", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", - "sebastian/comparator": "^1.2.3" - }, - "suggest": { - "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", - "doctrine/cache": "To use the DoctrineCacheAdapter", - "ext-curl": "To send requests using cURL", - "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", - "ext-sockets": "To use client-side monitoring" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "psr-4": { - "Aws\\": "src/" - }, - "files": [ - "src/functions.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Amazon Web Services", - "homepage": "http://aws.amazon.com" - } - ], - "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", - "homepage": "http://aws.amazon.com/sdkforphp", - "keywords": [ - "amazon", - "aws", - "cloud", - "dynamodb", - "ec2", - "glacier", - "s3", - "sdk" - ], - "time": "2020-03-09T18:10:46+00:00" - }, { "name": "braintree/braintree_php", "version": "3.35.0", @@ -341,16 +257,16 @@ }, { "name": "composer/composer", - "version": "1.9.3", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "1291a16ce3f48bfdeca39d64fca4875098af4d7b" + "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/1291a16ce3f48bfdeca39d64fca4875098af4d7b", - "reference": "1291a16ce3f48bfdeca39d64fca4875098af4d7b", + "url": "https://api.github.com/repos/composer/composer/zipball/b912a45da3e2b22f5cb5a23e441b697a295ba011", + "reference": "b912a45da3e2b22f5cb5a23e441b697a295ba011", "shasum": "" }, "require": { @@ -363,17 +279,17 @@ "psr/log": "^1.0", "seld/jsonlint": "^1.4", "seld/phar-utils": "^1.0", - "symfony/console": "^2.7 || ^3.0 || ^4.0", - "symfony/filesystem": "^2.7 || ^3.0 || ^4.0", - "symfony/finder": "^2.7 || ^3.0 || ^4.0", - "symfony/process": "^2.7 || ^3.0 || ^4.0" + "symfony/console": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/filesystem": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/finder": "^2.7 || ^3.0 || ^4.0 || ^5.0", + "symfony/process": "^2.7 || ^3.0 || ^4.0 || ^5.0" }, "conflict": { "symfony/console": "2.8.38" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7", - "phpunit/phpunit-mock-objects": "^2.3 || ^3.0" + "phpspec/prophecy": "^1.10", + "symfony/phpunit-bridge": "^3.4" }, "suggest": { "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", @@ -386,7 +302,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.10-dev" } }, "autoload": { @@ -417,7 +333,7 @@ "dependency", "package" ], - "time": "2020-02-04T11:58:49+00:00" + "time": "2020-03-13T19:34:27+00:00" }, { "name": "composer/semver", @@ -1667,16 +1583,16 @@ }, { "name": "laminas/laminas-feed", - "version": "2.12.0", + "version": "2.12.1", "source": { "type": "git", "url": "https://github.com/laminas/laminas-feed.git", - "reference": "64d25e18a6ea3db90c27fe2d6b95630daa1bf602" + "reference": "c9356994eb80d0f6b46d7e12ba048d450bf0cd72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/64d25e18a6ea3db90c27fe2d6b95630daa1bf602", - "reference": "64d25e18a6ea3db90c27fe2d6b95630daa1bf602", + "url": "https://api.github.com/repos/laminas/laminas-feed/zipball/c9356994eb80d0f6b46d7e12ba048d450bf0cd72", + "reference": "c9356994eb80d0f6b46d7e12ba048d450bf0cd72", "shasum": "" }, "require": { @@ -1697,7 +1613,7 @@ "laminas/laminas-http": "^2.7", "laminas/laminas-servicemanager": "^2.7.8 || ^3.3", "laminas/laminas-validator": "^2.10.1", - "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20", "psr/http-message": "^1.0.1" }, "suggest": { @@ -1730,7 +1646,7 @@ "feed", "laminas" ], - "time": "2019-12-31T16:46:54+00:00" + "time": "2020-03-23T10:40:31+00:00" }, { "name": "laminas/laminas-filter", @@ -1803,16 +1719,16 @@ }, { "name": "laminas/laminas-form", - "version": "2.14.3", + "version": "2.14.4", "source": { "type": "git", "url": "https://github.com/laminas/laminas-form.git", - "reference": "012aae01366cb8c8fb64e39a887363ef82f388dd" + "reference": "8b985f74bfe32910edb4ba9503877c4310228cd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-form/zipball/012aae01366cb8c8fb64e39a887363ef82f388dd", - "reference": "012aae01366cb8c8fb64e39a887363ef82f388dd", + "url": "https://api.github.com/repos/laminas/laminas-form/zipball/8b985f74bfe32910edb4ba9503877c4310228cd2", + "reference": "8b985f74bfe32910edb4ba9503877c4310228cd2", "shasum": "" }, "require": { @@ -1841,7 +1757,7 @@ "laminas/laminas-text": "^2.6", "laminas/laminas-validator": "^2.6", "laminas/laminas-view": "^2.6.2", - "phpunit/phpunit": "^5.7.23 || ^6.5.3" + "phpunit/phpunit": "^5.7.27 || ^6.5.14 || ^7.5.20" }, "suggest": { "laminas/laminas-captcha": "^2.7.1, required for using CAPTCHA form elements", @@ -1881,7 +1797,7 @@ "form", "laminas" ], - "time": "2019-12-31T16:56:34+00:00" + "time": "2020-03-18T22:38:54+00:00" }, { "name": "laminas/laminas-http", @@ -2007,16 +1923,16 @@ }, { "name": "laminas/laminas-i18n", - "version": "2.10.1", + "version": "2.10.2", "source": { "type": "git", "url": "https://github.com/laminas/laminas-i18n.git", - "reference": "815be447f1c77f70a86bf24d00087fcb975b39ff" + "reference": "9699c98d97d2f519def3da8d4128dfe6e6ad11bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/815be447f1c77f70a86bf24d00087fcb975b39ff", - "reference": "815be447f1c77f70a86bf24d00087fcb975b39ff", + "url": "https://api.github.com/repos/laminas/laminas-i18n/zipball/9699c98d97d2f519def3da8d4128dfe6e6ad11bf", + "reference": "9699c98d97d2f519def3da8d4128dfe6e6ad11bf", "shasum": "" }, "require": { @@ -2078,7 +1994,7 @@ "i18n", "laminas" ], - "time": "2019-12-31T17:07:17+00:00" + "time": "2020-03-20T11:57:14+00:00" }, { "name": "laminas/laminas-inputfilter", @@ -3161,16 +3077,16 @@ }, { "name": "laminas/laminas-validator", - "version": "2.13.1", + "version": "2.13.2", "source": { "type": "git", "url": "https://github.com/laminas/laminas-validator.git", - "reference": "36702f033486bf1953e254f5299aad205302e79d" + "reference": "e7bf6a2eedc0508ebde0ebc66662efeb0abbcb2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/36702f033486bf1953e254f5299aad205302e79d", - "reference": "36702f033486bf1953e254f5299aad205302e79d", + "url": "https://api.github.com/repos/laminas/laminas-validator/zipball/e7bf6a2eedc0508ebde0ebc66662efeb0abbcb2c", + "reference": "e7bf6a2eedc0508ebde0ebc66662efeb0abbcb2c", "shasum": "" }, "require": { @@ -3236,7 +3152,7 @@ "laminas", "validator" ], - "time": "2020-01-15T09:59:30+00:00" + "time": "2020-03-16T11:38:27+00:00" }, { "name": "laminas/laminas-view", @@ -3381,178 +3297,6 @@ ], "time": "2020-01-07T22:58:31+00:00" }, - { - "name": "league/flysystem", - "version": "1.0.65", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem.git", - "reference": "8f17b3ba67097aafb8318cd5c553b1acf7c891c8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8f17b3ba67097aafb8318cd5c553b1acf7c891c8", - "reference": "8f17b3ba67097aafb8318cd5c553b1acf7c891c8", - "shasum": "" - }, - "require": { - "ext-fileinfo": "*", - "php": ">=5.5.9" - }, - "conflict": { - "league/flysystem-sftp": "<1.0.6" - }, - "require-dev": { - "phpspec/phpspec": "^3.4", - "phpunit/phpunit": "^5.7.26" - }, - "suggest": { - "ext-fileinfo": "Required for MimeType", - "ext-ftp": "Allows you to use FTP server storage", - "ext-openssl": "Allows you to use FTPS server storage", - "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", - "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", - "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", - "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", - "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", - "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", - "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", - "league/flysystem-webdav": "Allows you to use WebDAV storage", - "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", - "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", - "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "description": "Filesystem abstraction: Many filesystems, one API.", - "keywords": [ - "Cloud Files", - "WebDAV", - "abstraction", - "aws", - "cloud", - "copy.com", - "dropbox", - "file systems", - "files", - "filesystem", - "filesystems", - "ftp", - "rackspace", - "remote", - "s3", - "sftp", - "storage" - ], - "time": "2020-03-08T18:53:20+00:00" - }, - { - "name": "league/flysystem-aws-s3-v3", - "version": "1.0.24", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", - "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/4382036bde5dc926f9b8b337e5bdb15e5ec7b570", - "reference": "4382036bde5dc926f9b8b337e5bdb15e5ec7b570", - "shasum": "" - }, - "require": { - "aws/aws-sdk-php": "^3.0.0", - "league/flysystem": "^1.0.40", - "php": ">=5.5.0" - }, - "require-dev": { - "henrikbjorn/phpspec-code-coverage": "~1.0.1", - "phpspec/phpspec": "^2.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Flysystem\\AwsS3v3\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "description": "Flysystem adapter for the AWS S3 SDK v3.x", - "time": "2020-02-23T13:31:58+00:00" - }, - { - "name": "league/flysystem-azure-blob-storage", - "version": "0.1.6", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/flysystem-azure-blob-storage.git", - "reference": "97215345f3c42679299ba556a4d16d4847ee7f6d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-azure-blob-storage/zipball/97215345f3c42679299ba556a4d16d4847ee7f6d", - "reference": "97215345f3c42679299ba556a4d16d4847ee7f6d", - "shasum": "" - }, - "require": { - "guzzlehttp/psr7": "^1.5", - "league/flysystem": "^1.0", - "microsoft/azure-storage-blob": "^1.1", - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "^5.7" - }, - "type": "library", - "autoload": { - "psr-4": { - "League\\Flysystem\\AzureBlobStorage\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Frank de Jonge", - "email": "info@frenky.net" - } - ], - "time": "2019-06-07T20:42:16+00:00" - }, { "name": "magento/composer", "version": "1.6.x-dev", @@ -3715,94 +3459,6 @@ ], "time": "2019-11-26T15:09:40+00:00" }, - { - "name": "microsoft/azure-storage-blob", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/Azure/azure-storage-blob-php.git", - "reference": "6a333cd28a3742c3e99e79042dc6510f9f917919" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Azure/azure-storage-blob-php/zipball/6a333cd28a3742c3e99e79042dc6510f9f917919", - "reference": "6a333cd28a3742c3e99e79042dc6510f9f917919", - "shasum": "" - }, - "require": { - "microsoft/azure-storage-common": "~1.4", - "php": ">=5.6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "MicrosoftAzure\\Storage\\Blob\\": "src/Blob" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Azure Storage PHP Client Library", - "email": "dmsh@microsoft.com" - } - ], - "description": "This project provides a set of PHP client libraries that make it easy to access Microsoft Azure Storage Blob APIs.", - "keywords": [ - "azure", - "blob", - "php", - "sdk", - "storage" - ], - "time": "2020-01-02T07:18:59+00:00" - }, - { - "name": "microsoft/azure-storage-common", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/Azure/azure-storage-common-php.git", - "reference": "be4df800761d0d0fa91a9460c7f42517197d57a0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Azure/azure-storage-common-php/zipball/be4df800761d0d0fa91a9460c7f42517197d57a0", - "reference": "be4df800761d0d0fa91a9460c7f42517197d57a0", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "~6.0", - "php": ">=5.6.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "MicrosoftAzure\\Storage\\Common\\": "src/Common" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Azure Storage PHP Client Library", - "email": "dmsh@microsoft.com" - } - ], - "description": "This project provides a set of common code shared by Azure Storage Blob, Table, Queue and File PHP client libraries.", - "keywords": [ - "azure", - "common", - "php", - "sdk", - "storage" - ], - "time": "2020-01-02T07:15:54+00:00" - }, { "name": "monolog/monolog", "version": "1.25.3", @@ -3837,88 +3493,29 @@ "sentry/sentry": "^0.13", "swiftmailer/swiftmailer": "^5.3|^6.0" }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "time": "2019-12-20T14:15:16+00:00" - }, - { - "name": "mtdowling/jmespath.php", - "version": "2.5.0", - "source": { - "type": "git", - "url": "https://github.com/jmespath/jmespath.php.git", - "reference": "52168cb9472de06979613d365c7f1ab8798be895" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", - "reference": "52168cb9472de06979613d365c7f1ab8798be895", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "symfony/polyfill-mbstring": "^1.4" - }, - "require-dev": { - "composer/xdebug-handler": "^1.2", - "phpunit/phpunit": "^4.8.36|^7.5.15" + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" }, - "bin": [ - "bin/jp.php" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "JmesPath\\": "src/" - }, - "files": [ - "src/JmesPath.php" - ] + "Monolog\\": "src/Monolog" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -3926,17 +3523,19 @@ ], "authors": [ { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" } ], - "description": "Declaratively specify how to extract elements from a JSON document", + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", "keywords": [ - "json", - "jsonpath" + "log", + "logging", + "psr-3" ], - "time": "2019-12-30T18:03:34+00:00" + "time": "2019-12-20T14:15:16+00:00" }, { "name": "paragonie/random_compat", @@ -3985,16 +3584,16 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.12.2", + "version": "v1.13.0", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "3b953109fdfc821c1979bc829c8b7421721fef82" + "reference": "bbade402cbe84c69b718120911506a3aa2bae653" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/3b953109fdfc821c1979bc829c8b7421721fef82", - "reference": "3b953109fdfc821c1979bc829c8b7421721fef82", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bbade402cbe84c69b718120911506a3aa2bae653", + "reference": "bbade402cbe84c69b718120911506a3aa2bae653", "shasum": "" }, "require": { @@ -4063,7 +3662,7 @@ "secret-key cryptography", "side-channel resistant" ], - "time": "2019-12-30T03:11:08+00:00" + "time": "2020-03-20T21:48:09+00:00" }, { "name": "pelago/emogrifier", @@ -4263,16 +3862,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.25", + "version": "2.0.26", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "c18159618ed7cd7ff721ac1a8fec7860a475d2f0" + "reference": "09655fcc1f8bab65727be036b28f6f20311c126c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/c18159618ed7cd7ff721ac1a8fec7860a475d2f0", - "reference": "c18159618ed7cd7ff721ac1a8fec7860a475d2f0", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/09655fcc1f8bab65727be036b28f6f20311c126c", + "reference": "09655fcc1f8bab65727be036b28f6f20311c126c", "shasum": "" }, "require": { @@ -4351,7 +3950,7 @@ "x.509", "x509" ], - "time": "2020-02-25T04:16:50+00:00" + "time": "2020-03-13T04:15:39+00:00" }, { "name": "psr/container", @@ -5711,23 +5310,23 @@ }, { "name": "allure-framework/allure-php-api", - "version": "1.1.7", + "version": "1.1.8", "source": { "type": "git", "url": "https://github.com/allure-framework/allure-php-commons.git", - "reference": "243c9a2259b60c1b7a9d298d4fb3fc72b4f64c18" + "reference": "5ae2deac1c7e1b992cfa572167370de45bdd346d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-php-commons/zipball/243c9a2259b60c1b7a9d298d4fb3fc72b4f64c18", - "reference": "243c9a2259b60c1b7a9d298d4fb3fc72b4f64c18", + "url": "https://api.github.com/repos/allure-framework/allure-php-commons/zipball/5ae2deac1c7e1b992cfa572167370de45bdd346d", + "reference": "5ae2deac1c7e1b992cfa572167370de45bdd346d", "shasum": "" }, "require": { "jms/serializer": "^0.16 || ^1.0", "php": ">=5.4.0", "ramsey/uuid": "^3.0", - "symfony/http-foundation": "^2.0 || ^3.0 || ^4.0" + "symfony/http-foundation": "^2.0 || ^3.0 || ^4.0 || ^5.0" }, "require-dev": { "phpunit/phpunit": "^4.0.0" @@ -5760,7 +5359,7 @@ "php", "report" ], - "time": "2020-02-05T16:43:19+00:00" + "time": "2020-03-13T10:47:35+00:00" }, { "name": "allure-framework/allure-phpunit", @@ -5812,18 +5411,102 @@ ], "time": "2017-11-03T13:08:21+00:00" }, + { + "name": "aws/aws-sdk-php", + "version": "3.133.42", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "4f0259689e66237e429f9fc1d879b2bcdce6dadb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/4f0259689e66237e429f9fc1d879b2bcdce6dadb", + "reference": "4f0259689e66237e429f9fc1d879b2bcdce6dadb", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^5.3.3|^6.2.1|^7.0", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4.1", + "mtdowling/jmespath.php": "^2.5", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "phpunit/phpunit": "^4.8.35|^5.4.3", + "psr/cache": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2020-03-23T18:17:07+00:00" + }, { "name": "behat/gherkin", - "version": "v4.6.1", + "version": "v4.6.2", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "25bdcaf37898b4a939fa3031d5d753ced97e4759" + "reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/25bdcaf37898b4a939fa3031d5d753ced97e4759", - "reference": "25bdcaf37898b4a939fa3031d5d753ced97e4759", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/51ac4500c4dc30cbaaabcd2f25694299df666a31", + "reference": "51ac4500c4dc30cbaaabcd2f25694299df666a31", "shasum": "" }, "require": { @@ -5869,7 +5552,7 @@ "gherkin", "parser" ], - "time": "2020-02-27T11:29:57+00:00" + "time": "2020-03-17T14:03:26+00:00" }, { "name": "cache/cache", @@ -6057,16 +5740,16 @@ }, { "name": "codeception/phpunit-wrapper", - "version": "6.8.0", + "version": "6.8.1", "source": { "type": "git", "url": "https://github.com/Codeception/phpunit-wrapper.git", - "reference": "20e054e9cee8de0523322367bcccde8e6a3db263" + "reference": "ca6e94c6dadc19db05698d4e0d84214e570ce045" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/20e054e9cee8de0523322367bcccde8e6a3db263", - "reference": "20e054e9cee8de0523322367bcccde8e6a3db263", + "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/ca6e94c6dadc19db05698d4e0d84214e570ce045", + "reference": "ca6e94c6dadc19db05698d4e0d84214e570ce045", "shasum": "" }, "require": { @@ -6085,7 +5768,7 @@ "type": "library", "autoload": { "psr-4": { - "Codeception\\PHPUnit\\": "src\\" + "Codeception\\PHPUnit\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -6099,7 +5782,7 @@ } ], "description": "PHPUnit classes used by Codeception", - "time": "2020-01-03T08:01:16+00:00" + "time": "2020-03-20T08:05:05+00:00" }, { "name": "codeception/stub", @@ -7598,6 +7281,90 @@ ], "time": "2017-05-10T09:20:27+00:00" }, + { + "name": "league/flysystem", + "version": "1.0.66", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/021569195e15f8209b1c4bebb78bd66aa4f08c21", + "reference": "021569195e15f8209b1c4bebb78bd66aa4f08c21", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.26" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "time": "2020-03-17T18:58:12+00:00" + }, { "name": "lusitanian/oauth", "version": "v0.8.11", @@ -7835,6 +7602,63 @@ "homepage": "http://vfs.bovigo.org/", "time": "2019-10-30T15:31:00+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.5.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "52168cb9472de06979613d365c7f1ab8798be895" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/52168cb9472de06979613d365c7f1ab8798be895", + "reference": "52168cb9472de06979613d365c7f1ab8798be895", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "symfony/polyfill-mbstring": "^1.4" + }, + "require-dev": { + "composer/xdebug-handler": "^1.2", + "phpunit/phpunit": "^4.8.36|^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2019-12-30T18:03:34+00:00" + }, { "name": "mustache/mustache", "version": "v2.13.0", @@ -8514,20 +8338,20 @@ }, { "name": "phpoption/phpoption", - "version": "1.7.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959" + "reference": "4acfd6a4b33a509d8c88f50e5222f734b6aeebae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959", - "reference": "77f7c4d2e65413aff5b5a8cc8b3caf7a28d81959", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/4acfd6a4b33a509d8c88f50e5222f734b6aeebae", + "reference": "4acfd6a4b33a509d8c88f50e5222f734b6aeebae", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0" + "php": "^5.5.9 || ^7.0 || ^8.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.3", @@ -8565,7 +8389,7 @@ "php", "type" ], - "time": "2019-12-15T19:35:24+00:00" + "time": "2020-03-21T18:07:53+00:00" }, { "name": "phpspec/prophecy", @@ -8632,16 +8456,16 @@ }, { "name": "phpstan/phpstan", - "version": "0.12.14", + "version": "0.12.18", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "37bdd26a80235d0f9045b49f4151102b7831cbe2" + "reference": "1ce27fe29c8660a27926127d350d53d80c4d4286" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/37bdd26a80235d0f9045b49f4151102b7831cbe2", - "reference": "37bdd26a80235d0f9045b49f4151102b7831cbe2", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/1ce27fe29c8660a27926127d350d53d80c4d4286", + "reference": "1ce27fe29c8660a27926127d350d53d80c4d4286", "shasum": "" }, "require": { @@ -8667,7 +8491,7 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "time": "2020-03-02T22:29:43+00:00" + "time": "2020-03-22T16:51:47+00:00" }, { "name": "phpunit/php-code-coverage",